最近工作需要,用python实现调用接口的示例代码,过程涉及到很多的加密算法,值得分享一下。
首先公钥和私钥如何生成,并且能兼容java平台,尝试了很多方法。最终决定用openssl命令
前提,需要安装openssl,Crypto库
生成公钥私钥对过程:
生成私钥:
openssl genrsa -out rsa_private_key.pem 1024
根据私钥生成公钥:
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
这时候的私钥还不能直接被使用,需要进行PKCS#8编码
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
命令中指明了输入私钥文件为rsa_private_key.pem,输出私钥文件为pkcs8_rsa_private_key.pem,不采用任何二次加密(-nocrypt)
这时候就获得了一对公钥和私钥,只要拿到对方的公钥,用自己的公钥的格式替换就可以使用啦~~
我们最好把全局变量给提出来,便于管理。这样子就不用改代码都改一遍了
文件Gl.py
#!-*- coding:utf-8 -*-
'''
Created on 2013-6-15
@author: shangwei
'''
'''
全局变量
'''
from Crypto.PublicKey import RSA
'''
publickey为对方的公钥
privatekey为商户自己的私钥
'''
publickey = RSA.importKey(open('rsa_public_key.pem','r').read())
privatekey=RSA.importKey(open('pkcs8_rsa_private_key.pem','r').read())
merchantaccount='YB010000000xx'
URL='xxx.xxx.com'
#!-*- coding:utf-8 -*-
'''
Created on 2013-5-24
@author: shangwei
'''
from Crypto import Random
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Hash import SHA
from hashlib import sha1
from rsa import key, common, encrypt
from urllib import urlencode
import base64
import hmac
from Crypto.PublicKey import RSA
import urllib
import urllib2
import time
import json
from Crypto.Signature import PKCS1_v1_5 as pk
import Gl
class MerchantAPI:
def doPost(self,url,values):
'''
post请求
参数URL
字典类型的参数
'''
req = urllib2.Request(url)
data = urllib.urlencode(values)
res = urllib2.urlopen(req, data)
ret = res.read()
return ret
def doGet(self,url,values):
'''
get请求
参数URL
字典类型的参数
'''
REQUEST = url + "?" + urllib.urlencode(values)
ret = urllib2.urlopen(REQUEST).read()
return ret
@staticmethod
def _pkcs7padding(data):
"""
对齐块
size 16
999999999=>9999999997777777
"""
size = AES.block_size
count = size - len(data)%size
if count:
data+=(chr(count)*count)
return data
@staticmethod
def _depkcs7padding(data):
"""
反对齐
"""
newdata = ''
for c in data:
if ord(c) > AES.block_size:
newdata+=c
return newdata
'''
aes加密base64编码
'''
def aes_base64_encrypt(self,data,key):
"""
@summary:
1. pkcs7padding
2. aes encrypt
3. base64 encrypt
@return:
string
"""
cipher = AES.new(key)
return base64.b64encode(cipher.encrypt(self._pkcs7padding(data)))
def base64_aes_decrypt(self,data,key):
"""
1. base64 decode
2. aes decode
3. dpkcs7padding
"""
cipher = AES.new(key)
return self._depkcs7padding(cipher.decrypt(base64.b64decode(data)))
'''
rsa加密
'''
def rsa_base64_encrypt(self,data,key):
'''
1. rsa encrypt
2. base64 encrypt
'''
cipher = PKCS1_v1_5.new(key)
return base64.b64encode(cipher.encrypt(data))
'''
rsa解密
'''
def rsa_base64_decrypt(self,data,key):
'''
1. base64 decrypt
2. rsa decrypt
示例代码
key = RSA.importKey(open('privkey.der').read())
>>>
>>> dsize = SHA.digest_size
>>> sentinel = Random.new().read(15+dsize) # Let's assume that average data length is 15
>>>
>>> cipher = PKCS1_v1_5.new(key)
>>> message = cipher.decrypt(ciphertext, sentinel)
>>>
>>> digest = SHA.new(message[:-dsize]).digest()
>>> if digest==message[-dsize:]: # Note how we DO NOT look for the sentinel
>>> print "Encryption was correct."
>>> else:
>>> print "Encryption was not correct."
'''
cipher = PKCS1_v1_5.new(key)
return cipher.decrypt(base64.b64decode(data), Random.new().read(15+SHA.digest_size))
'''
RSA签名
'''
def sign(self,signdata):
'''
@param signdata: 需要签名的字符串
'''
h=SHA.new(signdata)
signer = pk.new(Gl.privatekey)
signn=signer.sign(h)
signn=base64.b64encode(signn)
return signn
'''
RSA验签
结果:如果验签通过,则返回The signature is authentic
如果验签不通过,则返回"The signature is not authentic."
'''
def checksign(self,rdata):
signn=base64.b64decode(rdata.pop('sign'))
signdata=self.sort(rdata)
verifier = pk.new(Gl.publickey)
if verifier.verify(SHA.new(signdata), signn):
print "The signature is authentic."
else:
print "The signature is not authentic."
def sort(self,mes):
'''
作用类似与java的treemap,
取出key值,按照字母排序后将value拼接起来
返回字符串
'''
_par = []
keys=mes.keys()
keys.sort()
for v in keys:
_par.append(str(mes[v]))
sep=''
message=sep.join(_par)
return message
'''
请求接口前的加密过程
'''
def requestprocess(self,mesdata):
'''
加密过程:
1、将需要的参数mes取出key排序后取出value拼成字符串signdata
2、用signdata对商户私钥进行rsa签名,生成签名signn,并转base64格式
3、将签名signn插入到mesdata的最后生成新的data
4、用encryptkey16位常量对data进行AES加密后转BASE64,生成机密后的data
5、用对方公钥publickey对encryptkey16位常量进行RSA加密BASE64编码,生成加密后的encryptkey
'''
signdata=self.sort(mesdata)
print '需要签名的排序后的字符串为:'+signdata
signn=self.sign(signdata)
mesdata['sign']=signn
print mesdata
encryptkey = '1234567890123456'
data=self.aes_base64_encrypt(json.dumps(mesdata),encryptkey)
print '加密后的data='+data
values={}
values['merchantaccount']=Gl.merchantaccount
values['data']=data
values['encryptkey']=self.rsa_base64_encrypt(encryptkey,Gl.publickey)
return values
'''
对返回结果进行解密后输出
'''
def result_decrypt(self,result):
'''
1、返回的结果json传给data和encryptkey两部分,都为加密后的
2、用商户私钥对encryptkey进行RSA解密,生成解密后的encryptkey。参考方法:rsa_base64_decrypt
3、用解密后的encryptkey对data进行AES解密。参考方法:base64_aes_decrypt
'''
result=json.loads(result)
kdata=result['data']
kencryptkey=result['encryptkey']
print '返回的加密后的data='+kdata
print '返回的加密后的encryptkey='+kencryptkey
cryptkey=self.rsa_base64_decrypt(kencryptkey,Gl.privatekey)
print '解密后的encryptkey='+cryptkey
rdata=self.base64_aes_decrypt(kdata,cryptkey)
print '解密后的data='+rdata
return rdata
def testCreditPayAsync(self):
'''
生成公钥私钥对过程:
生成私钥:openssl genrsa -out rsa_private_key.pem 1024
根据私钥生成公钥: openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
这时候的私钥还不能直接被使用,需要进行PKCS#8编码:
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
命令中指明了输入私钥文件为rsa_private_key.pem,输出私钥文件为pkcs8_rsa_private_key.pem,不采用任何二次加密(-nocrypt)
加密过程:
1、将需要的参数mes取出key排序后取出value拼成字符串signdata
2、用signdata对商户私钥进行rsa签名,生成签名signn,并转base64格式
3、将签名signn插入到mes的最后生成新的data
4、用encryptkey16位常量对data进行AES加密后转BASE64,生成机密后的data
5、用对方公钥publickey对encryptkey16位常量进行RSA加密BASE64编码,生成加密后的encryptkey
6、将merchantaccount,第四部加密后的data,第五步加密后的encryptkey作为参数post请求给URL http://xxxx/xxx/api/xxx/xxx/xxx/xxx
7、返回的结果json传给data和encryptkey两部分,都为加密后的
8、用商户私钥对encryptkey进行RSA解密,生成解密后的encryptkey。参考方法:rsa_base64_decrypt
9、用解密后的encryptkey对data进行AES解密。参考方法:base64_aes_decrypt
'''
transtime=int(time.time())
od=str(random.randint(10, 100000))
mesdata={"merchantaccount":Gl.merchantaccount,"cardno":"xxxx758xxxx23xxxx","validthru":"04xx","cvv2":"200","phone":"1581xxxxxxx",
"orderid":"33hhkssseef3u"+od,"transtime":transtime,"currency":156,"amount":2,"productcatalog":"1","productname":"","productdesc":"",
"userip":"192.168.5.251","identityid":"ee","identitytype":6,"other":"","callbackurl":"http://IP/webtest/callback.do"}
values=self.requestprocess(mesdata)
url='http://'+Gl.URL+'/xxxx'
print url
result=self.doPost(url, values)
print result
rdata=json.loads(self.result_decrypt(result))
self.checksign(rdata)
if __name__=='__main__':
mer=MerchantAPI()
mer.testCreditPayAsync()
知识点:
调试代码的时候也遇到了一些小问题和技巧
import的时候,如果有同名的类可以起个别名。不然会有报错,告诉这个类找不到某个方法from Crypto.Cipher import PKCS1_v1_5,from Crypto.Signature import PKCS1_v1_5 as pk,这个需要注意一下
另外,如果想将字典内的单引号都变为双引号,可以用json.dumps方法