APP 支付调用 alipay.trade.app.pay 提示 invalid-signature
结论
invalid-signature(结果码 40002)表示支付宝网关无法使用当前 app_id 对应的应用公钥验证请求签名。
即使本地验证结果显示私钥与某个公钥匹配,也只能证明这对密钥本身有效,不能说明支付宝开放平台实际配置的公钥、应用环境和签名方式都正确。
建议先检查以下内容:
- 签名使用的是商户应用私钥,不是支付宝公钥或支付宝私钥。
- 开放平台配置的应用公钥与当前私钥配套。
app_id、网关地址和密钥配置属于同一环境。sign_type与实际签名算法一致,通常应为RSA2。- 签名原文与最终发送的内容完全一致。
- 先完成签名,再进行 URL 编码,不能对编码后的字符串签名。
- 没有混用公钥模式和证书模式。
优先使用官方 SDK 生成订单字符串
APP 支付不建议手工拼接和签名参数。服务端可以通过支付宝 SDK 调用 sdkExecute,取得完整订单字符串后原样返回给 APP。
Java 示例:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount("0.01");
model.setSubject("订单支付");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
// 将该字符串原样返回给 APP,不要再次排序、签名或整体 URL 编码
String orderString = response.getBody();
这里需要注意:
merchantPrivateKey是应用私钥。alipayPublicKey是支付宝公钥,主要用于验证支付宝返回的内容,不用于生成商户请求签名。response.getBody()应原样交给支付宝 APP SDK。- 使用沙箱环境时,需要改为对应的沙箱网关,并使用沙箱应用的
app_id和密钥。
检查开放平台中的应用公钥
本地公私钥匹配,只能说明这一对密钥有效。还要确认支付宝开放平台当前应用中保存的应用公钥,确实是由这份私钥配套生成的公钥。
常见问题有:
- 把支付宝公钥填进了应用公钥配置。
- 修改或重新生成私钥后,没有同步更新应用公钥。
- 公钥已经更新,服务却仍在使用旧私钥。
- 公钥配置在另一个应用中,请求使用的却是不同的
app_id。 - 在生产环境中使用了沙箱应用密钥,或在沙箱环境中使用了生产密钥。
- 多台服务器加载了不同的私钥配置。
- 密钥轮换期间,部分实例仍在使用旧密钥。
核对时要比较密钥内容本身,不能只看文件名。不要把私钥写入日志,也不要将其提交到代码仓库。
检查 RSA2 与字符集
如果请求中声明:
sign_type=RSA2
实际签名算法必须是:
SHA256withRSA
如果使用 RSA,对应的通常是 SHA1withRSA。请求声明 RSA2,实际却使用 SHA1withRSA,验签一定会失败。
签名时的字节编码也必须与请求中的 charset 一致。例如:
charset=utf-8
签名原文就应使用 UTF-8 转换为字节:
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
String sign = Base64.getEncoder().encodeToString(signed);
实际项目中,仍建议交给支付宝 SDK 处理私钥解析、参数排序、签名和编码。
检查签名原文
手工签名时,签名原文通常由顶层公共请求参数组成。排除 sign 和值为空的参数后,按照参数名的 ASCII 顺序排序,再以 key=value 的形式用 & 连接。
例如:
app_id=202100xxxxxxxxxx
biz_content={"out_trade_no":"ORDER_001","product_code":"QUICK_MSECURITY_PAY","subject":"订单支付","total_amount":"0.01"}
charset=utf-8
method=alipay.trade.app.pay
notify_url=https://example.com/alipay/notify
sign_type=RSA2
timestamp=2026-09-09 12:00:00
version=1.0
排序并拼接后,形式如下:
app_id=202100xxxxxxxxxx&biz_content={"out_trade_no":"ORDER_001","product_code":"QUICK_MSECURITY_PAY","subject":"订单支付","total_amount":"0.01"}&charset=utf-8&method=alipay.trade.app.pay¬ify_url=https://example.com/alipay/notify&sign_type=RSA2×tamp=2026-09-09 12:00:00&version=1.0
需要检查这些细节:
- 不要把
sign放进签名原文。 - 不要对已经完成 URL 编码的参数值签名。
- 签名后不要重新序列化或修改
biz_content。 - JSON 中的空格、转义字符、字段顺序或小数格式发生变化,都会改变签名原文。
notify_url是否包含查询参数或末尾斜杠,也会影响签名。- 签名值应先进行 Base64 编码,再按照 APP 支付订单字符串的要求进行 URL 编码。
- 不要对完整订单字符串重复进行 URL 编码。
例如,参与签名的内容是:
subject=测试订单
发送时可以编码为:
subject=%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95
但不能先对 测试订单 进行 URL 编码,再用编码后的结果参与签名。
检查私钥格式
不同语言和加密库接受的私钥格式可能不同,常见格式包括 PKCS#1 和 PKCS#8。Java SDK 通常要求传入去掉头尾标记和换行后的私钥正文,具体应以当前 SDK 构造参数的要求为准。
配置系统也可能破坏私钥内容,例如:
- 换行被保存成字面量
\n。 - 私钥前后出现多余的空格或引号。
- 环境变量长度被截断。
- Base64 正文被错误转义。
- 配置文件包含 BOM。
- 公钥内容被误填到私钥配置项中。
如果 SDK 能成功加载私钥,但支付宝网关仍然验签失败,应优先核对开放平台中的应用公钥和实际发送的签名原文。
公钥模式与证书模式不要混用
使用普通公钥模式时,需要配置:
- 应用私钥;
- 开放平台中的应用公钥;
- 支付宝公钥。
使用证书模式时,则要采用相应的证书初始化方式,并正确配置:
- 应用公钥证书;
- 支付宝公钥证书;
- 支付宝根证书;
- 对应的证书序列号。
不能在开放平台按证书模式配置应用,却使用普通公钥模式的客户端初始化方式。不同语言和版本的 SDK,初始化类与参数名称可能不同,应以当前使用的官方 SDK 接口为准。
推荐排查顺序
- 确认请求中的
app_id。 - 确认请求发送到生产网关还是沙箱网关。
- 查看运行实例实际加载的私钥。可以记录公钥指纹,但不能记录私钥。
- 从该私钥推导出公钥,并与开放平台中当前应用的公钥逐字节比较。
- 确认请求声明
sign_type=RSA2,实际使用的算法是SHA256withRSA。 - 确认
charset=utf-8与签名时使用的字节编码一致。 - 记录脱敏后的签名原文和最终订单字符串,检查参数是否在签名后又被修改。
- 检查 APP、代理层或网关是否进行了二次 URL 编码。
- 暂时改用官方 SDK 的
sdkExecute生成订单字符串,以排除手工签名问题。 - 如果使用集群,逐台检查密钥配置,确认没有部分实例仍在使用其他密钥。
出现这个错误时,最常见的原因不是公私钥在数学上不匹配,而是支付宝开放平台配置的应用公钥并非当前私钥对应的公钥,或者签名完成后,编码、JSON 序列化或参数修改导致实际发送的字符串与签名原文不一致。
备注:内容仅供参考。
