WechatPay-API-v3
搜索文档…
证书和回调报文解密
为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。本章节详细介绍了加密报文的格式,以及如何进行解密。

加密报文格式

AES-GCM是一种NIST标准的认证加密算法, 是一种能够同时保证数据的保密性、 完整性和真实性的一种加密模式。它最广泛的应用是在TLS中。
证书和回调报文使用的加密密钥为APIv3密钥
对于加密的数据,我们使用了一个独立的JSON对象来表示。为了方便阅读,示例做了Pretty格式化,并加入了注释。
1
{
2
"original_type": "transaction", // 加密前的对象类型
3
"algorithm":"AEAD_AES_256_GCM", // 加密算法
4
5
// Base64编码后的密文
6
"ciphertext": "...",
7
// 加密使用的随机串初始化向量)
8
"nonce": "...",
9
// 附加数据包(可能为空字符串)
10
"associated_data": ""
11
}
Copied!
加密的随机串,跟签名时使用的随机串没有任何关系,是不一样的。

解密

算法接口的细节,可以参考RFC 5116
大部分编程语言(较新版本)都支持了AEAD_AES_256_GCM。开发者可以参考下列的示例,了解如何使用您的编程语言实现解密。
Java
PHP
.NET
Python
1
import java.io.IOException;
2
import java.security.GeneralSecurityException;
3
import java.security.InvalidAlgorithmParameterException;
4
import java.security.InvalidKeyException;
5
import java.security.NoSuchAlgorithmException;
6
import java.util.Base64;
7
import javax.crypto.Cipher;
8
import javax.crypto.NoSuchPaddingException;
9
import javax.crypto.spec.GCMParameterSpec;
10
import javax.crypto.spec.SecretKeySpec;
11
12
public class AesUtil {
13
14
static final int KEY_LENGTH_BYTE = 32;
15
static final int TAG_LENGTH_BIT = 128;
16
private final byte[] aesKey;
17
18
public AesUtil(byte[] key) {
19
if (key.length != KEY_LENGTH_BYTE) {
20
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
21
}
22
this.aesKey = key;
23
}
24
25
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
26
throws GeneralSecurityException, IOException {
27
try {
28
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
29
30
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
31
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
32
33
cipher.init(Cipher.DECRYPT_MODE, key, spec);
34
cipher.updateAAD(associatedData);
35
36
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
37
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
38
throw new IllegalStateException(e);
39
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
40
throw new IllegalArgumentException(e);
41
}
42
}
43
}
Copied!
1
class AesUtil
2
{
3
/**
4
* AES key
5
*
6
* @var string
7
*/
8
private $aesKey;
9
10
const KEY_LENGTH_BYTE = 32;
11
const AUTH_TAG_LENGTH_BYTE = 16;
12
13
/**
14
* Constructor
15
*/
16
public function __construct($aesKey)
17
{
18
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
19
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
20
}
21
$this->aesKey = $aesKey;
22
}
23
24
/**
25
* Decrypt AEAD_AES_256_GCM ciphertext
26
*
27
* @param string $associatedData AES GCM additional authentication data
28
* @param string $nonceStr AES GCM nonce
29
* @param string $ciphertext AES GCM cipher text
30
*
31
* @return string|bool Decrypted string on success or FALSE on failure
32
*/
33
public function decryptToString($associatedData, $nonceStr, $ciphertext)
34
{
35
$ciphertext = \base64_decode($ciphertext);
36
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
37
return false;
38
}
39
40
// ext-sodium (default installed on >= PHP 7.2)
41
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
42
\sodium_crypto_aead_aes256gcm_is_available()) {
43
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
44
}
45
46
// ext-libsodium (need install libsodium-php 1.x via pecl)
47
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
48
\Sodium\crypto_aead_aes256gcm_is_available()) {
49
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
50
}
51
52
// openssl (PHP >= 7.1 support AEAD)
53
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
54
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
55
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
56
57
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
58
$authTag, $associatedData);
59
}
60
61
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
62
}
63
}
Copied!
1
public class AesGcm
2
{
3
private static string ALGORITHM = "AES/GCM/NoPadding";
4
private static int TAG_LENGTH_BIT = 128;
5
private static int NONCE_LENGTH_BYTE = 12;
6
private static string AES_KEY = "yourkeyhere";
7
8
public static string AesGcmDecrypt(string associatedData, string nonce, string ciphertext)
9
{
10
GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
11
AeadParameters aeadParameters = new AeadParameters(
12
new KeyParameter(Encoding.UTF8.GetBytes(AES_KEY)),
13
128,
14
Encoding.UTF8.GetBytes(nonce),
15
Encoding.UTF8.GetBytes(associatedData));
16
gcmBlockCipher.Init(false, aeadParameters);
17
18
byte[] data = Convert.FromBase64String(ciphertext);
19
byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
20
int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
21
gcmBlockCipher.DoFinal(plaintext, length);
22
return Encoding.UTF8.GetString(plaintext);
23
}
24
}
Copied!
1
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
2
import base64
3
4
def decrypt(nonce, ciphertext, associated_data):
5
key = "Your32Apiv3Key"
6
7
key_bytes = str.encode(key)
8
nonce_bytes = str.encode(nonce)
9
ad_bytes = str.encode(associated_data)
10
data = base64.b64decode(ciphertext)
11
12
aesgcm = AESGCM(key_bytes)
13
return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
Copied!
最近更新 3mo ago
复制链接