微信支付在 API 應(yīng)答的 HTTP 頭部 Wechatpay-Signature
中提供了應(yīng)答簽名。商戶應(yīng)驗證應(yīng)答簽名,以確保數(shù)據(jù)來自微信支付且未經(jīng)第三方篡改。
同樣地,在通知回調(diào)的 HTTP 頭部中,微信支付會包含回調(diào)報文的簽名。商戶必須驗證回調(diào)的簽名,以確保回調(diào)由微信支付發(fā)送。
您可以使用微信支付的 官方 SDK 。通過 SDK 調(diào)用微信支付接口時,無需關(guān)心簽名的生成和驗證,使接入更加便捷。
此外,您還可以參考以下內(nèi)容,自行驗證簽名,并遵循指引防止重放攻擊,以及應(yīng)對簽名探測流量。
# 前置條件
獲取 微信支付平臺證書。
微信支付 APIv3 使用微信支付平臺私鑰(而非商戶私鑰)進行應(yīng)答簽名。相應(yīng)的,您應(yīng)使用微信支付平臺證書中的公鑰驗簽。目前,微信支付平臺證書僅提供 API 下載,請參閱 獲取平臺證書列表。
提示
請注意,驗證應(yīng)答和通知回調(diào)的簽名應(yīng)使用微信支付平臺證書,而非商戶 API 證書。使用商戶 API 證書將驗證失敗。
# 檢查平臺證書序列號
在驗證簽名前,您應(yīng)先檢查 HTTP 頭 Wechatpay-Serial
的內(nèi)容是否跟商戶當前所持有的微信支付平臺證書的序列號一致。若不一致,請重新獲取證書。否則,簽名的私鑰和證書不匹配,將驗證失敗。
# 防止重放攻擊
重放攻擊 (opens new window)是指攻擊者截取報文及其簽名,并以惡意或欺詐目的重新傳輸數(shù)據(jù)的一種攻擊手段。
為了降低此類攻擊的風險,微信支付在 HTTP 頭 Wechatpay-Timestamp
中提供了生成簽名的時間戳。若微信支付需要重新發(fā)送某個通知回調(diào),我們也會重新生成相應(yīng)的時間戳和簽名。
在驗證簽名之前,商戶系統(tǒng)應(yīng)檢查時間戳是否已過期。我們建議商戶系統(tǒng)允許最多5分鐘的時間偏差。如果時間戳與當前時間的偏差超過5分鐘,您應(yīng)拒絕處理當前的響應(yīng)或回調(diào)通知。
提示
您應(yīng)當采用網(wǎng)絡(luò)時間協(xié)議(NTP)等機制實現(xiàn)商戶系統(tǒng)的時鐘同步,保證時間準確。
# 構(gòu)造驗簽名串
首先,您從應(yīng)答或通知回調(diào)中獲取以下信息:
- HTTP 頭
Wechatpay-Timestamp
中的應(yīng)答時間戳 - HTTP 頭
Wechatpay-Nonce
中的應(yīng)答隨機串 - 應(yīng)答報文主體(Response Body),請使用原始報文主體執(zhí)行驗簽。如果您使用了某個框架,要確保它不會篡改報文主體。對報文主體的任何篡改都會導致驗證失敗。
然后,請按照以下規(guī)則構(gòu)造應(yīng)答的驗簽名串。簽名串共有三行,行尾以 \n
結(jié)束,包括最后一行。\n
為換行符(ASCII 編碼值為 0x0A)。若應(yīng)答報文主體為空(如 HTTP 狀態(tài)碼為 204 No Content
),最后一行僅為一個 \n
換行符。
1應(yīng)答時間戳\n2應(yīng)答隨機串\n3應(yīng)答報文主體\n
以某次下載微信支付平臺證書 API 的應(yīng)答 HTTP 報文(省略了 ciphertext 的具體內(nèi)容)為例:
1HTTP/1.1 200 OK2Server: nginx3Date: Tue, 02 Apr 2019 12:59:40 GMT4Content-Type: application/json; charset=utf-85Content-Length: 22046Connection: keep-alive7Keep-Alive: timeout=88Content-Language: zh-CN9Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a10Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c11Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==12Wechatpay-Timestamp: 155420998013Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E114Cache-Control: no-cache, must-revalidate1516{"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\n2c5ac7061fccab6bf3e254dcf98995b8c\n3{"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
# 獲取應(yīng)答簽名
微信支付的應(yīng)答簽名通過 HTTP 頭 Wechatpay-Signature
傳遞。(請注意,示例可能存在換行,實際數(shù)據(jù)應(yīng)在一行)
1Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==
使用 base64 解碼 Wechatpay-Signature
字段值,得到應(yīng)答簽名。
提示
某些代理服務(wù)器或 CDN 服務(wù)提供商,在轉(zhuǎn)發(fā)時可能會“過濾”微信支付擴展的 HTTP 頭,導致應(yīng)用層無法獲取微信支付的簽名信息。商戶遇到這種情況時,我們建議嘗試調(diào)整代理服務(wù)器配置,或者通過直連的方式訪問微信支付的服務(wù)器和接收通知回調(diào)。
# 驗證簽名
很多編程語言的簽名驗證函數(shù)支持對驗簽名串和簽名進行簽名驗證。強烈建議商戶調(diào)用該類函數(shù),使用微信支付平臺公鑰對驗簽名串和簽名進行 SHA256 with RSA 簽名驗證。
下面展示使用命令行演示如何進行驗簽。假設(shè)我們已經(jīng)獲取了平臺證書并保存為 1900009191_wxp_cert.pem
。
首先,從微信支付平臺證書導出微信支付平臺公鑰。
1$ openssl x509 -in 1900009191_wxp_cert.pem -pubkey -noout > 1900009191_wxp_pub.pem2$ cat 1900009191_wxp_pub.pem3-----BEGIN PUBLIC KEY-----4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4zej1cqugGQtVSY2Ah8RMCKcr2UpZ8Npo+5Ja9xpFPYkWHaF1Gjrn3d5kcwAFuHHcfdc3yxDYx6+9grvJnCA2zQzWjzVRa3BJ5LTMj6yqvhEmtvjO9D1xbFTA2m3kyjxlaIar/RYHZSslT4VmjIatW9KJCDKkwpM6x/RIWL8wwfFwgz2q3Zcrff1y72nB8p8P12ndH7GSLoY6d2Tv0OB2+We2Kyy2+QzfGXOmLp7UK/pFQjJjzhSf9jxaWJXYKIBxpGlddbRZj9PqvFPTiep8rvfKGNZF9Q6QaMYTpTp/uKQ3YvpDlyeQlYe4rRFauH3mOE6j56QlYQWivknDX9VrwIDAQAB5-----END PUBLIC KEY-----
提示
Java支持使用證書初始化簽名對象,詳見 initVerify(Certificate) (opens new window),并不需要先導出公鑰。
然后,使用 base64 解碼應(yīng)答簽名,將保存為文件signature.txt
。
1$openssl base64 -d -A <<< \ 'CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==' > signature.txt
最后,驗證簽名,得到驗簽結(jié)果。
1$ openssl dgst -sha256 -verify 1900009191_wxp_pub.pem -signature signature.txt << EOF215542099803c5ac7061fccab6bf3e254dcf98995b8c4{"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":"..."}}]}5EOF6Verified OK
# 應(yīng)對簽名探測流量
為了確保商戶系統(tǒng)的安全,微信支付會在極少數(shù)應(yīng)答或通知回調(diào)中生成錯誤簽名,以探測商戶系統(tǒng)是否正確地驗證了簽名。
商戶系統(tǒng)不應(yīng)對探測流量進行特殊處理,而應(yīng)將其視為正常的應(yīng)答或通知回調(diào),并對其簽名進行驗證。
在排查問題時,您可以通過查看簽名值中的 WECHATPAY/SIGNTEST/
前綴快速判斷是否為探測流量。所有用于探測目的的簽名值都會包含此前綴。
在驗簽失敗的情況下,我們建議商戶系統(tǒng)采取以下措施:
如果應(yīng)答的簽名驗證失敗,商戶系統(tǒng)應(yīng)舍棄該應(yīng)答。為了提高用戶體驗,商戶系統(tǒng)可以適當?shù)刂卦嚕蛘咦層脩糁匦掳l(fā)起請求。微信支付不會針對重試請求發(fā)起探測。
若通知回調(diào)的簽名驗證失敗,商戶系統(tǒng)應(yīng)返回失敗(即應(yīng)答
4xx
或5xx
的狀態(tài)碼),等待微信支付攜帶正確簽名重新發(fā)送通知回調(diào)。
如果你有關(guān)于簽名探測任何疑問,請通過在線技術(shù)咨詢 (opens new window)聯(lián)系我們的技術(shù)支持。