微信支付公钥验签总失败?先用 openssl 手动验一遍三行签名串
文档:微信支付公钥验签(商户文档中心),更新时间 2024.11.20。
示例不可直接使用,需要把内容替换成真实请求信息。公钥模式的对接可以按下面几步处理。
1. 获取微信支付公钥
超级管理员或安全联系人登录商户平台,进入“账户中心 - API 安全”页面,点击申请公钥后可下载微信支付公钥。
(1)账户中心 -> API 安全
(2)微信支付公钥入口页面
(3)微信支付公钥详情页,点击下载公钥后,可看到公钥 id
2. 构造验签串
以某次 native 下单接口的应答为例,HTTP 头示例:
Content-Type: application/json; charset=utf-8
Content-Length: 52
Connection: keep-alive
Keep-Alive: timeout=8
Cache-Control: no-cache, must-revalidate
X-Content-Type-Options: nosniff
Request-ID: 08F5B8C2B506102C18FDDFEEA30620BE821E28EDC405-0
Content-Language: zh-CN
Wechatpay-Nonce: d824f2e086d3c1df967785d13fcd22ef
Wechatpay-Signature: mfI1CPqvBrgcXfgXMFjdNIhBf27ACE2YyeWsWV9ZI7T7RU0vHvbQpu9Z32ogzc+k8ZC5n3kz7h70eWKjgqNdKQF0eRp8mVKlmfzMLBVHbssB9jEZEDXThOX1XFqX7s7ymia1hoHQxQagPGzkdWxtlZPZ4ZPvr1RiqkgAu6Is8MZgXXrRoBKqjmSdrP1N7uxzJ/cjfSiis9FiLjuADoqmQ1P7p2N876YPAol7Rn0+GswwAwxldbdLrmVSjfytfSBJFqTMHn4itojgxSWWN1byuckQt8hSTEv/Lg97QoeGniYP17T80pJeQyL3b+295FPHSO2AtvCgyIbKMZ0BALilAA==
Wechatpay-Timestamp: 1722850421
Wechatpay-Serial: PUB_KEY_ID_0000000000000024101100397200000006
Wechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048
Body: {"code_url":"weixin://wxpay/bizpayurl?pr=JyC91EIz1"}
从应答或通知回调中获取:
- HTTP 头
Wechatpay-Timestamp中的应答时间戳 - HTTP 头
Wechatpay-Nonce中的应答随机串 - 应答报文主体(Response Body)。这里必须使用原始报文主体,不要让框架重新序列化 JSON、修改编码或改变字节内容。对报文主体的任何篡改都会导致验证失败。
按以下规则构造应答验签名串。签名串共有三行,行尾以 \n 结束,包括最后一行。\n 为换行符,ASCII 编码值为 0x0A。若应答报文主体为空,比如 HTTP 状态码为 204 No Content,最后一行仅为一个 \n 换行符。
应答时间戳\n
应答随机串\n
应答报文主体\n
则验签名串为:
1722850421\n
d824f2e086d3c1df967785d13fcd22ef\n
{"code_url":"weixin://wxpay/bizpayurl?pr=JyC91EIz1"}\n
3. 获取应答签名
微信支付的应答签名通过 HTTP 头 Wechatpay-Signature 传递。示例中可能存在换行,实际数据应在一行。
某些代理服务器或 CDN 服务提供商在转发时,可能会“过滤”微信支付扩展的 HTTP 头,导致应用层无法获取签名信息。遇到这种情况,建议调整代理服务器配置,或者通过直连方式访问微信支付服务器和接收通知回调。实际排查时,可以先把收到的头原样打日志,确认 Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Signature、Wechatpay-Serial 都在。
4. 验证签名
很多编程语言都有签名验证函数,支持对验签名串和签名进行验证。建议调用这类函数,使用微信支付公钥对验签名串和签名做 SHA256 with RSA 签名验证。
下面用命令行演示。假设已经获取了微信支付公钥并保存为 1900009191_wxp_pub.pem。
使用 base64 解码应答签名,保存为 signature.txt:
openssl base64 -d <<< 'mfI1CPqvBrgcXfgXMFjdNIhBf27ACE2YyeWsWV9ZI7T7RU0vHvbQpu9Z32ogzc+k8ZC5n3kz7h70eWKjgqNdKQF0eRp8mVKlmfzMLBVHbssB9jEZEDXThOX1XFqX7s7ymia1hoHQxQagPGzkdWxtlZPZ4ZPvr1RiqkgAu6Is8MZgXXrRoBKqjmSdrP1N7uxzJ/cjfSiis9FiLjuADoqmQ1P7p2N876YPAol7Rn0+GswwAwxldbdLrmVSjfytfSBJFqTMHn4itojgxSWWN1byuckQt8hSTEv/Lg97QoeGniYP17T80pJeQyL3b+295FPHSO2AtvCgyIbKMZ0BALilAA==' > signature.txt
验证签名:
openssl dgst -sha256 -verify 1900009191_wxp_pub.pem -signature signature.txt << EOF
1722850421
d824f2e086d3c1df967785d13fcd22ef
{"code_url":"weixin://wxpay/bizpayurl?pr=JyC91EIz1"}
EOF
得到 Verified OK 表示验签通过。如果结果是 Verification Failure,确认是否获取到了正确的微信支付公钥,以及验签串是否严格按照文档格式换行。
5. 注意事项
合理应对签名探测流量
为了确保商户系统安全,微信支付会在极少数应答或通知回调中生成错误签名,以探测商户系统是否正确地验证了签名。
商户系统不应对探测流量进行特殊处理,而应将其视为正常的应答或通知回调,并对其签名进行验证。排查问题时,可以通过查看签名值中的 WECHATPAY/SIGNTEST/ 前缀快速判断是否为探测流量。所有用于探测目的的签名值都会包含此前缀。
验签失败时,建议按下面方式处理:
- 如果应答的签名验证失败,商户系统应舍弃该应答。为了提高用户体验,可以适当重试,或者让用户重新发起请求。微信支付不会针对重试请求发起探测。
- 如果通知回调的签名验证失败,商户系统应返回失败,即应答 4xx 或 5xx 状态码,等待微信支付携带正确签名重新发送通知回调。
文件/图片下载接口跳过验签
文件/图片下载接口需要跳过验签流程。以下载账单接口为例,下载账单分三步:
第一步,请求下载账单接口,微信支付返回一个下载账单的 URL。
第二步,商户通过第一步获取的 URL 去下载账单。下载账单返回的是账单文件,响应信息请求头中不包含微信接口响应的签名值,因此需要跳过验签流程。
第三步,商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性。