WeChat payment in Java: API V3 performs signature verification on WeChat server response

1 Introduction

Remember one sentence: public key encryption, private key decryption; private key signing, public key verification.

The first two articles of WeChat Pay V3 describe how to sign the request and how to obtain and refresh the WeChat platform public key. This article will continue to expand on how to verify the WeChat Pay response result.

2. Why do you need to verify the response?

WeChat Pay will include the signature of the callback message in the HTTP header of the callback. Merchants must verify the signature of the response to ensure that the response is indeed from the WeChat payment server to avoid man-in-the-middle attacks. In addition to the public key of the WeChat platform, the verification response signature also requires other parameters from the request header.

Suppose the following is the response from the WeChat payment server:

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

{"prepay_id":"wx2922034726858082fbd40b511c67630000"}

Check the platform certificate serial number

When WeChat Pay responds, it will carry a WeChat platform certificate serial number, and get the value from the Wechatpay-Serial field in the response header to prompt us to use the certificate of the serial number for signature verification. If it does not exist, we need to refresh it. In the previous article, we stored the platform certificate serial number and certificate in the HashMap as a key-value pair. We only need to check whether it exists, and refresh it if it does not exist.

Construct verification signature string

The verification signature string can be constructed by obtaining the three parameters corresponding to the following method from the response result.

/**
 * Construct the verification signature string.
 *
 * @param wechatpayTimestamp HTTP Reply timestamp in header Wechatpay-Timestamp.
 * @param wechatpayNonce     HTTP Response random string in header Wechatpay-Nonce
 * @param body               response body
 * @return the string
 */
public String responseSign(String wechatpayTimestamp, String wechatpayNonce, String body) {
    return Stream.of(wechatpayTimestamp, wechatpayNonce, body)
            .collect(Collectors.joining("\n", "", "\n"));
}

Verify signature

The signature to be verified is obtained from the Wechatpay-Signature field in the response header. We use the WeChat payment platform public key to perform SHA256 with RSA signature verification on the verification signature string and signature.

   // Construct verification signature string  
        final String signatureStr = responseSign(wechatpayTimestamp, wechatpayNonce, body);
   // Load SHA256withRSA signer
        Signature signer = Signature.getInstance("SHA256withRSA");
  // Initialize the signer with the WeChat platform public key
        signer.initVerify(certificate);
   // Update the verification signature string we constructed to the signer
        signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
   // Decode the signature returned by the WeChat server in the request header with Base64 and verify it with the signer
        boolean result = signer.verify(Base64Utils.decodeFromString(wechatpaySignature));

Complete signature verification code

/**
 * We verify the response signature, compare it with the response signature, and use the WeChat platform certificate.
 *
 * @param wechatpaySerial    response.headers['Wechatpay-Serial']    The current WeChat platform certificate serial number
 * @param wechatpaySignature response.headers['Wechatpay-Signature']   WeChat platform signature
 * @param wechatpayTimestamp response.headers['Wechatpay-Timestamp']  Timestamp of WeChat server
 * @param wechatpayNonce     response.headers['Wechatpay-Nonce']   Random string provided by WeChat server
 * @param body               response.body WeChat server response body
 * @return the boolean
 */
@SneakyThrows
public boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {

    if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
        refreshCertificate();
    }
    Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);

    final String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, body);
    Signature signer = Signature.getInstance("SHA256withRSA");
    signer.initVerify(certificate);
    signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));

    return signer.verify(Base64Utils.decodeFromString(wechatpaySignature));
}

The CERTIFICATE_MAP platform certificate container can refer to the previous article.

3. Summary

Passing the signature verification means that the response to our request comes from the WeChat server, and the corresponding logical processing can be performed on the result. The WeChat payment API, whether V2 or V3, includes the use of Api certificate to sign the request and verify the response result. The process is a test of the use of the password digest algorithm, and the other is to organize the parameters to call the Http request. If you can master this ability, you will have an advantage in interviews and at work. Well, today's sharing is here, pay more attention: Code Farmer Little Fat Brother Get more practical programming dry goods.

Pay attention to the public number: Felordcn for more information

Personal blog: https://felord.cn

Tags: Java

Posted by 2005 on Sun, 08 May 2022 23:07:45 +0300