支付宝 demo 报错 invalid-signature,如何排查验签问题?
结论
invalid-signature 表示支付宝无法使用后台配置的应用公钥验证当前请求的签名。排查时,需要确认以下配置来自同一套密钥和应用环境:
- 程序中的
app_private_key - 支付宝开放平台对应应用所配置的应用公钥
- 当前请求使用的
app_id和运行环境
还要分清以下三类密钥的用途:
- 应用私钥:应用向支付宝发送请求时,用它生成签名。
- 应用公钥:配置在支付宝开放平台,支付宝用它验证应用的请求签名。
- 支付宝公钥:程序用它验证支付宝的响应或异步通知。
因此,程序同时使用应用私钥和兑换后获得的支付宝公钥通常没有问题,但后台保存的应用公钥必须与应用私钥成对,并且配置在当前 app_id 和当前运行环境中。
推荐的排查顺序
1. 确认应用私钥和后台应用公钥成对
这是最常见的原因。重新生成过密钥、上传了其他公钥,或者误用了另一应用的私钥,都会导致验签失败。
可以使用 OpenSSL 从私钥导出公钥,然后与支付宝开放平台中配置的应用公钥比较。
如果私钥是 PKCS#8 格式:
openssl pkey -in app_private_key.pem -pubout -out derived_public_key.pem
如果密钥文件没有 PEM 头尾,需要先整理成以下格式:
-----BEGIN PRIVATE KEY-----
私钥 Base64 内容
-----END PRIVATE KEY-----
导出的公钥应与开放平台中配置的应用公钥一致。比较时可以忽略以下内容:
-----BEGIN PUBLIC KEY----------END PUBLIC KEY------ 换行和空格
不要用支付宝公钥与应用私钥进行配对比较。支付宝公钥属于支付宝,不能从应用私钥中推导出来。
2. 检查 app_id 和运行环境
确认程序中的 app_id 对应的正是配置了该应用公钥的应用。
同时检查网关是否与环境匹配:
正式环境:https://openapi.alipay.com/gateway.do
沙箱环境:https://openapi-sandbox.dl.alipaydev.com/gateway.do
沙箱应用和正式应用的密钥配置相互独立。用正式应用的私钥请求沙箱网关,或者用沙箱应用的私钥请求正式网关,都可能导致验签失败。
还需排查以下情况:
- 使用了另一个应用的
app_id - 后台修改应用公钥后,程序仍在使用旧私钥
- 多套环境的配置文件加载错位
- 容器或服务器的环境变量覆盖了本地配置
3. 确认签名算法一致
目前常见的配置是:
sign_type=RSA2
RSA2 对应 SHA256WithRSA。如果程序实际使用的签名算法与配置不一致,也会导致验签失败。
Java SDK 初始化示例:
String gatewayUrl = "https://openapi.alipay.com/gateway.do";
String appId = "你的应用APPID";
String appPrivateKey = "应用私钥";
String alipayPublicKey = "支付宝公钥";
String charset = "UTF-8";
String signType = "RSA2";
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey,
"json",
charset,
alipayPublicKey,
signType
);
其中两个密钥的用途不同:
appPrivateKey 用于生成请求签名
alipayPublicKey 用于验证支付宝返回内容
支付宝验证请求签名时,使用的是开放平台中为该应用配置的应用公钥。
4. 检查私钥格式
不同语言、SDK 版本或密码库对私钥格式的要求可能不同。常见格式包括:
- PKCS#1:通常以
-----BEGIN RSA PRIVATE KEY-----开头 - PKCS#8:通常以
-----BEGIN PRIVATE KEY-----开头
支付宝密钥工具生成的私钥,应按照当前 SDK 配置项的要求填写。常见错误包括:
- SDK 需要纯 Base64 内容,却传入了 PEM 头尾
- SDK 需要完整 PEM,却只传入了中间内容
- 复制私钥时丢失了字符
- 配置文件将换行保存成了字面量
\n - 私钥前后含有引号、空格或不可见字符
- Java Properties、YAML 或环境变量对特殊字符进行了转义
程序启动时可以只记录密钥摘要,不要打印完整私钥。例如:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
String normalizedKey = appPrivateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] digest = MessageDigest.getInstance("SHA-256")
.digest(normalizedKey.getBytes(StandardCharsets.UTF_8));
System.out.println("appPrivateKey SHA-256: " + HexFormat.of().formatHex(digest));
通过摘要可以判断不同环境实际加载的私钥是否一致,同时避免泄露私钥。
5. 优先使用官方 SDK 生成签名
如果使用支付宝 SDK,不要在 SDK 完成签名后继续修改请求参数,也不要自行拼接或编码待签名字符串。
手动签名时常见的问题有:
- 参数排序错误
- 将
sign参数加入待签名字符串 - 漏掉参数或加入空值参数
- 在签名前进行 URL 编码
- 使用平台默认字符集,而不是 UTF-8
- JSON 内容在签名后被重新序列化
- 再次转义
+、/、=等 Base64 字符 - 签名后修改
biz_content sign_type填写为RSA2,实际却使用 SHA-1
签名对象必须与请求发送前的原始参数内容一致。URL 编码通常应在生成签名之后进行。
6. 不要混用普通公钥模式和证书模式
如果应用后台配置的是普通应用公钥,应使用公钥模式对应的 SDK 初始化方式。
如果应用启用了公钥证书模式,通常需要配置:
- 应用公钥证书
- 支付宝公钥证书
- 支付宝根证书
使用证书模式时,不能只截取证书中的某段公钥,再将其填入普通公钥参数。具体初始化接口和参数名称取决于所用 SDK 及其版本,应以该版本的 SDK 文档为准。
如何判断是请求签名还是响应验签出了问题
如果接口直接返回类似错误:
invalid-signature
验签出错
通常是支付宝验证应用请求时失败。此时应优先检查:
应用私钥 ↔ 后台应用公钥
app_id
网关环境
sign_type
待签名字符串
如果业务调用已经成功,但程序在处理支付宝返回结果或异步通知时报告验签失败,则应检查:
支付宝公钥
通知原始参数
charset
sign_type
参数是否在验签前被修改或解码
Java 中验证异步通知的典型写法如下:
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
params 应包含支付宝通知的完整原始参数。不要在验签前修改 trade_status、body、subject、fund_bill_list 等字段。
最稳妥的解决方法
如果无法确认现有密钥是否混用,可以重新建立完整的密钥链:
- 为当前应用重新生成一对应用公钥和应用私钥。
- 将新生成的应用公钥配置到当前
app_id的开放平台后台。 - 保存后台配置后,取得当前应用对应的支付宝公钥。
- 在程序中配置新的应用私钥和支付宝公钥。
- 确认
app_id、网关、RSA2和UTF-8均保持一致。 - 重启应用,确认环境变量或配置中心没有覆盖新配置。
- 使用官方 SDK 发起最小化请求,不再修改 SDK 生成的参数。
如果重新配置后仍然报错,可以记录以下信息进行对照,但不要记录完整私钥:
app_id
gateway
sign_type
charset
应用公钥指纹
应用私钥推导出的公钥指纹
支付宝公钥指纹
SDK 名称和版本
支付宝返回的 code、sub_code、msg、sub_msg
多数 invalid-signature 问题最终都与以下情况有关:应用私钥和后台应用公钥不匹配、沙箱与正式环境混用,或者参数在签名后又被修改。
备注:内容仅供参考。
