3. SM4密钥生成漏洞:弱密钥、固定IV、随机数种子复用

SM4作为国密标准中的对称分组密码算法,本身设计是安全的。但我在实际项目里见过太多「算法没问题,实现全是洞」的案例了。密钥生成这一环,恰恰是攻击者最喜欢下手的地方。

说白了,SM4的密钥长度是128位,暴力破解不现实。但如果你用了弱密钥、固定IV、或者随机数种子复用,那攻击者根本不需要暴力破解——你等于把门敞开了。

3.1 弱密钥问题:你以为随机了,其实没有

SM4的密钥调度算法会把128位主密钥扩展成32个轮密钥。如果主密钥本身熵不够,或者密钥扩展后出现规律性,那整个加密就形同虚设。

常见的弱密钥场景:

  • 全零密钥:0x00000000000000000000000000000000。我在一次金融终端对接时,发现对方测试环境居然用了全零密钥上线——还好是内网,不然数据等于明文。
  • 全F密钥:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF。某些硬件实现里,全F密钥会导致轮密钥出现对称性。
  • ASCII字符串直接填充:比如把"1234567890abcdef"直接当密钥用。这种密钥的字节分布极不均匀,熵值不到64位。
  • 时间戳截断:用毫秒时间戳的低32位重复4次拼成128位。攻击者只要知道大致时间窗口,就能穷举。
⚠️ 避坑指南
我曾经在一个物联网项目中,发现设备端用芯片唯一ID的低64位重复两次作为SM4密钥。看似随机,但同批次芯片的ID只有后32位不同——相当于所有设备的密钥空间只有2^32。攻击者拿到一台设备,就能推导出同批次其他设备的密钥。

3.2 固定IV:CBC模式下的灾难

SM4常用的工作模式是CBC。CBC模式的安全性高度依赖IV的不可预测性。如果IV固定,会带来两个致命问题:

  1. 相同明文产生相同密文:攻击者可以识别出重复的加密数据块。比如加密"登录成功"和"登录失败",固定IV下密文模式固定,攻击者一眼就能看出状态。
  2. IV预测攻击:如果IV是固定的,攻击者可以构造特殊明文来逐步还原密钥。这在SSL/TLS的BEAST攻击里已经演示得很清楚了。

我见过最离谱的一次:某政务系统用SM4-CBC加密数据库中的身份证号,IV写死在配置文件里,而且所有表共用同一个IV。这意味着相同身份证号的密文完全一样——加密了个寂寞。

✅ 正确做法
IV必须每次加密随机生成,且长度固定为16字节(128位)。IV不需要保密,可以随密文一起传输。推荐做法:
iv = os.urandom(16) 然后拼在密文前面,解密时先取前16字节作为IV。

3.3 随机数种子复用:最隐蔽的漏洞

这个坑比前两个更隐蔽。很多开发者知道要用随机IV、随机密钥,但随机数的生成方式错了。

典型错误代码:

// 错误示例:使用固定种子
srand(10086);  // 固定种子
uint8_t key[16];
for (int i = 0; i < 16; i++) {
    key[i] = rand() % 256;
}

// 错误示例:使用时间种子且快速调用
srand(time(NULL));
uint8_t iv[16];
for (int i = 0; i < 16; i++) {
    iv[i] = rand() % 256;
}
// 如果两次调用间隔小于1秒,time(NULL)返回相同值,IV完全一样

为什么会这样?因为rand()是伪随机数生成器,种子相同则序列完全相同。攻击者只要知道或猜到种子,就能重现整个密钥和IV序列。

种子复用的攻击场景:

场景 种子来源 攻击难度 实际案例
设备批量生产 芯片ID低32位 低 同批次设备密钥可预测
容器启动 容器启动时间戳 中 同一秒启动的容器密钥相同
虚拟机快照恢复 快照时的熵池状态 高 恢复后随机数序列完全重复
嵌入式设备上电 上电时间+计数器 低 冷启动后密钥可预测
💡 我的建议
在Linux/Unix环境下,永远使用 /dev/urandom 或 getrandom() 系统调用。在嵌入式环境中,如果硬件没有TRNG(真随机数发生器),至少要用多源熵混合:ADC噪声 + 时钟抖动 + 外部中断时间戳。不要自己实现随机数生成器——这是密码学里最容易翻车的地方之一。

3.4 知识体系:SM4密钥安全全景

下面这张图把SM4密钥生成过程中的风险点和防护措施串起来了。我建议你把它存下来,做代码审查时对照着看。

SM4密钥生成安全风险全景 SM4密钥生成入口 弱密钥 固定IV 随机数种子复用 弱密钥具体表现 • 全零/全F密钥 • ASCII字符串直接填充 • 时间戳截断重复 固定IV具体表现 • 配置文件写死IV • 所有表共用同一IV • IV为全零或固定值 种子复用具体表现 • srand(固定值) • time(NULL)做种子 • 熵池状态可预测 解决方案与最佳实践 核心原则:密钥熵≥128位 | IV每次随机生成 | 使用CSPRNG(如/dev/urandom) 避免自实现随机数生成器,优先使用操作系统提供的安全随机数接口

3.5 实战检查清单

每次做SM4密钥相关代码审查时,我习惯按下面这个清单过一遍。你也试试看:

  1. 密钥来源检查:密钥是硬编码的吗?是从配置文件读取的吗?如果是,配置文件权限设置了吗?
  2. 密钥熵检查:密钥的字节分布是否均匀?有没有出现重复字节模式?
  3. IV生成检查:每次加密是否都生成了新的IV?IV是否使用了安全随机数生成器?
  4. 随机数种子检查:代码中是否有srand()、srandom()调用?种子来源是否可预测?
  5. 密钥生命周期检查:密钥使用完后是否安全擦除?内存中是否有残留?
🔑 一句话总结
SM4算法本身没问题,问题出在密钥怎么来、IV怎么生、随机数怎么取。这三个点守住,SM4的安全性就有保障。守不住,算法再强也没用。