Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

关于Python下的支付宝APP支付

$
0
0

最近在折腾支付宝的移动支付(alipay.trade.app.pay), 直观的来说就是让iOS或者Android应用唤起支付宝,支付宝支付成功之后再返回应用。

支付宝APP支付没有提供python的SDK,后台和iOS以及Android的对接难免遇到了些问题,尤其是SHA1withRSA签名的部分。本文将会简单介绍一下支付宝APP支付的一些流程以及原理,希望能够给后来的Pythoner一些解决思路。

支付宝APP支付的流程 服务端拼接Order String, 对Order String进行SHA1withRSA签名并返回给APP,注意此时服务端并没有向支付宝发起请求。 APP调用支付宝的SDK,传入Order String进行支付。 支付成功之后,本地APP支付宝同步传回的result。服务端异步收到支付宝的消息。 Order String的拼接

将字典的key,value按照字母表的顺序排列在一起即可。比如原本是一个字典:

data = { "out_trade_no": "201601020304", "biz_content": {"product": "xxx", "title": "xxx"} }

排序之后得到 biz_content={"product":"xxx","title":"xxx"}&out_trade_no=201601020304 。请注意这里的value并没有进行url encode。

以下的代码可以帮你生成Order String。biz_content是一个json的字符串,因此biz_content里面的内容理论上是不需要排序的。

import json def ordered_data(data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 将字典类型的数据单独排序 for key in complex_keys: data[key] = json.dumps(data[key], sort_keys=True).replace(" ", "") return sorted([(k, v) for k, v in data.items()]) unsigned_items = ordered_data(data) unsigned_string = "&".join("{}={}".format(k, v) for k, v in unsigned_items) 签名 RSA私钥的生成

支付宝官方文档 里面给出了生成私钥的方法, openssl貌似更加方便一点:

OpenSSL> genrsa -out app_private_key.pem 1024 #生成私钥 OpenSSL> pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem #生成公钥 OpenSSL> exit #退出OpenSSL程序

运行上述的命令, 我们会得到三个文件:

app_private_key.pem --这是 pkcs#1格式的私钥 app_private_key_pkcs8.pem -- 这是pkcs#8格式的私钥 app_public_key.pem -- 这是pkcs#8格式的公钥

我们下面的Python和openssl用到的private key file,同时支持PKCS#1 和PKCS#8两种格式,因此可以传入任意一种格式的Private Key。

openssl对字符串进行SHA1withRSA签名

支付宝的规则是对Order String 进行RSAwithSHA1 签名,也即对Order String先SHA1得到摘要,然后对这个摘要进行签名。在继续阅读本文前,你需要明白摘要以及签名是什么意思。

运行以下命令可以对 abc\n (注意echo会额外传入一 个\n )进行签名, 然后输出base64编码的结果。

echo "abc" | openssl sha1 -sign privatekey.file | openssl base64 SHA1withRSA的Python实现

运行以下命令可以使用Python对 abc\n 进行签名:

from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA from Crypto.PublicKey import RSA def sign_string(private_key_path, unsigned_string): # 开始计算签名 key = RSA.importKey(open(private_key_path).read()) signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA.new(unsigned_string.encode("utf8"))) # base64 编码,转换为unicode表示并移除回车 sign = base64.encodebytes(signature).decode("utf8").replace("\n", "") return sign signed_string = sign_string(private_key_path, "abc\n")

Crypto 是Python自带的库。通过比较openssl以及Python计算 abc\n 的结果,如果二者相同则证明Python的签名实现正确。之后你就可以对更加复杂的Order String 进行签名了。

签名验证

我们之前已经获得了对 abc\n 进行签名的base64结果。现在可以用公钥验证之前生成的结构是否正确,以下的代码, 如果你的result为True那么表明验证签名功能是正确的。

def validate_sign(public_key_path, message, signature): # 开始计算签名 key = RSA.importKey(open(public_key_path).read()) signer = PKCS1_v1_5.new(key) digest = SHA.new() digest.update(message.encode("utf8")) if signer.verify(digest, base64.decodestring(signature.encode("utf8"))): return True return False result = validate_sign(your_public_key_path, "abc\n", signed_string) 再次生成 Order String

首先,我们之前已经生成过一次Order String: biz_content={"product":"xxx","title":"xxx"}&out_trade_no=201601020304 。生成的签名也需要被添加到里面: biz_content={"product":"xxx","title":"xxx"}&out_trade_no=201601020304&sign=OMXv7kLz

其次,服务器返回给APP的Order String是需要URL Encode的。也就是将key=value转换成key=quote_plus(value)。也就是这样的: biz_content=%7B%22product%22%3A%22xxx%22%2C%22title%22%3A%22xxx%22%7D&out_trade_no=201601020304&sign=OMXv7kLz .

异步回调支付结果的验证

之前我们有使用过自己的公钥验证自己的私钥签名的结果。验证支付结果回调请求的原理类似,只是需要使用支付宝的公钥。

其他

支付宝的消息返回非常不友好,比如唤起支付宝支付的时候,你有时候会看到错误 ALI40247 。

出现这种错误的原因有很多,签名错误是常见的一种。此时你需要使用openssl原生命令计算SHA1withRSA,再对比自己的结果,这样就可以保证签名是对的(再次说明,echo会传入额外的 \n 给openssl,你最好从文件读入需要openssl加密的字符串)。

当然也有其他原因,比如我传入参数 appid 而实际上应该传入 app_id 。当然坑爹的支付宝是不会有这么详细的提示的。


Viewing all articles
Browse latest Browse all 9596

Trending Articles