支付宝当面扫支付报错 isv.invalid-signature 如何排查?
结论
isv.invalid-signature 表示支付宝无法使用当前应用的公钥验证请求签名。
在线密钥匹配成功,只能说明私钥与某个公钥配对,不能说明以下配置都没有问题:
- 请求中的
app_id是否与密钥所属应用一致 - 使用的是否为应用私钥,而非支付宝公钥或支付宝私钥
- 支付宝开放平台保存的应用公钥是否与本地私钥匹配
sign_type、字符集和签名算法是否一致- 待签名字符串在签名后是否发生变化
- 是否混用了普通公钥模式和公钥证书模式
所以,这类问题通常与支付业务参数无关,更可能出在应用身份、密钥配置或签名过程上。
建议按以下顺序排查
1. 核对 app_id 和运行环境
先记录实际发送到支付宝网关的参数,不要只看配置文件:
app_id
method
sign_type
charset
timestamp
version
sign
逐项确认:
app_id是当前开放平台应用的 AppID- 私钥属于这个应用
- 应用后台配置的公钥与该私钥配对
- 没有混用沙箱应用、正式应用及其密钥
- 请求地址与所用环境一致
常见网关为:
正式环境:https://openapi.alipay.com/gateway.do
沙箱环境:https://openapi-sandbox.dl.alipaydev.com/gateway.do
即使两组密钥分别都能通过在线匹配,只要请求使用的是 A 应用的 app_id 和 B 应用的私钥,支付宝仍会返回 isv.invalid-signature。
2. 确认公私钥的用途
在普通公钥模式下:
- 商户系统保存并使用“应用私钥”生成请求签名
- 支付宝开放平台配置“应用公钥”
- 商户系统使用“支付宝公钥”验证支付宝返回结果或异步通知
这三者的用途不要混淆:
应用私钥:商户请求支付宝时签名
应用公钥:上传到支付宝开放平台
支付宝公钥:验证支付宝响应和通知
在线工具应当匹配“本地应用私钥”和“开放平台已配置的应用公钥”。如果本地私钥能与另一份本地公钥匹配,但上传到开放平台的还是旧公钥,线上请求依然会验签失败。
修改应用公钥后,还要检查服务器上的私钥是否已同步更新,以及旧进程、配置缓存和容器环境变量是否还在使用旧值。
3. 核对 sign_type
目前常见的配置是 RSA2,对应 SHA256WithRSA:
sign_type=RSA2
如果请求参数声明使用 RSA2,代码实际却按 SHA1WithRSA 签名,验签就会失败。
对应关系如下:
sign_type | Java 算法名 |
|---|---|
RSA2 | SHA256withRSA |
RSA | SHA1withRSA |
除非现有应用明确配置为旧的 RSA 模式,否则通常应使用 RSA2。不能只修改请求中的 sign_type,签名实现和开放平台配置也要保持一致。
4. 检查私钥格式
不同语言、不同版本的 SDK 对私钥格式的要求可能不同。常见问题有:
- 把公钥填入私钥配置项
- 复制私钥时缺行、缺字符,或者带入不可见字符
- 环境变量中的换行变成字面量
\n - PEM 头尾重复,或者格式不受当前 SDK 支持
- 使用了加密私钥,但程序没有提供解密口令
- PKCS#1 或 PKCS#8 格式不符合当前 SDK 的要求
PEM 私钥通常类似:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
如果私钥保存在环境变量中,要确认程序读取后包含的是真实换行。例如,Java 可以根据实际存储方式处理:
String privateKey = System.getenv("ALIPAY_APP_PRIVATE_KEY");
if (privateKey != null) {
privateKey = privateKey.replace("\\n", "\n").trim();
}
是否保留 PEM 头尾,要看所用 SDK 及其版本,不能一概而论。传入密钥时,应遵循对应 SDK 构造方法的要求。
5. 检查待签名字符串
如果签名没有完全交给官方 SDK 处理,需要逐项确认以下规则:
- 排除
sign参数 - 排除值为空的参数
- 按参数名的 ASCII 顺序排序
- 按
key=value的形式拼接 - 参数之间使用
&连接 - 使用声明的字符集生成字节
- 完成签名后,再对
sign进行 URL 编码
示意代码:
Map<String, String> params = new TreeMap<>();
params.put("app_id", appId);
params.put("method", "alipay.trade.precreate");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", "2026-09-08 10:30:00");
params.put("version", "1.0");
params.put("biz_content", bizContent);
String contentToSign = params.entrySet().stream()
.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(java.util.stream.Collectors.joining("&"));
签名生成后,以下任一变化都会让签名失效:
- 修改
biz_content - 改变 JSON 中的空格、转义或字段顺序
- 重复对参数进行 URL 编码
- 签名时使用原始值,发送时却使用另一份编码后的值
- 网关、中间件或 HTTP 客户端重新组装参数
- 签名时使用 UTF-8,发送时声明其他字符集
排查时应同时记录:
最终待签名字符串
签名结果
最终实际发送的请求参数
日志中的私钥必须脱敏,最好完全禁止输出。
6. 优先使用官方 SDK
以 Java 普通公钥模式为例,配置结构通常如下:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradePrecreateRequest request =
new AlipayTradePrecreateRequest();
request.setBizContent("""
{
"out_trade_no": "ORDER_202609080001",
"total_amount": "0.01",
"subject": "测试订单"
}
""");
AlipayTradePrecreateResponse response =
alipayClient.execute(request);
System.out.println(response.getCode());
System.out.println(response.getSubCode());
System.out.println(response.getSubMsg());
需要注意:
- 第三个密钥参数是应用私钥
alipayPublicKey是支付宝公钥,不是应用公钥appId、应用私钥和开放平台中的应用公钥必须属于同一个应用signType要与应用配置一致
这段示例只用于说明配置关系。具体构造方法应以项目实际使用的支付宝 SDK 版本为准。
证书模式需要单独检查
如果应用使用公钥证书模式,就不能继续按普通公钥模式配置。通常需要正确加载:
应用公钥证书
支付宝公钥证书
支付宝根证书
同时还要确认,SDK 根据请求计算出的证书序列号与当前证书一致。常见错误有:
- 更新证书后,服务器仍在加载旧文件
- 把普通“支付宝公钥”填入证书模式配置
- 多台服务器使用的证书版本不一致
- 容器挂载了错误的证书目录
普通公钥模式和证书模式只能选择一种,并使用与之对应的 SDK 初始化方式。
快速定位办法
可以创建一个最小化测试程序,只保留:
- 固定的正式或沙箱网关
- 明确的
app_id - 当前应用私钥
- 对应的支付宝公钥或证书
- 一个金额为
0.01的测试请求 - 官方 SDK 自动签名
如果最小程序能够成功调用,而业务系统仍然失败,问题通常出在以下位置:
- 配置中心读取了旧密钥
- 请求参数在签名后被修改
- HTTP 客户端进行了二次编码
- 多实例部署中有部分节点没有更新配置
- 第三方支付封装错误处理了
sign_type或字符集
如果最小程序仍然失败,就需要重新到开放平台核对 AppID、公钥配置、密钥模式和运行环境是否一一对应。
最常见的根因
实际排查时,可以优先检查以下几项:
app_id和应用私钥不属于同一个应用。- 开放平台保存的还是旧应用公钥。
- 请求声明使用
RSA2,代码实际却按RSA签名。 - 混用了沙箱和正式环境的 AppID、密钥或网关。
- 私钥通过环境变量传递后,换行格式被破坏。
- 自行拼接参数时,签名内容与最终发送内容不一致。
- 混用了普通公钥模式和公钥证书模式。
“密钥匹配成功”只能排除密钥对本身不匹配,无法排除上述配置和请求构造问题。排查时,最直接的办法是打印脱敏后的实际运行配置和完整待签名字符串,再与最终发出的参数逐项比较。
备注:内容仅供参考。
