因为支付宝官方的sdk并未提供Swift的版本,所以需要自己实现签名和验签。本文使用swift版本5.7+
版本配合krzyzanowskim/CryptoSwift实现。当然如果使用vapor/jwt-kit也可以的,用jwt-kit会更方便(无需处理密钥格式),验证过是可以走通的,因为我需要使用CryptoSwift的其他功能,所以这里以CryptoSwift为例。
密钥的处理
1、生成应用公钥和应用私钥
下载支付宝的开放平台密钥工具,生成密钥,加密算法为RSA2,生成应用公钥和应用私钥,工具默认生成的是X.509/SPKI 和 PKCS#8,需要转为PKCS#1格式。
2、获取支付宝公钥
在支付宝支付控制后台,设置加签方式密钥方式,之后将工具应用公钥复制到填写应用公钥输入框,点击确认上传,获得支付宝公钥。然后下载支付宝公钥,会得到一个公钥的txt文件。应用公钥的用处已完成,可以删除。
3、处理支付宝公钥
打开下载的支付宝公钥txt文件,在头部和尾部添加标识
//头部增加
-----BEGIN PUBLIC KEY-----
//尾部增加
-----END PUBLIC KEY-----
改造之后的文件内容类似于
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ……
-----END PUBLIC KEY-----
命令行执行下面命令转换为pkcs1格式,会得到alipayPublicKey_RSA2_pkcs1.txt文件,后续使用
openssl rsa -pubin -in alipayPublicKey_RSA2.txt -RSAPublicKey_out -out alipayPublicKey_RSA2_pkcs1.txt
4、应用私钥处理
开放平台密钥工具生成的应用私钥txt文件重命名为private_pkcs8.txt
,打开文件在头部和尾部分别增加以下标识
//头部增加
-----BEGIN PRIVATE KEY-----
//尾部增加
-----END PRIVATE KEY-----
改造之后的文件内容类似于
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASC……
-----END PRIVATE KEY-----
命令行执行以下命令转换为PKCS#1格式,会得到private_pkcs1.txt文件,留作后续使用
openssl pkey -in private_pkcs8.txt -out private_pkcs1.txt -traditional
发起支付
关键点:
1、表单的gateway.do需要设置charset,否则默认是gbk格式
var html = "<html><body onload='document.forms[0].submit()'>"
html += "<form method='POST' action='https://openapi.alipay.com/gateway.do?charset=utf-8'>"
2、构建post参数字典,加密时不要去掉sign_type,使用pkcs#1格式的私钥签名
3、签名传值传进去的是摘要,而不是原始数据,所以使用可以使用
需要验签字符串拼接
let unsignedString = parameters
.filter { $0.key != "sign"}
.sorted(by: { $0.key < $1.key })
.map { "\($0)=\($1)" }
.joined(separator: "&")
privateKeyRAS.sign(Data(unsignedString.utf8).byteArray.sha256(), variant: .digest_pkcs1v15_SHA256)
或者
privateKeyRAS.sign(Data(unsignedString.utf8).byteArray, variant: .message_pkcs1v15_SHA256)
支付验签
关键点:
- 验签使用前面生成的pkcs#1格式的支付宝公钥,而不是工具生成的应用公钥
- 在通知返回参数列表中,除去sign、sign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。(生活号异步待验签参数需保留sign_type参数)
- 将剩下参数进行url_decode, 然后进行字典排序,组成字符串,得到待签名字符串;
- 将签名参数(sign)使用 base64 解码为字节码串;
验签字符串拼接
let unsignedString = params
.filter { $0.key != "sign" && $0.value != "" && $0.key != "sign_type" }
.sorted(by: { $0.key < $1.key })
.map { "\($0.key)=\($0.value)" }
.joined(separator: "&")
let publicKeyRAS = try RSA(rawRepresentation: publicKeyData)
let result = try publicKeyRAS.verify(signature: signData.byteArray, for: Data(unsignedString.utf8).byteArray, variant: .message_pkcs1v15_SHA256)
return result
版权属于:东哥笔记 - DongGe.org
本文链接:https://blog.dongge.org/1387.html
本文采用知识共享署名4.0 国际许可协议进行许可。转载或大段使用必须添加本文链接,否则您将构成侵权!
微信公众号: 东哥org