最近用python作为客户端与.net下的web service进行数据交互,包括RSA密钥方面的交互遇到的一些问题的记录。
连接web service
在网上查看了一些方法后,最后使用suds这个库作为连接web service的工具,这个工具使用起来比较方便,算是比较小巧的,安装就直接在python shell下使用easy_install suds
就可以了。
>>> import suds
>>> url='http://localhost:8000/Host/host?wsdl'
>>> client = suds.client.Client(url)
>>> print client
Suds ( https://fedorahosted.org/suds/ ) version: 0.4 GA build: R699-20100913
Service ( Server ) tns="http://tempuri.org/"
Prefixes (1)
ns0 = "http://schemas.microsoft.com/2003/10/Serialization/"
Ports (1):
(BasicHttpBinding_Iserver)
Methods (4):
GetData(xs:base64Binary requestData, )
GetDataCopy(xs:base64Binary requestData, )
GetDataCopy2(xs:base64Binary requestData, )
GetKey()
Types (3):
ns0:char
ns0:duration
ns0:guid
>>> server = client.service
>>>
上面是我本地启动的一个服务,url就是服务发布的地址,在将地址输入到浏览器后可以看到发布的一些元数据。而在使用print client
打印出来的一些信息主要是Method
这栏,这里显示了四个方法,是我服务发布的四个对外接口,也是客户端调用的四个接口,包含有参数的一些基本信息。最后的server就可以调用服务接口的对象,例如value=server.GetKey()
,这里的value
就是服务返回的结果数据。对于suds
的具体用法及详细的信息可以查看suds文档,文档写的非常详细。返回的数据如果是一堆的字母,试着用base64解密后看看。
RSA加密的交互
由于服务端是使用.net发布的,相应的rsa的表示格式为xml,而python下的我使用的是rsa这个库,格式为pem的,两者的格式不一致,若要交互就必须将格式统一后才行。rsa
这个库有很多方法,具体可以使用help(rsa)
查看,类PublicKey
有个构造方法需要两个参数key=rsa.PublicKey(n,e)
,结果key
就是构造出的公钥,由于我这里GetKey
方法将服务端的公钥传递到了客户端:
>>> serverPubKey_CS = base64.b64decode(server.GetKey())
>>> print serverPubKey_CS
<RSAKeyValue><Modulus>2VCRn9IUE8ufRHIswpsTFiYr6sYT/SorKw/A1GTE4ux3oS12z4uRqrWGeGkXZ8q68HvmCFSn4h5xS+yvN17Fnw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
>>> serverPubKey_py = rsa.PublicKey(bytes2int(base64.b64decode('2VCRn9IUE8ufRHIswpsTFiYr6sYT/SorKw/A1GTE4ux3oS12z4uRqrWGeGkXZ8q68HvmCFSn4h5xS+yvN17Fnw==')),bytes2int(base64.b64decode('AQAB')))
这里serverPubKey_CS
为解密后的服务端公钥,是个xml格式的,而上面key=rsa.PublicKey(n,e)
构造方法中的n就是<Modulus>n</Modulus>
中间的这部分数据,当然e就是<Exponent>e</Exponent>
中间的数据了。两个数据全部获得,那么我们就可以转换成python
下面的公钥了,当然这两部分数据都是经过base64
加密的,所以我们首先要进行解密,由于python
的密钥的e
和n
都是一串数据,整数类型,那么还需要将解密后的数据转换成int
类型,这里有个方法bytes2int
就是完成这部分功能的,上面代码中的serverPubKey_py
就是最终的python
下面的公钥格式了。
def bytes2int(bytes):
"""Converts a list of bytes or a string to an integer
>>> (128*256 + 64)*256 + + 15
8405007
>>> l = [128, 64, 15]
>>> bytes2int(l)
8405007
"""
if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
raise TypeError("You must pass a string or a list")
# Convert byte stream to integer
integer = 0
for byte in bytes:
integer *= 256
if type(byte) is types.StringType: byte = ord(byte)
integer += byte
return integer
将python
的密钥格式转换成C#
能看懂的xml格式就是上面的逆过程,我们可以获得公钥的n
和e
,然后将它们都转换成byte
并且加密后组合成xml的格式,下面的代码完成了这个过程的转换:
pubKey_CS = '<RSAKeyValue><Modulus>'+base64.b64encode(int2bytes(pubKey.n))+'</Modulus><Exponent>'+base64.b64encode(int2bytes(pubKey.e))+'</Exponent></RSAKeyValue>'
pubkey_CS
就是C#
能看懂的密钥格式。
def int2bytes(number):
if not (type(number) is types.LongType or type(number) is types.IntType):
raise TypeError("You MUST ENTER A LONG OR INT")
string =""
while number>0:
string = "%s%s" % (chr(number & 0xff),string)
number/=256
return string
RSA在加密解密的时候注意事项
我们在使用rsa加密解密的时候最多,被加密解密的消息有个最大长度,由key.size
决定,在消息长度超过最大值的时候我们需要对消息进行分段加密和解密。
def encodeSplit(message,clientPubKey):
'''
分段加密 由于我这边的密钥长度为512,所以下面的53为key.size/8-11得出
'''
count = len(message)
if count>53:
jiami=""
index = 0
while index<count:
i = 53 if count-index>53 else count-index
submsg = message[index:index+i]
index+=i
j = rsa.encrypt(submsg,clientPubKey)
jiami+=j
return jiami
else:
jiami = rsa.encrypt(message,clientPubKey)
return jiami
def decodeSplit(message,clientPriKey):
"""
消息在传送过来之前是经过base64加密的,所以要先进行base64解密后才进行RSA分段解密
64为key.size/8,密钥长度为512
"""
message = base64.b64decode(base64.b64decode(message))
count = len(message)
if count>64:
jiemi=""
index=0
while index<count:
i = 64 if count-index>64 else count-index
submsg = message[index:index+i]
index+=i
j = rsa.decrypt(submsg,clientPriKey)
jiemi+=j
return jiemi
else:
jiemi = rsa.decrypt(message,clientPriKey)
return jiemi
第一次写python程序,感觉和写伪代码似的,挺爽!