如何验证签名

更新时间:2023.04.28

微信支付在 API 应答的 HTTP 头部 Wechatpay-Signature 中提供了应答签名。商户应验证应答签名,以确保数据来自微信支付且未经第三方篡改。

同样地,在通知回调的 HTTP 头部中,微信支付会包含回调报文的签名。商户必须验证回调的签名,以确保回调由微信支付发送。

您可以使用微信支付的 官方 SDK 。通过 SDK 调用微信支付接口时,无需关心签名的生成和验证,使接入更加便捷。

此外,您还可以参考以下内容,自行验证签名,并遵循指引防止重放攻击,以及应对签名探测流量。

# 前置条件

获取 微信支付平台证书

微信支付 APIv3 使用微信支付平台私钥(而非商户私钥)进行应答签名。相应的,您应使用微信支付平台证书中的公钥验签。目前,微信支付平台证书仅提供 API 下载,请参阅 获取平台证书列表

提示

请注意,验证应答和通知回调的签名应使用微信支付平台证书,而非商户 API 证书。使用商户 API 证书将验证失败。

# 检查平台证书序列号

在验证签名前,您应先检查 HTTP 头 Wechatpay-Serial 的内容是否跟商户当前所持有的微信支付平台证书的序列号一致。若不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将验证失败。

# 防止重放攻击

重放攻击 (opens new window)是指攻击者截取报文及其签名,并以恶意或欺诈目的重新传输数据的一种攻击手段。 为了降低此类攻击的风险,微信支付在 HTTP 头 Wechatpay-Timestamp 中提供了生成签名的时间戳。若微信支付需要重新发送某个通知回调,我们也会重新生成相应的时间戳和签名。

在验证签名之前,商户系统应检查时间戳是否已过期。我们建议商户系统允许最多5分钟的时间偏差。如果时间戳与当前时间的偏差超过5分钟,您应拒绝处理当前的响应或回调通知。

提示

您应当采用网络时间协议(NTP)等机制实现商户系统的时钟同步,保证时间准确。

# 构造验签名串

首先,您从应答或通知回调中获取以下信息:

  • HTTP 头 Wechatpay-Timestamp 中的应答时间戳
  • HTTP 头 Wechatpay-Nonce 中的应答随机串
  • 应答报文主体(Response Body),请使用原始报文主体执行验签。如果您使用了某个框架,要确保它不会篡改报文主体。对报文主体的任何篡改都会导致验证失败。

然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以 \n 结束,包括最后一行。\n 为换行符(ASCII 编码值为 0x0A)。若应答报文主体为空(如 HTTP 状态码为 204 No Content),最后一行仅为一个 \n 换行符。

1应答时间戳\n
2应答随机串\n
3应答报文主体\n

以某次下载微信支付平台证书 API 的应答 HTTP 报文(省略了 ciphertext 的具体内容)为例:

1HTTP/1.1 200 OK
2Server: nginx
3Date: Tue, 02 Apr 2019 12:59:40 GMT
4Content-Type: application/json; charset=utf-8
5Content-Length: 2204
6Connection: keep-alive
7Keep-Alive: timeout=8
8Content-Language: zh-CN
9Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a
10Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c
11Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==
12Wechatpay-Timestamp: 1554209980
13Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E1
14Cache-Control: no-cache, must-revalidate
15
16{"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"4de73afd28b6","associated_data":"certificate","ciphertext":"..."}}]}

则验签名串为

11554209980\n
2c5ac7061fccab6bf3e254dcf98995b8c\n
3{"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"4de73afd28b6","associated_data":"certificate","ciphertext":"..."}}]}\n

# 获取应答签名

微信支付的应答签名通过 HTTP 头 Wechatpay-Signature 传递。(请注意,示例可能存在换行,实际数据应在一行)

1Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==

使用 base64 解码 Wechatpay-Signature 字段值,得到应答签名。

提示

某些代理服务器或 CDN 服务提供商,在转发时可能会“过滤”微信支付扩展的 HTTP 头,导致应用层无法获取微信支付的签名信息。商户遇到这种情况时,我们建议尝试调整代理服务器配置,或者通过直连的方式访问微信支付的服务器和接收通知回调。

# 验证签名

很多编程语言的签名验证函数支持对验签名串和签名进行签名验证。强烈建议商户调用该类函数,使用微信支付平台公钥对验签名串和签名进行 SHA256 with RSA 签名验证。

下面展示使用命令行演示如何进行验签。假设我们已经获取了平台证书并保存为 1900009191_wxp_cert.pem

首先,从微信支付平台证书导出微信支付平台公钥。

1$ openssl x509 -in 1900009191_wxp_cert.pem -pubkey -noout > 1900009191_wxp_pub.pem
2$ cat 1900009191_wxp_pub.pem
3-----BEGIN PUBLIC KEY-----
4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4zej1cqugGQtVSY2Ah8RMCKcr2UpZ8Npo+5Ja9xpFPYkWHaF1Gjrn3d5kcwAFuHHcfdc3yxDYx6+9grvJnCA2zQzWjzVRa3BJ5LTMj6yqvhEmtvjO9D1xbFTA2m3kyjxlaIar/RYHZSslT4VmjIatW9KJCDKkwpM6x/RIWL8wwfFwgz2q3Zcrff1y72nB8p8P12ndH7GSLoY6d2Tv0OB2+We2Kyy2+QzfGXOmLp7UK/pFQjJjzhSf9jxaWJXYKIBxpGlddbRZj9PqvFPTiep8rvfKGNZF9Q6QaMYTpTp/uKQ3YvpDlyeQlYe4rRFauH3mOE6j56QlYQWivknDX9VrwIDAQAB
5-----END PUBLIC KEY-----

提示

Java支持使用证书初始化签名对象,详见 initVerify(Certificate) (opens new window),并不需要先导出公钥。

然后,使用 base64 解码应答签名,将保存为文件signature.txt

1$openssl base64 -d -A <<< \ 'CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==' > signature.txt

最后,验证签名,得到验签结果。

1$ openssl dgst -sha256 -verify 1900009191_wxp_pub.pem -signature signature.txt << EOF
21554209980
3c5ac7061fccab6bf3e254dcf98995b8c
4{"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"d215b0511e9c","associated_data":"certificate","ciphertext":"..."}}]}
5EOF
6Verified OK

# 应对签名探测流量

为了确保商户系统的安全,微信支付会在极少数应答或通知回调中生成错误签名,以探测商户系统是否正确地验证了签名。

商户系统不应对探测流量进行特殊处理,而应将其视为正常的应答或通知回调,并对其签名进行验证。 在排查问题时,您可以通过查看签名值中的 WECHATPAY/SIGNTEST/ 前缀快速判断是否为探测流量。所有用于探测目的的签名值都会包含此前缀。

在验签失败的情况下,我们建议商户系统采取以下措施:

  • 如果应答的签名验证失败,商户系统应舍弃该应答。为了提高用户体验,商户系统可以适当地重试,或者让用户重新发起请求。微信支付不会针对重试请求发起探测。

  • 若通知回调的签名验证失败,商户系统应返回失败(即应答 4xx5xx 的状态码),等待微信支付携带正确签名重新发送通知回调。

如果你有关于签名探测任何疑问,请通过在线技术咨询 (opens new window)联系我们的技术支持。