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)


批次密钥的生成与分发

  1. 服务器生成一个随机 批次密钥 (BatchKey, 32 字节)

    • 用于加密业务数据。
  2. 服务器使用 API Key 做 SHA-256 (32字节) 作为加密BatchKey的秘钥 来加密 BatchKey,并返回给用户SecretKey

secretKey = Base64(Nonce || Ciphertext || AuthTag)

  • 用户收到SecretKey后,使用同一 API Key 解密,得到原始 BatchKey

业务数据加密

用户或服务器都可以使用 BatchKey 加密业务数据。

加密步骤:

  1. 生成随机 Nonce (12字节)

  2. 调用 AES-256-GCM 加密:

Ciphertext, AuthTag = AES-GCM-Encrypt(BatchKey, Plaintext, Nonce, AAD=nil)

  • 拼接数据:

Output = Nonce || Ciphertext || AuthTag

  • OutputBase64 编码,得到最终的密文字符串。

返回给用户的数据:

EncData = Base64(Nonce || Ciphertext || AuthTag)


业务数据解密

用户拿到 EncData 后:

  1. 用 Base64 解码,得到原始字节串:

Raw = Nonce || Ciphertext || AuthTag

  • 分割字段:

    • Nonce = 前 12 字节

    • AuthTag = 最后 16 字节

    • Ciphertext = 中间部分

  • 调用 AES-256-GCM 解密:

Plaintext = AES-GCM-Decrypt(BatchKey, Ciphertext, Nonce, AuthTag, AAD=nil)

  • 得到原始明文数据。

数据格式示意

NonceCiphertextAuthTag
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),用户无需关心。