API v3版微信支付(三)----驗簽


簽名驗證

商戶可以按照下述步驟驗證應答或者回調的簽名。

 

如果驗證商戶的請求簽名正確,微信支付會在應答的HTTP頭部中包括應答簽名。我們建議商戶驗證應答簽名。

同樣的,微信支付會在回調的HTTP頭部中包括回調報文的簽名。商戶必須 驗證回調的簽名,以確保回調是由微信支付發送。

獲取平台證書

微信支付API v3使用微信支付 的平台私鑰(不是商戶私鑰 )進行應答簽名。相應的,商戶的技術人員應使用微信支付平台證書中的公鑰驗簽。目前平台證書只提供API進行下載,請參考 獲取平台證書列表

再次提醒,應答和回調的簽名驗證使用的是  微信支付平台證書,不是商戶API證書。使用商戶API證書是驗證不過的。

檢查平台證書序列號

微信支付的平台證書序列號位於HTTP頭Wechatpay-Serial。驗證簽名前,請商戶先檢查序列號是否跟商戶當前所持有的 微信支付平台證書的序列號一致。如果不一致,請重新獲取證書。否則,簽名的私鑰和證書不匹配,將無法成功驗證簽名。

構造驗簽名串

首先,商戶先從應答中獲取以下信息。

  • HTTP頭Wechatpay-Timestamp 中的應答時間戳。
  • HTTP頭Wechatpay-Nonce 中的應答隨機串
  • 應答主體(response Body)

然后,請按照以下規則構造應答的驗簽名串。簽名串共有三行,行尾以\n 結束,包括最后一行。\n為換行符(ASCII編碼值為0x0A)。若應答報文主體為空(如HTTP狀態碼為204 No Content),最后一行僅為一個\n換行符。


應答時間戳\n
應答隨機串\n
應答報文主體\n
              

如某個應答的HTTP報文為(省略了ciphertext的具體內容):


HTTP/1.1 200 OK Server: nginx Date: Tue, 02 Apr 2019 12:59:40 GMT Content-Type: application/json; charset=utf-8 Content-Length: 2204 Connection: keep-alive Keep-Alive: timeout=8 Content-Language: zh-CN Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA== Wechatpay-Timestamp: 1554209980 Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E1 Cache-Control: no-cache, must-revalidate {"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":"..."}}]} 

則驗簽名串為

1554209980 c5ac7061fccab6bf3e254dcf98995b8c {"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":"..."}}]}

獲取應答簽名

微信支付的應答簽名通過HTTP頭Wechatpay-Signature傳遞。(注意,示例因為排版可能存在換行,實際數據應在一行)

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

Wechatpay-Signature的字段值使用Base64進行解碼,得到應答簽名。

某些代理服務器或CDN服務提供商,轉發時會“過濾”微信支付擴展的HTTP頭,導致應用層無法取到微信支付的簽名信息。商戶遇到這種情況時,我們建議嘗試調整代理服務器配置,或者通過直連的方式訪問微信支付的服務器和接收通知。

驗證簽名

很多編程語言的簽名驗證函數支持對驗簽名串和簽名 進行簽名驗證。強烈建議商戶調用該類函數,使用微信支付平台公鑰對驗簽名串和簽名進行SHA256 with RSA簽名驗證。

下面展示使用命令行演示如何進行驗簽。假設我們已經獲取了平台證書並保存為1900009191_wxp_cert.pem 。

首先,從微信支付平台證書導出微信支付平台公鑰

$ openssl x509 -in 1900009191_wxp_cert.pem -pubkey -noout > 1900009191_wxp_pub.pem $ cat 1900009191_wxp_pub.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4zej1cqugGQtVSY2Ah8R MCKcr2UpZ8Npo+5Ja9xpFPYkWHaF1Gjrn3d5kcwAFuHHcfdc3yxDYx6+9grvJnCA 2zQzWjzVRa3BJ5LTMj6yqvhEmtvjO9D1xbFTA2m3kyjxlaIar/RYHZSslT4VmjIa tW9KJCDKkwpM6x/RIWL8wwfFwgz2q3Zcrff1y72nB8p8P12ndH7GSLoY6d2Tv0OB 2+We2Kyy2+QzfGXOmLp7UK/pFQjJjzhSf9jxaWJXYKIBxpGlddbRZj9PqvFPTiep 8rvfKGNZF9Q6QaMYTpTp/uKQ3YvpDlyeQlYe4rRFauH3mOE6j56QlYQWivknDX9V rwIDAQAB -----END PUBLIC KEY-----
Java支持使用證書初始化簽名對象,詳見  initVerify(Certificate),並不需要先導出公鑰。

然后,把簽名base64解碼后保存為文件signature.txt

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

最后,驗證簽名

$ openssl dgst -sha256 -verify 1900009191_wxp_pub.pem -signature signature.txt << EOF 1554209980 c5ac7061fccab6bf3e254dcf98995b8c {"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":"..."}}]} EOF Verified OK


代碼實現
 /**
     * 驗證簽名
     *
     * @param timestamp   微信平台傳入的時間戳
     * @param nonce       微信平台傳入的隨機字符串
     * @param requestBody 微信平台傳入的消息體
     * @param signature   微信平台傳入的簽名
     * @return
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws IOException
     * @throws InvalidKeyException
     */
    public static boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        //構造驗簽名串
        String signatureStr = timestamp + "\n" + nonce + "\n" + JSONObject.toJSONString(requestBody) + "\n";
        // 加載SHA256withRSA簽名器
        Signature signer = Signature.getInstance("SHA256withRSA");
        // 用微信平台公鑰對簽名器進行初始化(調上一節中的獲取平台證書方法)
        signer.initVerify(CommonUtils.getCertificates());
        // 把我們構造的驗簽名串更新到簽名器中
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        // 把請求頭中微信服務器返回的簽名用Base64解碼 並使用簽名器進行驗證
        boolean result = signer.verify(Base64Utils.decodeFromString(signature));
        return result;
    }

最后來個完整實例:

/**
     * 回調地址
     *
     * @param requestBody
     * @return
     * @throws IOException
     */
    @PostMapping("/api/notify/complaintsNotify")
    public Map<String, String> complaintsNotify(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws Exception {
        Map<String, String> data = new HashMap<>();
        String signature = request.getHeader("Wechatpay-Signature");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        //平台證書序列號不是API證書序列號
        String serial = request.getHeader("Wechatpay-Serial");
        log.info("頭信息---簽名:" + signature);
        log.info("頭信息---時間戳:" + timestamp);
        log.info("頭信息---隨機字符:" + nonce);
        log.info("頭信息---平台證書序列號:" + serial);
        log.info("獲取到的body信息:" + JSONObject.toJSONString(requestBody));
        //驗簽
        boolean signCheck = SignUtils.signCheck(timestamp, nonce, requestBody, signature);
        log.info("驗簽結果:" + signCheck);
        if (signCheck) {
            //解密參數
            Resource resource = JSONObject.parseObject(JSONObject.toJSONString(requestBody.get("resource")), Resource.class);
            AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8"));
            String string = aesUtil.decryptToString(resource.getAssociated_data().getBytes("utf-8"), resource.getNonce().getBytes("utf-8"), resource.getCiphertext());
            ComplaintInfo complaintInfo = JSONObject.parseObject(string, ComplaintInfo.class);
            //獲取投訴詳情
            ComplaintDetail complaintDetail = CommonUtils.GetComplaintsInfo(complaintInfo.getComplaint_id());
            data.put("code", "SUCCESS");
            data.put("message", "成功");
            return data;
        }
        return data;
    }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM