1. Introduction

WeChat pay (1) : in the Java API V3 version signature, rounding the article explained the WeChat punchinello pay V3 version of the API signature, when we (your own server) that needs to be according to our request WeChat pay the server API certificate to the signature of the parameters, and WeChat server will be according to our signature attestation to confirm the request from our server. In the same way, our server should also identify the response from the wechat Pay server to confirm that the response really comes from the wechat pay server, which is the visa check. The verification uses ** [wechat Payment platform certificate public key **], not the merchant API certificate. Using merchant API certificates is not verifiable. Today to share how to obtain the wechat platform public key and dynamic refresh wechat platform public key.

2. Obtain the public key of the wechat platform certificate

The wechat platform certificate is the wechat Payment platform’s own certificate, which is beyond our control and valid.

Wechat server will be replaced regularly, so we are required to obtain the public key regularly. And we can only get it by calling the interface /v3/ Certificates, which also needs to be signed (see the previous article). You can obtain the certificate, put it statically on the server, and manually update the static certificate. Can also be dynamically acquired once and for all. This paper takes a once and for all approach.

Platform certificate Interface document: wechatpay-api.gitbook. IO/wechatPay-a…

3. Decrypt the certificate and callback packets

To ensure security, key information is encrypted aes-256-GCM in wechat Pay’s callback notification and platform certificate download interface. That is to say, the response information we get is encrypted, and only after decryption can we get the real wechat platform certificate public key. The response body should look like this, depending on how you call the platform certificate interface, and should look like the following:

{
    "data": [{"effective_time": "2020-10-21T14:48:49+08:00"."encrypt_certificate": {
                // Encryption algorithm
                "algorithm": "AEAD_AES_256_GCM".// Attach packet (possibly null)
                "associated_data": "certificate".// Base64 encoded ciphertext
                "ciphertext": "".// use random string to initialize vector)
                "nonce": "88b4e15a0db9"
            },
            "expire_time": "2025-10-20T14:48:49+08:00".// Certificate serial number
            "serial_no": "217016F42805DD4D5442059D373F98BFC5252599"}}]Copy the code

You can use various JSON libraries to decrypt the following methods to obtain the certificate, using the APIv3 key. The general decryption method is:

/** * Decrypts the response body@paramApiV3Key API V3 KEY API V3 KEY vendor platform set to a 32-bit character string *@param associatedData  response.body.data[i].encrypt_certificate.associated_data
 * @param nonce          response.body.data[i].encrypt_certificate.nonce
 * @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext
 * @return the string
 * @throws GeneralSecurityException the general security exception
 */
public String decryptResponseBody(String apiV3Key,String associatedData, String nonce, String ciphertext) {
    try {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
   
        byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }    
        return new String(bytes, StandardCharsets.UTF_8);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        throw new IllegalStateException(e);
    } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
        throw newIllegalArgumentException(e); }}Copy the code

The request body of the callback is also decrypted in this way.

3. Dynamic refresh

Then you can get the wechat platform certificate public key. You can then define a Map that dynamically refreshes the certificate with the serial number as KEY and the certificate as Value.

// Define global container to save wechat platform certificate public key Note thread safety
private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();

// here is the core code for the refreshCertificate method
String publicKey = decryptResponseBody(associatedData, nonce, ciphertext);

final CertificateFactory cf = CertificateFactory.getInstance("X509");

ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
Certificate certificate = null;
try {
    certificate = cf.generateCertificate(inputStream);
} catch (CertificateException e) {
    e.printStackTrace();
}
String responseSerialNo = objectNode.get("serial_no").asText();
/ / clean up the HashMap
CERTIFICATE_MAP.clear();
// Add the certificate
CERTIFICATE_MAP.put(responseSerialNo, certificate);
Copy the code

The dynamic refresh strategy is easy to write:

// It should be flushed when the certificate container is empty or the certificate serial number provided in response is not in the container
if(CERTIFICATE_MAP.isEmpty() || ! CERTIFICATE_MAP.containsKey(wechatpaySerial)) { refreshCertificate(); }// Then call
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);
Copy the code

4. To summarize

Although you can get the response results of other interfaces without checking, it is very necessary from the perspective of financial security. At the same time, because the wechat platform certificate is not subject to our control, it will be more convenient to adopt dynamic refresh, and there is no need to worry about the issue of expiration. In this paper, we call the interface to get the ciphertext and decrypt to get the certificate. In the next article, we will verify the signature of the obtained certificate to ensure that our response is sent from the wechat server. Please pay attention to the code farmer Xiao Pangge to get relevant updates in time.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn