第三章:密钥生命周期管理——生成、分发、使用、存储、销毁
密钥这东西,说白了一辈子就干五件事:生下来、传出去、用起来、存起来、最后销毁。哪一步没做好,整个加密体系就可能白搭。我最早做SM4商用项目时,光顾着算法强度和性能,结果在密钥存储上栽过跟头——嗯,后面细说。
3.1 密钥生成:不是你想象的随机就够
生成一个SM4会话密钥,关键就两个点:随机源质量和密钥空间。SM4密钥固定128位,看上去不长,但如果随机源被污染,比如用了伪随机数生成器(PRNG)且种子可预测,那攻击者就能提前算出所有会话密钥。
我个人习惯在硬件安全模块(HSM)里生成密钥,实在没有HSM,也必须用操作系统提供的硬件真随机数生成器(比如Intel的RDRAND)。我曾在一个嵌入式设备项目中,发现开发人员直接用 rand() 生成密钥……结果你知道的,所有设备密钥都相同。那次教训之后,我们加了SP800-90A CTR_DRBG做熵源混合。
代码示例(伪代码,展示核心逻辑):
// 生成SM4会话密钥的推荐流程
entropy_source = hw_rng_gather(256); // 硬件真随机256位
seed = hkdf_extract(entropy_source, salt);
sk = hkdf_expand(seed, "SM4-session-key", 128);
return sk; // 128位SM4密钥
3.2 密钥分发:最容易被忽视的“中间人”环节
密钥生好了,怎么安全地交给对端?直接明文发过去?那等于没加密。你想想看,密钥分发阶段的核心矛盾是:加密数据需要密钥,传输密钥又需要加密通道——典型的“先有鸡还是先有蛋”。
解决方案通常有两种:
- 非对称加密封装:用接收方的公钥(如ECC密钥)加密会话密钥。SM2非对称加密正好干这事。
- Diffie-Hellman密钥协商:双方通过DH交换临时公钥,各自算出共享秘密,再用KDF导出会话密钥。SM4+SM2混合方案里常用ECDH。
注意:分发时必须附带身份认证,否则中间人劫持后替换公钥,就能窃听所有会话。我在维护一个旧系统时,发现对方只用了静态DH(没有签名),结果安全审计直接亮了红灯。后来我们强制要求在密钥交换报文里加SM2签名。
| 方案 | 安全性 | 性能 | 适用场景 |
|---|---|---|---|
| SM2公钥加密传输 | 高(需PKI信任根) | 中等(一次非对称运算) | 点对点、有证书体系 |
| ECDH密钥协商 | 高(前向安全性) | 中等(两次点乘) | 实时通信、无CA场景 |
| PSK预共享密钥 | 取决于部署 | 最快(无需计算) | 设备出厂预置、物联网 |
3.3 密钥使用:会话期间的安全边界
密钥交到使用时,最容易出的问题就是越界使用。比如一个会话密钥本用于SM4-CTR加密,结果被拿去同时做MAC计算,这就违反密钥用途分离原则。我在一个金融终端项目里见过,工程师把加密密钥又当签名密钥用,后来被合规检查发现,整批设备必须重新灌装密钥。
使用阶段的核心规则:
- 一个密钥只用于一个目的:加密、认证、密钥派生要分开。
- 密钥不暴露给应用层:加密操作应在HSM、TEE或内核加密框架内完成。
- 记录使用次数:每次加密/解密都要计数器递增。如果连续解密失败超出阈值,立即触发密钥边界刷新。
3.4 密钥存储:持久化就等于风险
会话密钥理论上只在会话期间有效,但很多系统为了重连效率,会把会话密钥缓存到磁盘或数据库里。一旦持久化,就变成了静态存储密钥,需要额外的保护层。
我建议的存储策略:
- 内存驻留:会话密钥只存在于加密驱动的内存中,不落地。
- 若必须存储:使用密钥加密密钥(KEK)加密后保存。KEK由HSM或者硬件绑定密钥保护。
- 存储介质最小化:只存储密钥的句柄(handle)或密文,不存明文。
举个例子,TLS 1.3的会话票据:服务器把会话密钥用主密钥加密后发给客户端存着,下次重用。这就是典型的“存储即加密后存储”模式。
3.5 密钥销毁:最后一步,也是“回不了头”的一步
会话结束或轮换后,旧密钥必须被彻底清除。什么是彻底?覆盖内存、清除cache、刷掉寄存器的所有副本。直接free()指针是不够的——C语言里被释放的内存还在那里,攻击者能通过core dump读走。
我常用的销毁步骤:
// SM4会话密钥安全销毁(伪代码)
void secure_wipe(uint8_t* key, size_t len) {
// 1. 用平台随机值覆盖
hw_fill_random(key, len);
// 2. 再用0x00覆盖
memset(key, 0x00, len);
// 3. 最后用0xFF覆盖
memset(key, 0xFF, len);
// 4. 内存屏障,防止编译器优化
asm volatile("" : : "r"(key) : "memory");
// 5. 释放内存(可选)
free(key);
key = NULL;
}
注意:硬件安全模块通常会自动在会话结束后清除内部密钥,但应用层调用的密钥句柄也要及时失效。我曾经在代码review时发现有人把密钥句柄当作普通整数传递,结果句柄泄露后攻击者仍能通过HSM调用解密——因为HSM只认识句柄,不认谁在用。
– 使用显式零化函数(SecureZeroMemory / OPENSSL_cleanse)
– 禁用核心转储(setrlimit RLIMIT_CORE)
– 多线程环境加锁确保所有线程副本都被清除
3.6 生命周期总览图
这张图把五个阶段串成一个闭环,你想想看,每一环都依赖前一环的安全假设。如果一个密钥生成熵不够,后面再强的分发方案也是白搭。同理,销毁不彻底,攻击者能从内存残留恢复会话密钥。