代码 支付宝证书模式报 40002:app_cert_sn 与根证书 SN 算错的排查路径

2026-10-24 09:02:19

支付宝证书模式报 40002:app_cert_sn 与根证书 SN 算错的排查路径

现象:待签名串没问题,40002 还在

支付宝接口报 40002。先按“对比待签名串”的思路查了一遍:待签名串对、私钥对、编码也对,但错误还在,只是 sub_code 变了:

{"code":"40002","msg":"Invalid Arguments","sub_code":"isv.missing-app-cert-sn","sub_msg":"缺少应用公钥证书序列号"}

或者:

{"code":"40002","msg":"Invalid Arguments","sub_code":"isv.invalid-alipay-root-cert-sn","sub_msg":"无效支付宝根证书序列号"}

这类错误跟签名内容无关,问题出在“证书模式的请求参数没带全或带错”。公钥证书模式要求除常规参数外再带两个值:app_cert_sn(应用公钥证书 SN)和 alipay_root_cert_sn(支付宝根证书 SN)。这两个值参与签名,不是可选的附加头。

两个 SN 分别对应什么

SN 既不是证书文件名,也不是 app_id。按支付宝技术支持的说明:SN 值是通过解析 X.509 证书文件中的签发机构名称(issuer name)以及内置序列号(serialNumber),把二者拼接后计算 MD5 得到的;拼接结果形如 687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6,前后两段各 32 位十六进制,中间一个下划线。

  • app_cert_sn ← 应用公钥证书(appCertPublicKey_xxx.crt)
  • alipay_root_cert_sn ← 支付宝根证书(alipayRootCert.crt)

支付宝根证书 SN 有一个可对照的固定值:687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6,官方说 2028 年之前不会变。算出来的值如果不是这个,先别怀疑算法,先确认读的是不是支付宝根证书。

用 openssl 自行计算

按文档算法用 openssl 复现,便于和 SDK 打印的值对比:

CERT=appCertPublicKey_2021000000000000.crt
NAME=$(openssl x509 -in "$CERT" -noout -issuer | sed 's/^issuer=//' | tr -d '\n')
SERIAL=$(openssl x509 -in "$CERT" -noout -serial | cut -d= -f2 | tr -d '\n')
printf '%s' "$NAME$SERIAL" | md5sum | cut -d' ' -f1

以上为按文档算法自行实现,务必与 SDK 输出比对。

拿输出的 32 位十六进制,与 SDK/日志里打印的 app_cert_sn 的下划线后半段对比。一致说明证书文件选对了;不一致,八成是读错了证书(把支付宝公钥证书当成应用公钥证书),或者拼接时多了换行、空格。

这一步值得写成一次性脚本放进部署检查,比在业务代码里猜快得多。

三种 sub_code 各自指向什么

sub_code含义常见原因
isv.missing-app-cert-sn缺少应用公钥证书序列号请求里没带 app_cert_sn:SDK 用错提交方法,或自行实现签名时漏拼
isv.invalid-alipay-root-cert-sn无效支付宝根证书序列号alipay_root_cert_path 路径错/非绝对路径、读错文件、sign_type 不是 RSA2、timestamp 被转义、PHP SDK 版本低
isv.invalid-signature签名不匹配待签名串与请求参数不一致(先查这里)

SDK 的提交方法会静默决定有没有 SN

证书模式下提交方法不是 execute:

  • Java:execute() → certificateExecute()
  • .NET:execute() → CertificateExecute()
  • PHP:仍然用 execute()

Java 用 execute() 调证书模式,SDK 不会带 app_cert_sn,服务端直接返回 isv.missing-app-cert-sn。这个错不会在本地报,只有在请求真正发出去才出现,所以“本地测试用 Mock”的流程里完全看不到。

更麻烦的一种:新创建应用的资金类接口(如 alipay.fund.trans.uni.transfer 单笔转账、现金红包)强制校验证书签名。你用普通公钥方式签名,报的也是 isv.missing-app-cert-sn——报错文字在说“缺 SN”,实际是“你用错了签名模式”。这时改参数没用,得整条链路切证书模式:应用私钥不变,但验签要改成读支付宝公钥证书。

两个容易被忽略的转义坑

1. timestamp 被转义成 ×tamp

&timestamp= 在 HTML 环境里被解析成 ×tamp(× 是乘号实体)。PHP 把待签串或 URL 直接输出到页面时最常见。表现就是根证书 SN 报无效,因为服务端解析到的参数根本不是 timestamp。要看日志里的原始串,别在浏览器里看。

2. PHP SDK 版本导致的 SN 解析错误

PHP 服务端 SDK 4.2.0 以下存在 openssl 兼容问题:解析出的 SN 后半段可能出现 0X 开头,例如 ...c5d8_0X941eef3187dddf3d3b83462e1d5416,大小写与格式都不对。升到 4.2.0 以上即可;升不了的话,短期可以在代码里把已知正确的 SN 常量写死兜底——但这是补丁,不是修复,根证书轮换时会再炸一次。

回调验签用的是哪张证书

证书模式下异步通知的验签,读的是“支付宝公钥证书”(alipayCertPublicKey_RSA2.crt),不是应用公钥证书,也不是 alipayRootCert.crt。三个文件名字很像,用途完全不同:

  • appCertPublicKey_*.crt:你的应用公钥,服务端用来校验你的签名 → 算出 app_cert_sn
  • alipayCertPublicKey_RSA2.crt:支付宝公钥,你用来验支付宝的签名
  • alipayRootCert.crt:根证书,用于证书链校验 → 算出 alipay_root_cert_sn

把 appCertPublicKey 传进验签函数,结果只会是“验签一直 false”,然后你去查订单、查金额,都是对的,时间全浪费在错的方向。

取舍:证书模式还是公钥模式

  • 公钥模式:两个密钥字符串,配置简单,但支付宝公钥更新时要人工换(换的时候没感知就会验签失败)。
  • 证书模式:三个证书文件 + 两个 SN,路径、文件名、SN 任何一处错都报 40002;换来的是证书链校验和可轮换:支付宝公钥证书可以动态下载,不用等人工替换。

新应用的资金类接口基本没有选择权,只能证书模式。存量项目如果只是普通交易接口、且运维能盯住公钥更新,继续用公钥模式并不丢人;但如果你的失败模式是“半夜验签突然全挂且没人知道为什么”,证书模式的自动轮换更省事。

上线前自查清单:

  • app_cert_sn 与脚本算出的值一致
  • alipay_root_cert_sn 是否为固定值(2028 年前)
  • sign_type=RSA2
  • Java/.NET 提交方法是否 certificateExecute
  • 回调验签读的是 alipayCertPublicKey_RSA2.crt
  • 日志里打印的原始待签串没有 ×tamp

来源与边界

SN 算法、固定根证书 SN、SDK 版本问题、提交方法对应关系均摘自支付宝技术支持文档:

openssl 计算片段按文档描述的算法自行拼装,未在真实应用上逐条复现;你的 sub_code 与 sub_msg 以实际响应为准。适用于支付宝公钥证书模式(RSA2),不涉及国际版 Alipay Global 与间连模式。

推荐文章

程序员茄子在线接单