支付宝 OpenAPI 40002 isv.invalid-signature:先别换密钥,先对比待签名串
报错分类:先看 sub_code 和异常类型
服务端返回:
{"code":"40002","msg":"Invalid Arguments","sub_code":"isv.invalid-signature","sub_msg":"无效签名"}
同一个 sub_code 下,还可能遇到这几种 sub_msg:
- 「验签出错,请确认charset参数放在了URL查询字符串中且各参数值使用charset参数指示的字符集编码」
- 「验签出错,建议检查签名字符串或签名私钥与应用公钥是否匹配,网关生成的验签字符串为:...」
配置类错误是另一个码:
{"code":"40003","msg":"Insufficient Conditions","sub_code":"isv.missing-signature-config","sub_msg":"验签出错, 未配置对应签名算法的公钥或者证书"}
证书模式还有一组:sub_code 为 isv.missing-app-cert-sn / isv.missing-alipay-root-cert-sn / isv.invalid-alipay-root-cert-sn / isv.app-cert-expired。
客户端侧异常:
- java.security.SignatureException: Signature length not correct: got 256 but was expecting 128(或反过来)
- com.alipay.api.AlipayApiException: RSA 私钥格式不正确,请检查是否正确配置了 PKCS8 格式的私钥。
missing-signature-config / missing-*-cert-sn 这类报错属于配置没配,不用改代码;invalid-signature 才是待签名串的问题。Signature length not correct 和 PKCS8 异常先在客户端查密钥格式与签名算法。
待签名串怎么拼
按官方「请求参数签名」的规则,分为四步:
- 取所有请求参数,不含字节类型参数(文件、字节流),剔除 sign;剔除值为空的参数。
- 按参数名第一个字符 ASCII 升序排序,相同再比第二个字符,以此类推。
- 拼成 参数=参数值,用 & 连接。
- 用 SHA256WithRSA(RSA2)对整串签名,Base64 编码得到 sign。
示例待签名串:
app_id=2014072300007148&biz_content={"button":[...]}&charset=GBK&method=alipay.mobile.public.menu.add&sign_type=RSA2×tamp=2014-07-24 03:07:50&version=1.0
biz_content 是一个整体字符串,里面的 JSON 不参与排序,也不做 urlencode。sign_type=RSA2 本身参与请求签名。
与异步通知验签的差别在这里:异步通知验签要剔除 sign、sign_type 两个参数,参数值先 URLDecode 再排序;生活号通知保留 sign_type。请求签名是剔除 sign、保留 sign_type。两套规则不一样,复制代码最容易在这里出错。
高发错因(按排查成本排序)
A. 网关地址尾部带了 ?
官方把「支付宝网关后携带 ?」列为 SDK 报错原因。gateway 固定写 https://openapi.alipay.com/gateway.do,不要把参数拼到 gateway 后面。
B. charset 不一致
请求参数写 charset=utf-8,签名时却按 GBK 编码(或相反),中文 subject 会乱码成「鎮ㄥソ」,进而签名串对不上。定位办法:把 subject 改成纯数字 123 再测一次,通了就说明是编码问题。
C. URLEncode 时机错
正确顺序:先按原始值排序拼串 → 签名 → 再对各参数值(含 sign)做 urlencode 后发出。反过来先 encode 再签,签名串与网关侧不一致。自签名时必须显式对 sign 做 encode。
D. 私钥格式
Java 用 PKCS8,官方文档写其它语言用 PKCS1(PHP/Go 实际也能用 PKCS8,未实测全部语言);私钥必须一行、去掉换行。转换命令:
openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out pkcs8.pem
以及 openssl rsa -in k.pem -pubout 验证私钥与公钥是否配对。
E. sign_type 与密钥长度不匹配
1024 位私钥配 sign_type=RSA,2048 位配 RSA2;错配会报「sign值与sign_type参数指定的签名类型不一致」。2018-01-05 之后创建的应用只支持 RSA2。验签时出现 got 256 but was expecting 128,通常是拿 RSA2 的公钥去验 RSA 的签名。
F. 公钥用错
开放平台后台要上传「应用公钥」,代码里签名用「应用私钥」,验签支付宝返回用「支付宝公钥」。把应用公钥当支付宝公钥去验签,报 sign check fail: check Sign and Data Fail。三者任意两个串用了都过不去。
G. 证书模式漏参数
公钥证书模式下,待签名串里必须多带 app_cert_sn 与 alipay_root_cert_sn。这两个 SN 不是 X.509 里的 serialNumber,而是把证书签发机构名称(issuer name)与内置序列号(serialNumber)拼起来做 MD5。PHP SDK 实际实现是 md5(array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber'])。不要自己手写,直接调 SDK 的 AlipaySignature.getCertSN()。Java 证书模式的提交方法要从 execute() 换成 certificateExecute(),PHP 仍是 execute()。资金类接口(单笔转账、现金红包)强制走证书模式。
openssl 本地自证:不开商户后台
把代码里实际参与签名的字符串原样写到 /tmp/to_sign.txt(不要多一个换行),把 sign base64 解码成二进制,用应用公钥验一遍:
echo -n "$(cat /tmp/to_sign.txt)" > /tmp/plain.txt
printf '%s' "$SIGN" | base64 -d > /tmp/sig.bin
openssl dgst -sha256 -verify app_public_key.pem -signature /tmp/sig.bin /tmp/plain.txt
返回 Verified OK,说明「你的私钥 + 你的串 + 你的公钥」三者自洽,问题一定在串的内容(charset、多空格、少参数、gateway 带 ?);返回 Verification Failure,则是私钥/公钥不配对或 sign 传参时被二次编码。
再拿报错报文里「网关生成的验签字符串」与你打印的串做逐字符对比。官方示例里网关串的 biz_content 值前甚至带了一个空格,说明网关完全按你发送的内容拼接,任何多余空格都会被算进去。
排查顺序
- 看 sub_code 先分类:missing-signature-config / missing-*-cert-sn 属于配置没配,不用改代码;invalid-signature 才是串的问题。
- 只用必填参数 + 纯数字 subject 发一次请求,排除编码与业务字段。
- 打印待签名串、sign、完整请求 URL 三样,和网关串对比。
- 最后才怀疑密钥不匹配(openssl 验一遍公私钥对)。
边界 / 未实测
- 沙箱网关是 https://openapi.alipaydev.com/gateway.do,app_id、私钥、支付宝公钥必须与沙箱配套;生产与沙箱混用报的也是 invalid-signature。
- mapi 老网关(https://mapi.alipay.com/gateway.do)的密钥在「mapi 网关产品密钥」处配置,和 openapi 不是一处。
- 官方自助排查页:https://opendoc.alipay.com/common/02kdnf,签名原理页:https://opendoc.alipay.com/common/02khjm。
- 文中 PHP/Go 使用 PKCS8 私钥未逐一实测,以 SDK 文档为准。