Wechat payment in Java (1): API V3 version signature details

Time:2021-5-9

1. Preface

Recently, when you are struggling with wechat payment, the certificate is still quite annoying, so it is necessary to share some experience to reduce your step in the development of wechat payment. At present, the API of wechat payment has developed toV3Version, using the popular restful style.

微信支付V2与V3的区别

Today, let’s share the difficulties of wechat payment——autographAlthough there are many useful SDKs, you need to know more about wechat payment if you want to know more about it.

2. API certificate

In order to ensure the security of fund sensitive data and ensure the capital transactions in our business are safe. At present, wechat payment is provided in the authoritative CA certificate (API certificate) issued by the third partyPrivate keyTo sign.You can set and obtain API certificate through merchant platform

API证书

Remember that you will be prompted to download when you set it for the first time, and download will not be provided later. Please refer to the instructions for details.

API证书说明

Found after settingzipCompressed package decompression, there are many files, for java development, only need to pay attention toapiclient_cert.p12This certificate file containsPublic private keyWe need to put it on the server side and parse it with Java.p12File to get the public key and private key.

Be sure to ensure the security of the certificate on the server side, which involves the security of funds

Parsing API certificate

The next step is the certificate analysis. There are many methods for certificate analysis on the Internet. Here I use a more “formal” method to analyze, using the JDK security packagejava.security.KeyStoreTo analyze.

Wechat payment API certificate usesPKCS12Algorithm, we passKeyStoreTo obtain the carrier of the public-private key pairKeyPairAnd certificate serial numberserialNumber, I encapsulate the tool class:

import org.springframework.core.io.ClassPathResource;

import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

/**
 * KeyPairFactory
 *
 * @author dax
 * @since 13:41
 **/
public class KeyPairFactory {

    private KeyStore store;

    private final Object lock = new Object();

    /**
     *Obtain public and private keys
     *
     * @param keyPath  the key path
     * @param keyAlias the key alias
     * @param keyPass  password
     * @return the key pair
     */
    public KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {
        ClassPathResource resource = new ClassPathResource(keyPath);
        char[] pem = keyPass.toCharArray();
        try {
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(resource.getInputStream(), pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            //The serial number of the certificate is also useful
            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            //The public key of the certificate
            PublicKey publicKey = certificate.getPublicKey();
            //The private key of the certificate
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
    
            return new KeyPair(publicKey, storeKey);

        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " + resource, e);
        }
    }
}

You can see that it’s a modified version of JWT’s public and private key extraction method in Pangge spring security tutorial. You can compare the differences.

There are three parameters in this method, which must be explained here:

  • keyPathAPI certificateapiclient_cert.p12OfclasspathPath, we usually put it in theresourcesPath, of course, you can modify the way to get the certificate input stream.
  • keyAliasThe alias of the certificate. This wechat document does not exist. Pangge gets the value fixed asTenpay Certificate
  • keyPassThe certificate password, which is the merchant number by default, also needs to be used in other configurationsmchidThat’s what you useSuper administratorLog in to the wechat merchant platform and display a series of numbers in the personal data.

3. V3 signature

Wechat payment V3 versionWhen we call the specific wechat payment API, we carry a specific coding string in the HTTP request header for the wechat payment server to verify the source of the request and ensure that the request is authentic.

Signature format

The specific format of the signature string, a total of five lines, a line can not be less, each line to newline character\nend.

HTTP request method
URL\n
Request timestamp
Request random string
Request message body
  • HTTP request methodThe request method required by the wechat payment API you call, for example, APP payment isPOST
  • URLFor example, the app payment document ishttps://api.mch.weixin.qq.com/v3/pay/transactions/app, remove the domain name part to get the URL participating in the signature. If there are query parameters in the request, a ‘?’ should be appended at the end of the URL And the corresponding query string. Here is/v3/pay/transactions/app
  • Request timestampServer system time stamp to ensure that the server time is correct and usedSystem.currentTimeMillis() / 1000Just get it.
  • Request random stringFind a tool class to generate similar593BEC0C930BF1AFEB40B4A08C8FB242It’s just a string.
  • Request message bodyIf it isGETRequest direct forNull character""; When the request method isPOSTorPUTPlease use theReal sendingOfJSONMessage. Picture upload API, please usemetaCorrespondingJSONMessage.

Generate signature

Then we use the merchant’s private key toString to be signedSha256 with RSA signature, and the signature results are analyzedBase64 encodingGet the signature value. The corresponding core Java code is:

/**
 *V3 sha256with RSA signature
 *
 *@ param method request method get Post put delete, etc
 *@ param canonicalurl, for example https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1  ——> /v3/pay/transactions/app?version=1
 *@ param timestamp the current timestamp should be configured in token, so the signature should be consistent with token
 *The @ param noncestr random string should be consistent with that in token
 *@ param body request body get is "" post is JSON
 *The key pair resolved by @ param keypair merchant API certificate actually uses the private key
 * @return the string
 */
@SneakyThrows
String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
    String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
            .collect(Collectors.joining("\n", "", "\n"));
    Signature sign = Signature.getInstance("SHA256withRSA");
    sign.initSign(keyPair.getPrivate());
    sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
    return Base64Utils.encodeToString(sign.sign());
}

4. Use signature

After the signature is generated, it will be combined with some parameters to form aTokenTo the corresponding HTTP requestAuthorizationIn the request header, the format is:

Authorization: WECHATPAY2-SHA256-RSA2048 {Token}

TokenIt consists of the following five parts

TokenGenerated core code:

/**
 *Generate token
 *
 *@ param mchid merchant number
 *@ param noncestr random string 
 *@ param timestamp
 *@ param serialno certificate serial number
 *@ param signature
 * @return the string
 */
String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
    final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
    //Generate token
    return String.format(TOKEN_PATTERN,
            wechatPayProperties.getMchId(),
            nonceStr, timestamp, serialNo, signature);
}

TheTokenPut it in the request header according to the above format to complete the use of signature.

5. Summary

In this paper, we have a complete analysis of the difficulty signature and the use of signature in wechat payment V3 version, and also explained the parsing of API certificate, which I believe can help you solve some specific problems in payment development. Later, I will explain the verification of signatureManong little fat brotherAcquire series knowledge in time.

Pay attention to the official account: Felordcn for more information

Personal blog: https://felord.cn

Recommended Today

The selector returned by ngrx store createselector performs one-step debugging of fetching logic

Test source code: import { Component } from ‘@angular/core’; import { createSelector } from ‘@ngrx/store’; export interface State { counter1: number; counter2: number; } export const selectCounter1 = (state: State) => state.counter1; export const selectCounter2 = (state: State) => state.counter2; export const selectTotal = createSelector( selectCounter1, selectCounter2, (counter1, counter2) => counter1 + counter2 ); // […]