PAYATHON 2026

支付宝 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 等字段。

最稳妥的解决方法

如果无法确认现有密钥是否混用,可以重新建立完整的密钥链:

  1. 为当前应用重新生成一对应用公钥和应用私钥。
  2. 将新生成的应用公钥配置到当前 app_id 的开放平台后台。
  3. 保存后台配置后,取得当前应用对应的支付宝公钥。
  4. 在程序中配置新的应用私钥和支付宝公钥。
  5. 确认 app_id、网关、RSA2 和 UTF-8 均保持一致。
  6. 重启应用,确认环境变量或配置中心没有覆盖新配置。
  7. 使用官方 SDK 发起最小化请求,不再修改 SDK 生成的参数。

如果重新配置后仍然报错,可以记录以下信息进行对照,但不要记录完整私钥:

app_id
gateway
sign_type
charset
应用公钥指纹
应用私钥推导出的公钥指纹
支付宝公钥指纹
SDK 名称和版本
支付宝返回的 code、sub_code、msg、sub_msg

多数 invalid-signature 问题最终都与以下情况有关:应用私钥和后台应用公钥不匹配、沙箱与正式环境混用,或者参数在签名后又被修改。

备注:内容仅供参考。