国密数字信封技术原理与实现
概述
数字信封(Digital Envelope)是一种将对称加密的高效性和非对称加密的密钥分发安全性相结合的密码学技术。它通过非对称加密保护对称密钥,再用对称加密保护实际数据,实现了"用公钥锁住信封(对称密钥),用私钥打开信封取数据"的巧妙设计。
在中国商用密码体系中,数字信封特指使用国密算法(SM2 + SM4)实现的数字信封方案,遵循GM/T 0044.2-2014《基于SM2密码算法的证书认证系统密码及其相关安全技术规范 第2部分:数字信封技术规范》。该技术广泛应用于电子政务、金融支付、医疗健康、物联网等对数据安全有严格要求的场景。
基本原理
1. 为什么需要数字信封
纯对称加密和非对称加密各有优劣:
| 特性 | 对称加密(SM4/AES) | 非对称加密(SM2/RSA) |
|---|---|---|
| 加密速度 | 极快(硬件加速) | 慢(100-1000倍) |
| 密钥管理 | 困难(N个用户需N²个密钥) | 简单(仅需公钥) |
| 密钥长度 | 128/256位 | 256位(SM2)/ 2048+位(RSA) |
| 适用场景 | 大数据量加密 | 小数据量加密、密钥交换 |
| 安全性 | 依赖密钥保密 | 依赖数学难题(离散对数/因子分解) |
2. 工作流程
#### 2.1 加密流程(发送方)
┌─────────────────────────────────────────────────┐
│ 发送方 │
│ │
│ 1. 生成随机对称密钥 K(SM4密钥,128位) │
│ 2. 用接收方公钥 PK_R 加密 K → E_K │
│ 3. 用 K 加密明文 M → C │
│ 4. 发送 (E_K, C) │
└─────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 接收方 │
│ │
│ 1. 用自身私钥 SK_R 解密 E_K → K │
│ 2. 用 K 解密密文 C → M │
│ 3. 得到原始明文 M │
└─────────────────────────────────────────────────┘#### 2.2 数学描述
设:
- $M$:明文消息
- $K$:随机生成的SM4对称密钥(128位)
- $PK_R$:接收方的SM2公钥
- $SK_R$:接收方的SM2私钥
- $Enc_{sm2}(PK_R, \cdot)$:SM2加密运算
- $Dec_{sm2}(SK_R, \cdot)$:SM2解密运算
- $Enc_{sm4}(K, \cdot)$:SM4加密运算(CBC/GCM模式)
- $Dec_{sm4}(K, \cdot)$:SM4解密运算
$$Envelope = (E_K, C) = (Enc_{sm2}(PK_R, K), Enc_{sm4}(K, M))$$
接收方恢复明文:
$$M = Dec_{sm4}(K, C) = Dec_{sm4}(Dec_{sm2}(SK_R, E_K), C)$$
3. SM2数字信封的详细过程
GM/T 0044.2-2014定义了SM2数字信封的具体实现方式。SM2加密采用C1C3C2顺序(非标准ASN.1 DER编码):
SM2加密过程:
1. 随机选择整数 k ∈ [1, n-1](n为SM2曲线阶数)
2. C1 = [k]G (G为生成点,k为随机数,[k]表示标量乘法)
3. [k]PB (PB为接收方公钥,[k]表示标量乘法)
4. (X2, Y2) = [k]PB 的坐标
5. t = KDF(X2 || Y2, klen) (密钥派生函数)
6. C2 = M ⊕ t (明文与派生密钥异或)
7. C3 = Hash(X2 || M || Y2) (SM3哈希)
8. 密文输出:C1 || C3 || C2SM2数字信封中,对称密钥K(128位)作为明文M输入上述加密过程,输出C1||C3||C2形式的密文。
关键点:SM2加密本身是概率性的(每次加密使用不同的随机数k),这意味着相同的对称密钥K使用相同的SM2公钥加密两次,会得到完全不同的密文。这一性质保证了数字信封的语义安全性。
国密数字信封的实现
1. Python实现(gmssl库)
# 环境要求:pip install gmssl>=3.2
# 注意:gmssl 库的 SM2 加密/签名功能存在已知实现缺陷,
# 生产环境请使用 Tongsuo(通联SSL)或 BabaSSL 的 Python 绑定。
from gmssl import sm4, func
import os
# ========================================
# 第一部分:SM4 密钥生成与加密
# ========================================
# 1. 生成随机 SM4 密钥(128 位 = 16 字节)
sm4_key = os.urandom(16)
sm4_key_hex = sm4_key.hex() # 32 位十六进制字符串
print(f"SM4 密钥(hex): {sm4_key_hex}")
# 2. SM4-CBC 加密数据
plaintext = b"Secret data for digital envelope demo"
iv = os.urandom(16) # CBC 模式需要 16 字节 IV
crypt_sm4 = sm4.CryptSM4()
crypt_sm4.set_key(sm4_key, sm4.SM4_ENCRYPT)
ciphertext = crypt_sm4.crypt_cbc(iv, plaintext)
print(f"SM4-CBC 密文(hex): {ciphertext.hex()[:40]}...")
# 3. SM4-CBC 解密验证
crypt_sm4_dec = sm4.CryptSM4()
crypt_sm4_dec.set_key(sm4_key, sm4.SM4_DECRYPT)
decrypted = crypt_sm4_dec.crypt_cbc(iv, ciphertext)
assert decrypted == plaintext, "SM4 解密失败"
print(f"SM4 解密验证: OK")
# ========================================
# 第二部分:SM2 密钥对生成
# ========================================
# gmssl 库不提供 SM2 密钥生成 API,需要通过椭圆曲线标量乘法推导。
# 实际生产中建议使用 openssl 或 Tongsuo 生成 SM2 密钥对。
from gmssl import sm2
def generate_sm2_keypair():
r"""使用 gmssl 库的内部椭圆曲线运算生成 SM2 密钥对"""
ecc = sm2.default_ecc_table
# G 点格式: "Gx_hex || Gy_hex"(各 64 位十六进制)
G_str = ecc['g']
Gx = G_str[:64]
Gy = G_str[64:]
# G 的 Jacobian 坐标形式: (Gx, Gy, 1),每分量 64 位十六进制
G_jacobian = Gx + Gy + "0" * 63 + "1"
# 创建临时实例以使用 _kg 方法(标量乘法)
dummy_priv = "0" * 64
dummy_pub = "04" + "0" * 128
dummy_sm2 = sm2.CryptSM2(public_key=dummy_pub, private_key=dummy_priv)
# 生成随机私有标量
d_hex = func.random_hex(64)
# 计算公钥 Q = d * G
Q_jacobian = dummy_sm2._kg(int(d_hex, 16), G_jacobian)
# _kg 已返回仿射坐标 (x, y) 拼接的 128 位十六进制字符串
qx = Q_jacobian[:64]
qy = Q_jacobian[64:]
pub_hex = "04" + qx + qy
return d_hex, pub_hex
priv_key, pub_key = generate_sm2_keypair()
print(f"SM2 私钥: {priv_key[:32]}...")
print(f"SM2 公钥: {pub_key[:32]}...")
# ========================================
# 第三部分:完整数字信封组装
# ========================================
# 在实际应用中,SM2 加密 SM4 密钥的步骤需要可靠的 SM2 实现库。
# 此处展示数字信封的数据结构:
envelope = {
"sm2_encrypted_sm4_key": "<SM2 加密后的 SM4 密钥(C1||C3||C2 格式)>",
"sm4_cbc_nonce": iv.hex(),
"sm4_cbc_ciphertext": ciphertext.hex(),
}
print(f"数字信封数据结构:")
for k, v in envelope.items():
print(f" {k}: {str(v)[:50]}...")
# ========================================
# 解密流程(伪代码,因 gmssl SM2 实现有缺陷)
# ========================================
### 2. Java实现(Bouncy Castle)public class DigitalEnvelope {
static { Security.addProvider(new BouncyCastleProvider()); }
/** * 数字信封加密 */ public static byte[] encrypt(byte[] data, ECPublicKey publicKey) throws Exception { // 1. 生成随机SM4密钥 KeyGenerator kg = KeyGenerator.getInstance("SM4", "BC"); kg.init(128); SecretKey sm4Key = kg.generateKey(); byte[] sm4KeyBytes = sm4Key.getEncoded();
// 2. SM2加密SM4密钥 SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2); sm2Engine.init(true, publicKey); byte[] encryptedSm4Key = sm2Engine.processBlock(sm4KeyBytes, 0, sm4KeyBytes.length);
// 3. SM4-GCM加密数据 Cipher sm4Cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC"); byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv); GCMParameterSpec paramSpec = new GCMParameterSpec(128, iv); sm4Cipher.init(Cipher.ENCRYPT_MODE, sm4Key, paramSpec); byte[] encryptedData = sm4Cipher.doFinal(data);
// 4. 组装信封 // 格式: [encryptedSm4Key长度(4字节)][encryptedSm4Key][iv(12字节)][encryptedData] ByteBuffer buffer = ByteBuffer.allocate(4 + encryptedSm4Key.length + 12 + encryptedData.length); buffer.putInt(encryptedSm4Key.length); buffer.put(encryptedSm4Key); buffer.put(iv); buffer.put(encryptedData); return buffer.array(); }
/** * 数字信封解密 */ public static byte[] decrypt(byte[] envelope, ECPrivateKey privateKey, ECDomainParameters domain) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(envelope); int keyLen = buffer.getInt(); byte[] encryptedSm4Key = new byte[keyLen]; buffer.get(encryptedSm4Key); byte[] iv = new byte[12]; buffer.get(iv); byte[] encryptedData = new byte[buffer.remaining()]; buffer.get(encryptedData);
// 1. SM2解密SM4密钥 SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2); sm2Engine.init(false, new ECPrivateKeyParameters(privateKey.getD(), domain)); byte[] sm4KeyBytes = sm2Engine.processBlock(encryptedSm4Key, 0, keyLen);
// 2. SM4-GCM解密数据 Cipher sm4Cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC"); GCMParameterSpec paramSpec = new GCMParameterSpec(128, iv); sm4Cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sm4KeyBytes, "SM4"), paramSpec); return sm4Cipher.doFinal(encryptedData); } }
### 3. C/C++实现(Tongsuo/OpenSSL)#include
#define SM4_KEY_LEN 16 #define SM4_IV_LEN 12 #define SM4_GCM_TAG_LEN 16
/** * 数字信封加密 * 输入: plaintext, plaintext_len, public_key * 输出: 动态分配的信封缓冲区(调用者free) */ int digital_envelope_encrypt( const unsigned char *plaintext, size_t plaintext_len, EC_KEY *pub_key, unsigned char **out_envelope, size_t *out_len) { unsigned char sm4_key[SM4_KEY_LEN]; unsigned char sm4_iv[SM4_IV_LEN]; unsigned char encrypted_sm4_key[512]; // SM2密文最大512字节 unsigned char encrypted_data[4096]; unsigned char gcm_tag[SM4_GCM_TAG_LEN];
// 1. 生成随机SM4密钥和IV if (RAND_bytes(sm4_key, SM4_KEY_LEN) != 1 || RAND_bytes(sm4_iv, SM4_IV_LEN) != 1) { return -1; }
// 2. SM2加密SM4密钥 int encrypted_key_len = SM2_encrypt(pub_key, sm4_key, SM4_KEY_LEN, encrypted_sm4_key, sizeof(encrypted_sm4_key)); if (encrypted_key_len <= 0) { return -1; }
// 3. SM4-GCM加密数据 EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); int len = 0, total_len = 0;
EVP_EncryptInit_ex(ctx, EVP_sm4_gcm(), NULL, NULL, NULL); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, SM4_IV_LEN, NULL); EVP_EncryptInit_ex(ctx, NULL, NULL, sm4_key, sm4_iv); EVP_EncryptUpdate(ctx, encrypted_data, &len, plaintext, plaintext_len); total_len = len; EVP_EncryptFinal_ex(ctx, encrypted_data + len, &len); total_len += len; EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SM4_GCM_TAG_LEN, gcm_tag); EVP_CIPHER_CTX_free(ctx);
// 4. 组装信封: [sm2密文长度(2B)][sm2密文][iv(12B)][gcm_tag(16B)][密文] size_t envelope_size = 2 + encrypted_key_len + SM4_IV_LEN + SM4_GCM_TAG_LEN + total_len; *out_envelope = malloc(envelope_size); if (!*out_envelope) return -1;
unsigned char *p = *out_envelope; p[0] = (encrypted_key_len >> 8) & 0xFF; p[1] = encrypted_key_len & 0xFF; p += 2; memcpy(p, encrypted_sm4_key, encrypted_key_len); p += encrypted_key_len; memcpy(p, sm4_iv, SM4_IV_LEN); p += SM4_IV_LEN; memcpy(p, gcm_tag, SM4_GCM_TAG_LEN); p += SM4_GCM_TAG_LEN; memcpy(p, encrypted_data, total_len);
*out_len = envelope_size; return 0; }
## 数字信封 vs 传统加密
### 1. 三种加密方式的对比
| 特性 | 纯对称加密 | 纯非对称加密 | 数字信封(混合) |
|------|-----------|-------------|----------------|
| 密钥数量 | 1个共享密钥 | N对公私钥 | 1次性对称密钥+N对公私钥 |
| 加密速度 | 快 | 慢 | 快(数据)+ 慢(密钥) |
| 密钥分发 | 不安全 | 安全 | 安全 |
| 适用数据量 | 任意 | 有限(受密钥长度限制) | 任意 |
| 安全性 | 依赖密钥保管 | 依赖数学难题 | 依赖SM2+SM4双方安全 |
| 典型应用 | 磁盘加密、数据库加密 | 数字签名、密钥交换 | 邮件加密、文件加密、数据传输 |
### 2. 数字信封的独特优势
1. **性能最优**:大数据量使用SM4-GCM硬件加速,密钥交换使用SM2保证安全
2. **密钥轮换简单**:每次加密生成新的随机SM4密钥,即使SM4密钥泄露也只影响单次通信
3. **前向安全性**:每次使用不同的随机密钥,过去的通信不会被未来泄露的密钥破解
4. **支持多方加密**:同一份数据可以用多个接收方的SM2公钥加密SM4密钥,实现一对多加密
## 应用场景
### 1. 电子病历加密存储
医疗机构使用数字信封保护患者电子病历:不同角色持有各自的SM2私钥,可以独立解密SM4密钥进而访问病历数据。院方密钥用于审计和合规检查。
2. 金融交易数据保护
银行间数据传输使用数字信封:
- 交易报文使用SM4-GCM加密(高性能,支持硬件加速)
- SM4密钥使用收款方SM2公钥加密(确保只有收款方能解密)
- 同时使用发送方SM2私钥签名(不可否认性)
- 使用SM3对信封整体做完整性校验
3. 物联网设备固件升级
IoT设备固件升级时:
- 固件镜像较大(MB级别),不适合直接用SM2加密
- 使用SM4加密固件镜像(速度快)
- 使用设备唯一SM2公钥加密SM4密钥(数字信封)
- 设备收到升级包后,用设备私钥解密SM4密钥,再用SM4密钥解密固件
4. 云存储数据加密
用户在云端存储加密数据时:
- 数据在客户端使用SM4-GCM加密
- SM4密钥使用用户SM2公钥加密形成数字信封
- 云服务商只存储密文和数字信封,无法查看原始数据
- 用户可随时随地用私钥解密访问
安全性分析
1. 数字信封的攻击面
数字信封的安全性依赖于SM2和SM4两个组件的安全性:
攻击类型 影响组件 后果 防护措施 SM2私钥泄露 SM2 所有历史通信可被解密 密钥定期轮换、HSM保护 SM4密钥泄露 SM4 单次通信可被解密 每次加密生成新密钥 随机数重用 SM2 C1重用导致私钥泄露 使用CSPRNG生成k 选择密文攻击 SM2 通过恶意密文获取信息 使用CCA2安全的SM2加密模式 侧信道攻击 SM2/SM4 通过功耗/时间推断密钥 常量时间实现、掩码防护
2. SM2数字信封的CCA2安全性
标准SM2加密(GM/T 0003.2-2012)在随机预言机模型下具有CCA2(选择密文攻击第2类安全)安全性。但实际实现中需注意:
- C1C3C2顺序:GM/T 0044.2规定密文顺序为C1||C3||C2,与ISO/IEC 18033-2的C1||C2||C3不同
- KDF参数:密钥派生函数的输出长度必须等于SM4密钥长度(128位)
- 哈希验证:解密时应先验证C3(SM3哈希),再解密SM4密钥,防止选择密文攻击
3. 密钥管理安全
数字信封中最脆弱的环节往往是密钥管理而非算法本身:
- SM2私钥存储:应使用HSM(密码机)或安全 enclave 存储
- SM4密钥生成:必须使用CSPRNG(如/dev/urandom),不可用伪随机数
- 密钥销毁:内存中的SM4密钥使用后应立即清零(
memset_s或SecureZeroMemory`)
性能基准
| 操作 | SM4-GCM | SM2加密 | 数字信封(总) |
|---|---|---|---|
| 1KB数据 | ~0.01ms | ~2ms | ~2.01ms |
| 10KB数据 | ~0.1ms | ~2ms | ~2.1ms |
| 100KB数据 | ~1ms | ~2ms | ~3ms |
| 1MB数据 | ~10ms | ~2ms | ~12ms |
测试环境:Intel Xeon E5-2680 v4, Tongsuo 1.1.1, SM4-GCM模式关键观察:SM2加密耗时固定(约2ms),与数据量无关;SM4加密耗时与数据量线性相关。对于大数据量,数字信封的性能几乎等同于纯SM4加密,SM2的开销可以忽略不计。
相关实践
- 混合加密SM4数字信封Python实现 — 完整可运行的数字信封代码示例
- SM4数据库字段加密 — 数字信封在数据库加密中的应用
- 国密算法全链路性能基准 — SM2/SM4性能实测数据
参考来源
- GM/T 0044.2-2014《基于SM2密码算法的证书认证系统密码及其相关安全技术规范 第2部分:数字信封技术规范》
- GM/T 0003.2-2012《SM2椭圆曲线公钥密码算法 第2部分:数字加密规范》
- GM/T 0051-2016《SMVP智能密码钥匙密码应用接口规范》
- GB/T 32907-2016《信息安全技术 SM2椭圆曲线公钥密码算法》
- NIST SP 800-56A《Recommendation for Pair-Wise Key-Scheme Establishment Using Integer Factorization Cryptography》
- ISO/IEC 18033-2《Information technology — Security techniques — Encryption algorithms — Part 2: Asymmetric ciphers》