Java中的微信支付(2):API V3 微信平臺證書的獲取與刷新

1. 前言

Java中的微信支付(1):API V3版本簽名詳解一文中胖哥講解了微信支付V3版本API的簽名,當我方(你本身的服務器)請求微信支付服務器時須要根據我方的API證書對參數進行加簽,微信服務器會根據我方簽名驗籤以肯定請求來自我方服務器。那麼一樣的道理我方的服務器也要對微信支付服務器的響應進行鑑別來肯定響應真的來自微信支付服務器,這就是驗籤。驗籤使用的是【微信支付平臺證書公鑰】,不是商戶API證書。使用商戶API證書是驗證不過的。今天就來分享一下如何得到微信平臺公鑰和動態刷新微信平臺公鑰。java

2. 獲取微信平臺證書公鑰

微信平臺證書是微信支付平臺本身的證書,咱們是管不了的,並且是有效期的。

微信服務器會按期更換,因此也要求我方按期獲取公鑰。並且咱們只能經過調用接口/v3/certificates來得到,此接口也須要進行簽名(可參考上一篇文章)。你能夠獲取證書後靜態放到服務器上,手動更新靜態證書;也能夠動態獲取一勞永逸。本文采起一勞永逸的辦法。git

[平臺證書接口文檔](): https://wechatpay-api.gitbook...

3. 證書和回調報文解密

爲了保證安全性,微信支付在回調通知平臺證書下載接口中,對關鍵信息進行了AES-256-GCM加密。也就是說咱們拿到響應的信息是被加密的,須要解密後才能得到真正的微信平臺證書公鑰。響應體大體是這樣的,具體根據你調用平臺證書接口,應該大差不差是下面這個結構:算法

{
    "data": [
        {
            "effective_time": "2020-10-21T14:48:49+08:00",
            "encrypt_certificate": {
                // 加密算法
                "algorithm": "AEAD_AES_256_GCM",
                    // 附加數據包(可能爲空)
                "associated_data": "certificate",
                   // Base64編碼後的密文
                "ciphertext": "",
                 // 加密使用的隨機串初始化向量)
                "nonce": "88b4e15a0db9"
            },
            "expire_time": "2025-10-20T14:48:49+08:00",
            // 證書序列號
            "serial_no": "217016F42805DD4D5442059D373F98BFC5252599"
        }
    ]
}

你可使用各類JSON類庫取得下面方法的參數進行解密以獲取證書,同時這裏須要用到APIv3密鑰,通用的解密方式爲:json

/**
 * 解密響應體.
 *
 * @param apiV3Key       API V3 KEY  API v3密鑰 商戶平臺設置的32位字符串
 * @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 new IllegalArgumentException(e);
    }
}
回調的請求體也是此方法進行解密。

3. 動態刷新

而後就能拿到微信平臺證書公鑰。而後你能夠定義個Map,以證書的序列號爲KEY,以證書爲Value來動態刷新,關鍵僞代碼:api

// 定義全局容器 保存微信平臺證書公鑰  注意線程安全 
private static final Map<String, Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>();

// 下面是刷新方法 refreshCertificate  的核心代碼
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();
// 清理HashMap
CERTIFICATE_MAP.clear();
// 放入證書
CERTIFICATE_MAP.put(responseSerialNo, certificate);

動態刷新的策略就很好寫了:安全

// 當證書容器爲空 或者 響應提供的證書序列號不在容器中時  就應該刷新了
if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
    refreshCertificate();
}
// 而後調用 
Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);

4. 總結

雖然驗籤你不作能夠拿到其它接口的響應結果,可是從資金安全的角度來講這是十分必要的。同時由於微信平臺證書不收我方控制,採起動態刷新也會更加方便,沒必要再擔憂過時的問題。本文咱們經過調用接口拿到密文並解密得到證書。下一篇咱們將經過得到的證書進行簽名驗證來確保咱們的響應是微信服務器發過來的,請關注:碼農小胖哥 及時得到相關的更新。服務器

關注公衆號:Felordcn 獲取更多資訊微信

我的博客:https://felord.cn微信支付

相關文章
相關標籤/搜索