会话密钥解密指引
SUD OP SVR 后台会加密用户的会话密钥 (sessionKey),并通过客户端以 encrypted_session_key 的形式传递给应用服务端(APP SVR)。服务端需先对其进行解密,获取真实的 sessionKey,随后方可使用该真实密钥对用户数据进行签名和加密。
1.1 前置准备
在开始集成前,请确保完成以下准备工作:
- 配置内容加密密钥:需在 SUD OpenPaaS 管理后台完成对称密钥的配置。当前支持两种加密算法:
- AES-256-GCM:密钥为 32 字节(256 位),配置为 Base64 编码。
- SM4-GCM:密钥为 16 字节(128 位),配置为 Base64 编码。
请根据所选算法配置相应类型的密钥,并妥善保管。
1.2 解密与校验流程
1. 对数据进行Base64解码
将获取到的 encrypted_session_key(Base64 字符串)解码为二进制数据 (byte array / Buffer)。
2. 拆分数据
解码后的二进制密文数据结构固定为以下拼接格式:
text
+----------------+-------------------+-------------------+
| IV (12 字节) | 密文 (变长) | 认证标签 (16 字节) |
+----------------+-------------------+-------------------+IV:初始化向量,12 字节
密文:加密后的载荷数据,长度可变
认证标签:GCM 模式的认证信息,16 字节
3. 解密与认证
根据所使用的算法(AES-256-GCM 或 SM4-GCM),使用相同的密钥、IV、认证标签对密文进行解密。
解密成功时,返回原始 JSON 字符串载荷。
解密失败时,说明数据无效或被篡改。
4. 校验载荷内容
解密后的 JSON 字符串,并包含以下字段:
json
{"user_id":"xxx","session_key":"xxx"}| 参数名 | 必选 | 类型 | 说明 |
|---|---|---|---|
| user_id | 是 | string | 用户的唯一标识 ID |
| session_key | 是 | string | 会话密钥 |
参数校验规则:
user_id校验用户是否相同;
1.3 示例代码
通用说明
- 示例代码均基于「Base64 编码的对称密钥」开发;
- 示例仅包含核心加密逻辑,生产环境需补充日志、异常兜底等工程化处理;
- 对于 SM4-GCM 算法,部分编程语言可能需要额外引入第三方库(如 Java 需 BouncyCastle,Go 需 tjfoc/gmsm,Python 需 gmssl,Node.js 需 sm-crypto 等),示例中会注明依赖。
Java
AES-256-GCM 示例
javaimport javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; public class AESGCMDecryptUtil { private static final int IV_SIZE_BYTES = 12; private static final int TAG_SIZE_BITS = 128; public static String decrypt(String encryptedData, String base64Key) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64Key); if (keyBytes.length != 32) { throw new IllegalArgumentException("AES-256 密钥长度必须为 32 字节"); } SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); byte[] data = Base64.getDecoder().decode(encryptedData); if (data.length < IV_SIZE_BYTES + TAG_SIZE_BITS / 8) { throw new IllegalArgumentException("数据长度不足,无法拆分 IV 和标签"); } byte[] iv = new byte[IV_SIZE_BYTES]; System.arraycopy(data, 0, iv, 0, IV_SIZE_BYTES); byte[] ciphertextWithTag = new byte[data.length - IV_SIZE_BYTES]; System.arraycopy(data, IV_SIZE_BYTES, ciphertextWithTag, 0, ciphertextWithTag.length); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_SIZE_BITS, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec); byte[] plaintext = cipher.doFinal(ciphertextWithTag); return new String(plaintext, StandardCharsets.UTF_8); } public static void main(String[] args) throws Exception { String encryptedData = "Base64编码的内容"; String base64Key = "你的Base64编码的32字节AES密钥"; String payload = decrypt(encryptedData, base64Key); System.out.println("解密载荷:" + payload); } }SM4-GCM 示例(需 BouncyCastle 库)
javaimport org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.Security; import java.util.Base64; public class SM4GCMDecryptUtil { static { Security.addProvider(new BouncyCastleProvider()); } private static final int IV_SIZE_BYTES = 12; private static final int TAG_SIZE_BITS = 128; public static String decrypt(String encryptedData, String base64Key) throws Exception { byte[] keyBytes = Base64.getDecoder().decode(base64Key); if (keyBytes.length != 16) { throw new IllegalArgumentException("SM4 密钥长度必须为 16 字节"); } SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "SM4"); byte[] data = Base64.getDecoder().decode(encryptedData); if (data.length < IV_SIZE_BYTES + TAG_SIZE_BITS / 8) { throw new IllegalArgumentException("数据长度不足,无法拆分 IV 和标签"); } byte[] iv = new byte[IV_SIZE_BYTES]; System.arraycopy(data, 0, iv, 0, IV_SIZE_BYTES); byte[] ciphertextWithTag = new byte[data.length - IV_SIZE_BYTES]; System.arraycopy(data, IV_SIZE_BYTES, ciphertextWithTag, 0, ciphertextWithTag.length); Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC"); GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_SIZE_BITS, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec); byte[] plaintext = cipher.doFinal(ciphertextWithTag); return new String(plaintext, StandardCharsets.UTF_8); } }
Go
AES-256-GCM 示例
gopackage main import ( "crypto/aes" "crypto/cipher" "encoding/base64" "fmt" ) func DecryptAESGCM(encryptedData, base64Key string) (string, error) { key, err := base64.StdEncoding.DecodeString(base64Key) if err != nil { return "", fmt.Errorf("密钥解码失败: %w", err) } if len(key) != 32 { return "", fmt.Errorf("AES-256 密钥长度必须为 32 字节") } data, err := base64.StdEncoding.DecodeString(encryptedData) if err != nil { return "", fmt.Errorf("签名解码失败: %w", err) } if len(data) < 12+16 { return "", fmt.Errorf("数据长度不足") } iv := data[:12] ciphertextWithTag := data[12:] block, err := aes.NewCipher(key) if err != nil { return "", fmt.Errorf("创建 AES 密码块失败: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("创建 GCM 失败: %w", err) } plaintext, err := gcm.Open(nil, iv, ciphertextWithTag, nil) if err != nil { return "", fmt.Errorf("解密失败: %w", err) } return string(plaintext), nil }SM4-GCM 示例(需 tjfoc/gmsm 库)
依赖:
go get -u github.com/tjfoc/gmsmgoimport ( "crypto/cipher" "encoding/base64" "fmt" "github.com/tjfoc/gmsm/sm4" ) func DecryptSM4GCM(encryptedData, base64Key string) (string, error) { key, err := base64.StdEncoding.DecodeString(base64Key) if err != nil { return "", fmt.Errorf("密钥解码失败: %w", err) } if len(key) != 16 { return "", fmt.Errorf("SM4 密钥长度必须为 16 字节") } data, err := base64.StdEncoding.DecodeString(encryptedData) if err != nil { return "", fmt.Errorf("签名解码失败: %w", err) } if len(data) < 12+16 { return "", fmt.Errorf("数据长度不足") } iv := data[:12] ciphertextWithTag := data[12:] block, err := sm4.NewCipher(key) if err != nil { return "", fmt.Errorf("创建 SM4 密码块失败: %w", err) } gcm, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("创建 GCM 失败: %w", err) } plaintext, err := gcm.Open(nil, iv, ciphertextWithTag, nil) if err != nil { return "", fmt.Errorf("解密失败: %w", err) } return string(plaintext), nil }
Python
AES-256-GCM 示例
依赖:
pip install cryptographypythonimport base64 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend def decrypt_aes_gcm(encrypted_data: str, base64_key: str) -> str: key = base64.b64decode(base64_key) if len(key) != 32: raise ValueError("AES-256 密钥长度必须为 32 字节") data = base64.b64decode(encrypted_data) iv = data[:12] ciphertext_with_tag = data[12:] cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag=ciphertext_with_tag[-16:]), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(ciphertext_with_tag[:-16]) + decryptor.finalize() return plaintext.decode('utf-8')SM4-GCM 示例(需 gmssl 库)
依赖:
pip install gmsslpythonimport base64 from gmssl.sm4 import CryptSM4, SM4_GCM def decrypt_sm4_gcm(encrypted_data: str, base64_key: str) -> str: key = base64.b64decode(base64_key) if len(key) != 16: raise ValueError("SM4 密钥长度必须为 16 字节") data = base64.b64decode(encrypted_data) iv = data[:12] ciphertext = data[12:-16] tag = data[-16:] crypt = CryptSM4() crypt.set_key(key, SM4_GCM) plaintext = crypt.decrypt_gcm(ciphertext, iv, tag) return plaintext.decode('utf-8')
PHP
AES-256-GCM 示例
phpfunction decryptAESGCM(string $encryptedData, string $base64Key): string { $key = base64_decode($base64Key, true); if ($key === false || strlen($key) !== 32) { throw new Exception("AES-256 密钥长度必须为 32 字节"); } $data = base64_decode($encryptedData, true); if ($data === false) { throw new Exception("签名解码失败"); } $iv = substr($data, 0, 12); $ciphertextWithTag = substr($data, 12); $tag = substr($ciphertextWithTag, -16); $ciphertext = substr($ciphertextWithTag, 0, -16); $plaintext = openssl_decrypt( $ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag ); if ($plaintext === false) { throw new Exception("解密失败: " . openssl_error_string()); } return $plaintext; }SM4-GCM 示例(需第三方扩展,如 php-sm4 或 openssl 的 SM4 支持)
phpfunction decryptSM4GCM(string $encryptedData, string $base64Key): string { // 1. 解码密钥 $key = base64_decode($base64Key, true); if ($key === false || strlen($key) !== 16) { throw new Exception("SM4 密钥必须是16字节的Base64字符串"); } // 2. 解码签名数据 $data = base64_decode($encryptedData, true); if ($data === false) { throw new Exception("签名Base64解码失败"); } if (strlen($data) < 12 + 16) { throw new Exception("签名数据长度不足,无法拆分IV和标签"); } // 3. 拆分 IV、密文、标签 $iv = substr($data, 0, 12); // 12字节IV $ciphertextWithTag = substr($data, 12); // 剩余部分 = 密文 + 标签 $tag = substr($ciphertextWithTag, -16); // 最后16字节是标签 $ciphertext = substr($ciphertextWithTag, 0, -16); // 前面的部分为密文 // 4. 创建SM4实例并解密 $sm4 = new \Sm4\Sm4(); $sm4->setKey($key); // php-sm4 扩展的 GCM 解密方法:decryptGcm($ciphertext, $iv, $tag, $aad = '') $plaintext = $sm4->decryptGcm($ciphertext, $iv, $tag); if ($plaintext === false) { throw new Exception("SM4-GCM解密失败,可能密钥错误或数据被篡改"); } return $plaintext; }
Node.js
AES-256-GCM 示例
javascriptconst crypto = require('crypto'); function decryptAESGCM(encryptedData, base64Key) { const key = Buffer.from(base64Key, 'base64'); if (key.length !== 32) throw new Error('AES-256 密钥长度必须为 32 字节'); const data = Buffer.from(encryptedData, 'base64'); const iv = data.subarray(0, 12); const ciphertext = data.subarray(12, -16); const tag = data.subarray(-16); const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(tag); const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return plaintext.toString('utf8'); }SM4-GCM 示例(需 sm-crypto 库)
依赖:
npm install sm-cryptojavascriptconst sm4 = require('sm-crypto').sm4; const crypto = require('crypto'); // 仅用于随机IV,解密无需 function decryptSM4GCM(encryptedData, base64Key) { // 1. 解码密钥 const key = Buffer.from(base64Key, 'base64'); if (key.length !== 16) { throw new Error('SM4 密钥必须是16字节的Base64字符串'); } // 2. 解码签名数据 const data = Buffer.from(encryptedData, 'base64'); if (data.length < 12 + 16) { throw new Error('签名数据长度不足,无法拆分IV和标签'); } // 3. 拆分 IV、密文、标签 const iv = data.subarray(0, 12); // 12字节IV const ciphertextWithTag = data.subarray(12); // 剩余部分 = 密文 + 标签 const tag = ciphertextWithTag.subarray(-16); // 最后16字节是标签 const ciphertext = ciphertextWithTag.subarray(0, -16); // 前面的部分为密文 // 4. 调用 sm-crypto 的 GCM 解密 // 注意:sm4.gcm.decrypt 需要传入 hex 字符串或 Buffer?根据文档,支持 Buffer/ArrayBuffer // 这里直接传入 Buffer,返回 Buffer 或 string(需查看实际返回) // 确保参数顺序:sm4.gcm.decrypt(ciphertext, key, iv, tag, aad?) const plaintext = sm4.gcm.decrypt(ciphertext, key, iv, tag); // 如果返回的是 Buffer,转换为字符串 return Buffer.isBuffer(plaintext) ? plaintext.toString('utf8') : plaintext; }
1.4 常见问题排查
解密失败常见原因
- 密钥不匹配:解密使用的密钥与加密时不一致,或算法类型错误。
- 数据格式错误:Base64 解码失败,或数据长度不足 12+16 字节。
- 认证标签错误:数据被篡改、标签提取位置错误或 GCM 认证失败。
- IV 长度不一致:未按 12 字节拆分。
- 跨语言兼容性问题:拼接顺序必须严格为 IV + 密文 + 标签。