Java中的微信支付(2):API V3 微信平臺證書的獲取與重新整理
阿新 • • 發佈:2020-10-30
![](https://img2020.cnblogs.com/other/1739473/202010/1739473-20201030090354982-980711550.jpg)
## 1. 前言
在[Java中的微信支付(1):API V3版本簽名詳解](https://mp.weixin.qq.com/s/iiTCr57FgbAb6s0P0hT-9Q)一文中胖哥講解了微信支付V3版本API的簽名,當我方(你自己的伺服器)請求微信支付伺服器時需要根據我方的**API證書**對引數進行加簽,微信伺服器會根據我方簽名驗籤以確定請求來自我方伺服器。那麼同樣的道理我方的伺服器也要對微信支付伺服器的響應進行鑑別來確定響應真的來自微信支付伺服器,這就是**驗籤**。驗籤使用的是**【微信支付平臺證書公鑰**】,不是商戶API證書。**使用商戶API證書是驗證不過的**。今天就來分享一下如何獲得微信平臺公鑰和動態重新整理微信平臺公鑰。
## 2. 獲取微信平臺證書公鑰
> 微信平臺證書是微信支付平臺自己的證書,我們是管不了的,而且是有效期的。
微信伺服器會定期更換,所以也要求我方定期獲取公鑰。而且我們只能通過呼叫介面`/v3/certificates`來獲得,此介面也需要進行簽名(可參考[上一篇](https://mp.weixin.qq.com/s/iiTCr57FgbAb6s0P0hT-9Q)文章)。你可以獲取證書後靜態放到伺服器上,手動更新靜態證書;也可以動態獲取一勞永逸。本文采取一勞永逸的辦法。
> [平臺證書介面文件]():https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu
## 3. 證書和回撥報文解密
為了保證安全性,微信支付在**回撥通知**和**平臺證書下載**介面中,對關鍵資訊進行了`AES-256-GCM`加密。也就是說**我們拿到響應的資訊是被加密的,需要解密後才能獲得真正的微信平臺證書公鑰**。響應體大致是這樣的,具體根據你呼叫平臺證書介面,應該大差不差是下面這個結構:
```json
{
"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金鑰`,通用的解密方式為:
```java
/**
* 解密響應體.
*
* @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來動態重新整理,關鍵虛擬碼:
```java
// 定義全域性容器 儲存微信平臺證書公鑰 注意執行緒安全
private static f