CN - Decryption for Bulk Create Virtual Cards Number File
🔐 AES-256-GCM 加解密流程文档
本方案使用 AES-256-GCM 算法,密钥长度 32 字节(256 bit)。
所有密钥、数据均使用 Base64 编码 传输。
aad=nil
(无附加认证数据)。
核心概念
API Key
每个用户拥有一个唯一的 API Key
(任意长度字符串,例如 64 位)。
作用:作为根密钥,用户可用它来加解密服务端发给他的“批次密钥”。
批次密钥 (Batch Key)
服务端为每一批次业务生成的随机 32 字节二进制密钥。 作用:用于加解密实际业务数据。
返给用户的秘钥(Secret Key)
批次密钥的生成与分发
-
服务器生成一个随机 批次密钥 (BatchKey, 32 字节)
- 用于加密业务数据。
-
服务器使用 API Key 做 SHA-256 (32字节) 作为加密BatchKey的秘钥 来加密 BatchKey,并返回给用户SecretKey:
secretKey = Base64(Nonce || Ciphertext || AuthTag)
- 用户收到SecretKey后,使用同一 API Key 解密,得到原始 BatchKey。
业务数据加密
用户或服务器都可以使用 BatchKey 加密业务数据。
加密步骤:
-
生成随机 Nonce (12字节)。
-
调用 AES-256-GCM 加密:
Ciphertext, AuthTag = AES-GCM-Encrypt(BatchKey, Plaintext, Nonce, AAD=nil)
- 拼接数据:
Output = Nonce || Ciphertext || AuthTag
- 将
Output
用 Base64 编码,得到最终的密文字符串。
返回给用户的数据:
EncData = Base64(Nonce || Ciphertext || AuthTag)
业务数据解密
用户拿到 EncData
后:
- 用 Base64 解码,得到原始字节串:
Raw = Nonce || Ciphertext || AuthTag
-
分割字段:
-
Nonce
= 前 12 字节 -
AuthTag
= 最后 16 字节 -
Ciphertext
= 中间部分
-
-
调用 AES-256-GCM 解密:
Plaintext = AES-GCM-Decrypt(BatchKey, Ciphertext, Nonce, AuthTag, AAD=nil)
- 得到原始明文数据。
数据格式示意
Nonce | Ciphertext | AuthTag |
---|---|---|
12字节 | N字节 | 16字节 |
最终传输时:
EncData = Base64(Nonce || Ciphertext || AuthTag)
用户侧解密流程
用户收到 SecretKey EncryptedBatchKey(Base64)
和 Card NumberEncryptedData(Base64)
,解密流程如下:
Step 1. 从 API Key 派生 AES Key
APIKeyAESKey = SHA256(APIKey) // 结果是 32 字节
Step 2. 解密批次密钥
BatchKey = AES-GCM-Decrypt(APIKeyAESKey, SecretKey, Nonce, AuthTag, AAD=nil)
Step 3. 解密卡号
CardNumber = AES-GCM-Decrypt(BatchKey, EncryptedCardNumber, Nonce, AuthTag, AAD=nil)
代码示例
import hashlib, base64
from Crypto.Cipher import AES
def derive(api_key: str) -> bytes:
return hashlib.sha256(api_key.encode()).digest() # 32 bytes
def decrypt_gcm(key: bytes, enc_b64: str, aad: bytes = None) -> bytes:
raw = base64.b64decode(enc_b64)
nonce, ct_tag = raw[:12], raw[12:]
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce, mac_len=16)
if aad:
cipher.update(aad)
return cipher.decrypt_and_verify(ct_tag[:-16], ct_tag[-16:])
# 批次密钥解包
api_key = "your-api-key..."
api_key_ase = derive(api_key)
batch_key = decrypt_gcm(api_key_ase, secret_key) # []byte
# 业务数据解密
plain = decrypt_gcm(batch_key, enc_data_b64)
print(plain.decode())
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"errors"
)
const (
gcmNonceSize = 12
gcmTagSize = 16
)
func deriveKeyFromAPIKey(apiKey string) []byte {
h := sha256.Sum256([]byte(apiKey))
return h[:] // 32 bytes
}
func DecryptGCM(key []byte, encBase64 string, aad []byte) ([]byte, error) {
raw, err := base64.StdEncoding.DecodeString(encBase64)
if err != nil {
return nil, err
}
if len(raw) < gcmNonceSize+gcmTagSize {
return nil, errors.New("ciphertext too short")
}
nonce := raw[:gcmNonceSize]
cipherAndTag := raw[gcmNonceSize:]
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCMWithTagSize(block, gcmTagSize)
if err != nil {
return nil, err
}
plain, err := gcm.Open(nil, nonce, cipherAndTag, aad)
if err != nil {
return nil, err
}
return plain, nil
}
func main() {
apiKey := "xxx"
secretKey := "1111"
encodeData := "qweqweqwe"
aesApiKey := deriveKeyFromAPIKey(apiKey)
batchKey,_ := DecryptGCM(aesApiKey,secretKey,nil)
cardNum,_ := DecryptGCM(batchKey,encodeData,nil)
}
注意事项
-
密钥长度必须是 32 字节(AES-256)。
-
Nonce 必须唯一,同一个 BatchKey 不能重复使用相同 Nonce,否则会导致严重安全问题。
-
AuthTag
由 AES-GCM 自动生成,解密时必须验证。 -
本方案中 不使用 AAD(传
nil
),用户无需关心。
Updated 2 days ago