2、对称密钥生成:AES、DES/3DES、SM4
对称密钥生成,说白了就是给加密算法“造一把锁”。锁的质量好不好,直接决定了数据安全的下限。我这些年做安全审计,见过太多因为密钥生成不规范导致整个系统形同虚设的案例。今天咱们就把 AES、DES/3DES、SM4 这三种主流对称算法的密钥生成讲透。
2.1 AES 密钥生成
AES 是目前应用最广的对称加密算法。它的密钥长度有三种:128 位、192 位、256 位。我个人习惯,只要性能允许,一律用 256 位。为什么?多出来的 128 位,换来的安全边际非常可观。
核心原则:AES 密钥必须是均匀分布的随机字节序列。任何可预测的模式都会降低有效密钥空间。
2.1.1 随机数源的选择
生成 AES 密钥,第一步是选对随机数生成器。我见过有人直接用 rand() 生成密钥,那基本等于没锁门。正确的做法是:
- Linux/Unix 环境:从
/dev/urandom读取,或者使用getrandom()系统调用。 - Windows 环境:调用
CryptGenRandom()或BCryptGenRandom()。 - 跨平台库:OpenSSL 的
RAND_bytes(),或者 libsodium 的randombytes_buf()。
注意:不要使用 /dev/random 生成大量密钥。它会在熵池不足时阻塞,导致你的服务 hang 住。我曾经排查过一个线上故障,最后发现是密钥生成线程被 /dev/random 卡死了。
2.1.2 代码示例:生成 AES-256 密钥
#include <openssl/rand.h>
#include <stdint.h>
int generate_aes256_key(uint8_t *key_out, size_t len) {
// len 必须等于 32(256 位)
if (len != 32) return -1;
// 使用 OpenSSL 的 CSPRNG
if (RAND_bytes(key_out, len) != 1) {
// 处理错误:随机数生成失败
return -1;
}
return 0;
}
嗯,这里要注意:RAND_bytes() 返回 1 表示成功,0 表示失败。别搞反了,我刚开始写的时候也犯过这个错。
2.1.3 密钥强度验证
生成完密钥,我建议做一次简单的“冒烟测试”:
- 检查密钥是否全零。全零密钥说明随机数生成器可能没初始化。
- 检查密钥的字节分布是否均匀。可以用卡方检验,但生产环境一般不做,太耗时。
- 确认密钥长度正确。AES-128 是 16 字节,AES-192 是 24 字节,AES-256 是 32 字节。
2.2 DES/3DES 密钥生成
说实话,DES 现在基本可以进博物馆了。它的 56 位有效密钥长度,用现代硬件暴力破解只需要几个小时。但很多遗留系统还在用,咱们还是得会处理。
安全警告:DES 已被 NIST 正式废弃。新系统绝对不要用。3DES 虽然还没完全退役,但 NIST 也计划在 2023 年后逐步淘汰。能用 AES 就别碰 3DES。
2.2.1 DES 密钥的奇偶校验位
DES 密钥有个坑:每个字节的最低有效位是奇偶校验位,不参与加密。也就是说,你生成的 8 字节密钥,实际有效位只有 56 位。我曾经在项目里看到有人直接拿用户输入的密码当 DES 密钥,结果因为奇偶校验不对,加解密死活对不上。
正确的做法是:生成 8 个随机字节,然后调整每个字节的奇偶位。
void adjust_des_parity(uint8_t *key, size_t len) {
for (size_t i = 0; i < len; i++) {
// 计算当前字节中 1 的个数
int bits = 0;
uint8_t b = key[i];
while (b) {
bits += b & 1;
b >>= 1;
}
// 如果 1 的个数是偶数,把最低位设为 1
if ((bits & 1) == 0) {
key[i] ^= 1;
}
}
}
2.2.2 3DES 密钥的三种模式
3DES 有三种密钥选项,很多人搞混:
| 模式 | 密钥长度 | 有效安全强度 | 说明 |
|---|---|---|---|
| TDEA Keying Option 1 | 24 字节(3 个 8 字节密钥) | 112 位 | 三个独立密钥,安全性最高 |
| TDEA Keying Option 2 | 16 字节(2 个 8 字节密钥) | 80 位 | K1 和 K2 独立,K3 = K1 |
| TDEA Keying Option 3 | 8 字节(3 个密钥相同) | 56 位 | 等同于 DES,毫无意义 |
你想想看,Option 3 其实就是 DES,用 3DES 的壳跑 DES 的逻辑。我见过一些老旧金融系统还在用 Option 2,说实话,80 位的安全强度在今天已经岌岌可危了。
2.3 SM4 密钥生成
SM4 是中国国家密码管理局发布的对称加密算法,也是国密标准的一部分。它的密钥长度固定为 128 位(16 字节)。
2.3.1 SM4 密钥的特殊要求
SM4 的密钥生成流程和 AES 类似,但有一个关键区别:SM4 的密钥扩展算法对某些“弱密钥”比较敏感。虽然 SM4 标准没有像 DES 那样明确列出弱密钥列表,但我在实践中发现,全零密钥和全一密钥会导致加密结果出现可预测的模式。
我的建议:生成 SM4 密钥后,额外检查一下密钥是否全零、全一,或者所有字节都相同。虽然概率极低,但多一道检查总没坏处。
2.3.2 使用 GMSSL 生成 SM4 密钥
#include <gmssl/sm4.h>
#include <gmssl/rand.h>
int generate_sm4_key(uint8_t *key_out) {
// SM4 密钥固定 16 字节
if (gmssl_rand_bytes(key_out, 16) != 1) {
return -1;
}
// 可选:检查弱密钥
uint8_t weak_patterns[][16] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}
};
for (int i = 0; i < 2; i++) {
if (memcmp(key_out, weak_patterns[i], 16) == 0) {
// 重新生成
return generate_sm4_key(key_out);
}
}
return 0;
}
2.4 三种算法密钥生成对比
为了方便你快速查阅,我把三种算法的密钥生成要点整理成了一张表:
| 算法 | 密钥长度 | 随机数要求 | 特殊处理 | 推荐状态 |
|---|---|---|---|---|
| AES | 16/24/32 字节 | CSPRNG | 无 | 强烈推荐 |
| DES | 8 字节(有效 56 位) | CSPRNG | 奇偶校验位调整 | 已废弃 |
| 3DES | 16/24 字节 | CSPRNG | 奇偶校验位调整 | 逐步淘汰 |
| SM4 | 16 字节 | CSPRNG | 建议检查弱密钥 | 国密推荐 |
2.5 密钥生成的核心流程
下面这张图展示了对称密钥生成的通用流程,不管用哪种算法,骨架都是一样的:
2.6 避坑指南
最后,分享几个我踩过的坑:
- 不要用时间戳做种子。 我曾经接手过一个项目,密钥生成用的是
srand(time(NULL))+rand()。同一秒内生成的所有密钥一模一样。攻击者只要知道大致时间范围,就能穷举出所有密钥。 - 生成后不要打印密钥。 日志里打印密钥是安全大忌。我见过有人为了方便调试,把密钥 hex 打到了日志里,结果日志文件泄露,整个系统沦陷。
- 密钥生成和密钥使用要分离。 生成密钥的模块和使用密钥的模块,最好由不同的团队维护。这样可以避免单点故障。
- 定期轮换密钥。 密钥不是生成一次用一辈子。AES 密钥建议每 1-2 年轮换一次,SM4 同样。3DES 如果还在用,建议每半年轮换。