寻找官方且经过测试的支付宝签名与加密解密工具类
明确结论
参考实现应优先从支付宝开放平台的目标接口文档、官方 SDK 下载页或官方代码仓库中获取。如果目标接口采用支付宝开放平台的通用签名机制,可以重点参考官方 Java SDK 中的 AlipaySignature 等工具类。Java SDK 源码较完整,适用范围也较广,适合用来核对 Ruby 实现。
不要直接移植博客或零散代码片段中的所谓“支付宝工具类”。签名失败往往不是 RSA 算法出了问题,而是待签名字符串、字符编码、参数过滤规则或响应验签范围不一致。
支付宝不同产品、接口版本和接入模式可能使用不同协议。因此,具体实现必须遵循目标接口文档规定的 sign_type、密钥格式、签名字段和验签方式。
去哪里找官方参考实现
可以按以下顺序查找:
- 打开支付宝开放平台的目标 API 文档。
- 查看文档中的“SDK 调用”“签名机制”“接口工具”或“示例代码”。
- 下载官方 Java SDK 或对应语言的 SDK。
- 在 SDK 源码中搜索以下类或方法:
AlipaySignaturersaSignrsaCheckV1rsa256CheckContentgetSignContent
- 使用开放平台提供的签名验签工具,交叉验证 Ruby 实现。
官方 SDK 的包名和方法名可能随版本变化,不能只根据类名判断实现是否适用于当前接口。更可靠的依据是接口文档引用的 SDK 版本及其源码。
在采用支付宝开放平台通用协议的 Java SDK 中,相关工具通常位于类似下面的包中:
com.alipay.api.internal.util.AlipaySignature
如果接口还涉及 AES 解密,可以在同一版本的 SDK 中继续搜索 AlipayEncrypt、aesEncrypt、aesDecrypt 等名称。具体类名以实际下载的官方 SDK 为准。
Ruby 版 RSA2 签名参考
下面是一份通用的 RSA2 签名示例,可以作为移植时的代码骨架。参数过滤规则和待签名内容仍需根据具体支付宝接口确认。
require "openssl"
require "base64"
module AlipayCrypto
module_function
def sign_content(params)
params
.reject { |key, value| key.to_s == "sign" || value.nil? || value.to_s.empty? }
.sort_by { |key, _value| key.to_s }
.map { |key, value| "#{key}=#{value}" }
.join("&")
end
def rsa2_sign(content, private_key_pem)
private_key = OpenSSL::PKey::RSA.new(private_key_pem)
signature = private_key.sign(OpenSSL::Digest::SHA256.new, content)
Base64.strict_encode64(signature)
end
def rsa2_verify(content, signature, public_key_pem)
public_key = OpenSSL::PKey::RSA.new(public_key_pem)
decoded_signature = Base64.strict_decode64(signature)
public_key.verify(
OpenSSL::Digest::SHA256.new,
decoded_signature,
content
)
rescue ArgumentError, OpenSSL::PKey::PKeyError
false
end
end
调用示例:
params = {
"app_id" => "your_app_id",
"method" => "alipay.trade.page.pay",
"charset" => "utf-8",
"sign_type" => "RSA2",
"timestamp" => "2026-09-09 10:00:00",
"version" => "1.0",
"biz_content" => '{"out_trade_no":"ORDER_1001","total_amount":"0.01"}'
}
content = AlipayCrypto.sign_content(params)
signature = AlipayCrypto.rsa2_sign(content, private_key_pem)
puts content
puts signature
这里采用的是:
RSA + SHA-256 + Base64
这就是支付宝文档通常所说的 RSA2 签名方式。如果目标接口明确要求 RSA,其摘要算法通常不同,不能直接使用 SHA256。
公钥和私钥格式处理
Ruby 的 OpenSSL::PKey::RSA 通常需要 PEM 格式的密钥。如果配置中保存的是不含头尾标记的 Base64 内容,可以先补上 PEM 标记:
def wrap_private_key(base64_key)
body = base64_key.scan(/.{1,64}/).join("\n")
<<~PEM
-----BEGIN PRIVATE KEY-----
#{body}
-----END PRIVATE KEY-----
PEM
end
def wrap_public_key(base64_key)
body = base64_key.scan(/.{1,64}/).join("\n")
<<~PEM
-----BEGIN PUBLIC KEY-----
#{body}
-----END PUBLIC KEY-----
PEM
end
私钥可能采用 PKCS#1,也可能采用 PKCS#8。两种格式的 PEM 头通常分别是:
-----BEGIN RSA PRIVATE KEY-----
和:
-----BEGIN PRIVATE KEY-----
不要试图只修改 PEM 头来“转换”密钥格式。如果格式不匹配,应使用 OpenSSL 等工具完成真正的格式转换。
如何证明 Ruby 实现正确
验证实现时,最好准备一组固定测试向量,而不是只尝试一次线上请求:
- 固定一组参数、私钥和支付宝公钥。
- 使用官方 SDK 生成待签名字符串和签名。
- 使用 Ruby 对同一个字符串签名。
- 分别进行交叉验签:
- 用 Ruby 验证官方 SDK 生成的签名;
- 用官方 SDK 验证 Ruby 生成的签名。
- 继续测试中文、空值、特殊字符和嵌套 JSON。
对于 RSA PKCS#1 v1.5 签名,如果密钥、摘要算法和原文相同,通常可以直接比较 Base64 结果。即便结果可以直接比较,验签是否成功仍是更重要的判断标准。
容易踩坑的地方
待签名字符串必须完全一致
是否排除空值和 sign 参数、是否按参数名排序,以及 biz_content 是否保留原始字符串,都要严格遵循接口文档。
例如,下面两个 JSON 的语义相同,但作为签名原文时并不相同:
{"a":1,"b":2}
{ "b": 2, "a": 1 }
完成签名后,不要重新格式化 JSON,也不要先解析再序列化或改变字段顺序。
URL 编码和签名是两个阶段
通常需要先根据原始参数值生成待签名字符串,再把签名结果作为请求参数进行 URL 编码。除非具体接口文档明确要求,否则不能直接对 URL 编码后的整段查询字符串签名。
响应验签不能想当然
支付宝同步响应、异步通知和部分特殊产品可能采用不同的验签内容。有些场景验证参数集合,有些场景验证响应中的特定原始内容。不要自行解析、重排或重新生成响应 JSON 后再验签,应按照接口文档和官方 SDK 的处理方式提取待验签内容。
“支付宝公钥”不是“应用公钥”
应用私钥用于生成签名,支付宝公钥用于验证支付宝返回的签名。应用公钥通常需要配置到支付宝开放平台,但不能用来验证支付宝响应。
如果使用支付宝公钥证书模式,还需要按证书模式处理,不能把证书文件简单地当作普通公钥字符串。
加密与签名不是一回事
RSA2 签名用于确认数据来源和完整性,不负责隐藏数据。只有当接口文档明确要求 encrypt_type、AES 密钥或特定加密流程时,才需要实现加密和解密。
RSA 签名代码不能代替 AES 解密,也不要为普通支付请求自行增加加密步骤。
比较可靠的实现方式是以目标接口文档为准,选择同一时期的支付宝官方 Java SDK 作为参考,逐项核对参数规范化、RSA2 签名、响应验签和可选的 AES 处理逻辑,最后使用官方工具和固定测试向量验证 Ruby 版本。
备注:内容仅供参考。
