身份认证初始化出现 isv.invalid-signature 验签错误
结论
isv.invalid-signature 表示支付宝网关根据本次身份认证初始化请求重新计算签名后,得到的验签结果与请求中的 sign 不一致。
支付、退款请求能够正常通过,不代表身份认证初始化请求的签名也正确。每次请求都要根据自身参数单独生成签名,不能跨接口复用。排查时,应重点核对身份认证请求的参数集合、biz_content 原文、字符编码、签名算法,以及应用与密钥是否匹配。
常见原因
1. 复用了其他请求的签名
sign 与当前请求内容一一对应。只要 method、timestamp、biz_content 等任意参数发生变化,就必须重新生成签名。
身份认证初始化接口应使用:
alipay.user.certify.open.initialize
支付或退款请求生成的 sign 不能直接用于该接口。
2. 生成签名后又修改了参数
参与签名的参数必须与最终发送到网关的参数完全一致。常见问题有:
- 签名后重新序列化了
biz_content - JSON 字段顺序、空格或转义字符发生变化
- 某个公共参数没有参与签名,发送时却带上了
- 签名时保留了空值,发送时又删除了该参数
- URL 编码前后使用了不同的字符串
- 表单组件或 HTTP 客户端再次修改了参数
正确的顺序是先用原始参数生成签名,再对需要传输的值进行 URL 编码。不要将 URL 编码后的内容用于签名。
3. biz_content 被重复编码或转义
身份认证初始化请求通常包含 identity_param、merchant_config 等嵌套 JSON。如果接口要求这些字段以 JSON 字符串形式传递,就要按要求正确转义。
以下内容仅用于展示结构,具体业务字段以当前接入文档为准:
{
"outer_order_no": "CERT_202609090001",
"biz_code": "FACE",
"identity_param": "{\"identity_type\":\"CERT_INFO\",\"cert_type\":\"IDENTITY_CARD\",\"cert_name\":\"张三\",\"cert_no\":\"证件号码\"}",
"merchant_config": "{\"return_url\":\"https://example.com/cert/result\"}"
}
生成签名后,不要再次使用 JSON 格式化工具处理这些内容,也不要提前对整个 biz_content 进行 URL 编码。
4. 应用、私钥和公钥不匹配
需要逐项确认:
- 请求中的
app_id是否属于当前应用 - 签名时使用的应用私钥是否与该应用对应
- 开放平台配置的应用公钥是否由这把私钥配对生成
- 是否混用了沙箱和生产环境的
app_id、网关地址或密钥 - 是否误用支付宝公钥对请求进行签名
- 更换密钥后,程序是否还在读取旧配置
应用私钥用于给商户请求签名,支付宝公钥用于验证支付宝响应签名,二者不能互换。
如果支付和退款请求来自另一个应用、另一套配置或另一台服务实例,也不能据此排除密钥不匹配的问题。
5. sign_type 或字符编码不一致
如果请求中声明:
sign_type=RSA2
charset=utf-8
程序必须使用 RSA2,也就是 SHA256withRSA,并将请求内容转换为 UTF-8 字节后参与签名。不能在请求中声明 RSA2,实际却使用 SHA1withRSA。
私钥格式也需要检查。部分语言或加密库要求使用 PKCS#8 私钥,直接传入 PKCS#1 私钥可能报错,也可能产生非预期结果。
推荐解决方法
建议优先使用支付宝官方 SDK,由 SDK 完成公共参数组装、待签名字符串生成、签名和请求发送,减少手工拼接造成的差异。
Java 示例:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayUserCertifyOpenInitializeRequest request =
new AlipayUserCertifyOpenInitializeRequest();
request.setBizContent(
"{"
+ "\"outer_order_no\":\"CERT_202609090001\","
+ "\"biz_code\":\"FACE\","
+ "\"identity_param\":"
+ "\"{\\\"identity_type\\\":\\\"CERT_INFO\\\","
+ "\\\"cert_type\\\":\\\"IDENTITY_CARD\\\","
+ "\\\"cert_name\\\":\\\"张三\\\","
+ "\\\"cert_no\\\":\\\"证件号码\\\"}\","
+ "\"merchant_config\":"
+ "\"{\\\"return_url\\\":\\\"https://example.com/cert/result\\\"}\""
+ "}"
);
AlipayUserCertifyOpenInitializeResponse response =
alipayClient.execute(request);
if (response.isSuccess()) {
String certifyId = response.getCertifyId();
} else {
System.out.println(response.getCode());
System.out.println(response.getSubCode());
System.out.println(response.getSubMsg());
}
如果应用使用支付宝公钥证书模式,应按证书模式初始化客户端并调用相应的 SDK 执行方法,不能将普通公钥模式和证书模式的配置混用。
手工签名时的核对方法
如果需要手工签名,应按以下流程处理:
- 收集所有需要参与签名的请求参数。
- 排除
sign参数和值为空的参数。 - 按参数名的 ASCII 顺序排序。
- 拼接为
key=value&key=value。 - 使用应用私钥和请求中声明的算法生成签名。
- 对生成的 Base64 签名值进行 URL 编码,然后发送请求。
示意代码:
Map<String, String> params = new TreeMap<>();
params.put("app_id", appId);
params.put("method", "alipay.user.certify.open.initialize");
params.put("format", "json");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", timestamp);
params.put("version", "1.0");
params.put("biz_content", bizContent);
// signContent 必须使用最终发送参数中的原始值拼接
String signContent = buildSignContent(params);
String sign = rsa256Sign(signContent, appPrivateKey, StandardCharsets.UTF_8);
params.put("sign", sign);
排查时还应记录以下内容,并做好脱敏处理:
app_id
method
charset
sign_type
timestamp
version
biz_content 的原始字符串
客户端生成的待签名字符串
最终发送的参数名列表
将客户端生成的待签名字符串与错误信息中网关生成的待验签字符串逐字符比较,通常能直接找到缺失参数、转义差异、编码变化或多余空格。
注意事项
- 不要记录应用私钥、完整证件号码或其他敏感身份信息。
- 不要混用格式化后的 JSON 和压缩后的 JSON。
- 签名完成后不要修改
biz_content。 - 不要先对参数值进行 URL 编码再签名。
- 应确认服务器实际加载的配置,不能只检查配置文件中的内容。
- 如果使用官方 SDK 后仍然报错,可以构造一笔全新的最小请求。确认应用、环境和密钥完全一致后,再将脱敏后的待签名字符串及请求参数提交给支付宝技术支持核对。
备注:内容仅供参考。
