4. SM4 ECB模式误用:一个不该犯的经典错误
ECB模式,说白了就是“电子密码本”模式。每个明文块独立加密,同样的明文块永远输出同样的密文块。听起来简单,对吧?但正是这个“简单”,埋下了巨大的安全隐患。
我在做金融系统安全审计时,遇到过好几家厂商的加密模块都用了ECB模式。问他们为什么选ECB,回答几乎一样:“简单、快、好实现。”嗯,确实快,但安全呢?
4.1 ECB模式的安全缺陷
ECB模式的核心问题就一个:明文块之间没有关联性。每个16字节的SM4分组都是独立加密的。这意味着什么?
- 模式可识别:相同的明文块产生相同的密文块。攻击者可以通过观察密文重复模式,推断出明文结构。
- 重放攻击:攻击者可以截获并重排密文块,接收方无法检测到数据被篡改。
- 块边界泄露:密文块边界直接对应明文块边界,攻击者可以精确定位敏感数据位置。
举个例子。假设你加密一段JSON:{"name":"张三","balance":1000000}。用ECB模式加密后,攻击者虽然看不懂密文,但能看出密文中有重复的块。为什么?因为JSON的键名重复出现。攻击者甚至可以通过“密文块替换”把别人的余额改成你的——想想看,这有多可怕。
4.2 实际案例分析
我记得有个真实案例。某支付公司用SM4-ECB加密交易流水。他们觉得“国密算法很安全”,却忽略了模式选择的问题。结果呢?
安全团队做渗透测试时,发现了一个漏洞:
- 攻击者注册两个账户,分别发起交易A和交易B
- 交易A的密文是
C1 C2 C3,交易B的密文是C4 C5 C6 - 攻击者把密文替换成
C1 C5 C3——把交易A的第一个块和交易B的第二个块拼在一起 - 服务端解密后,居然成功验证了!因为ECB模式不检测块顺序
这就是典型的“密文块重排攻击”。ECB模式没有提供任何完整性保护。你想想看,如果攻击者能随意重组密文块,那加密还有什么意义?
4.3 为什么CBC和CTR是更好的选择
我个人习惯,只要涉及批量数据加密,首选CBC或CTR模式。为什么?
| 特性 | ECB | CBC | CTR |
|---|---|---|---|
| 块间关联 | 无 | 有(链式依赖) | 有(计数器驱动) |
| 相同明文→相同密文 | 是 | 否(IV不同时) | 否(IV不同时) |
| 重放攻击防护 | 无 | 有(依赖IV) | 有(依赖IV) |
| 并行加密 | 支持 | 不支持 | 支持 |
| 错误传播 | 1个块 | 2个块 | 1个块 |
| 国密合规推荐 | ❌ | ✅ | ✅ |
CBC模式:每个明文块先与前一个密文块异或,再加密。这样,即使明文块相同,只要前一个密文块不同,输出就不同。而且,密文块的顺序被链式依赖锁死了——你没法重排。
CTR模式:把SM4当作流密码用。用一个计数器生成密钥流,再与明文异或。好处是:可以并行加密、解密,而且错误不会传播。但要注意——计数器绝对不能重复使用。我在项目中见过有人把计数器设成固定值,结果加密结果完全可预测——这比ECB还糟糕。
4.4 代码示例:从ECB迁移到CBC
下面是一个SM4-ECB的典型实现(错误示范):
// ❌ 错误:使用ECB模式
public byte[] encryptECB(byte[] plaintext, byte[] key) {
Sm4Engine engine = new Sm4Engine();
engine.init(true, new KeyParameter(key));
byte[] ciphertext = new byte[plaintext.length];
int offset = 0;
while (offset < plaintext.length) {
engine.processBlock(plaintext, offset, ciphertext, offset);
offset += 16;
}
return ciphertext;
}
改成CBC模式后:
// ✅ 正确:使用CBC模式
public byte[] encryptCBC(byte[] plaintext, byte[] key, byte[] iv) {
// iv必须是16字节随机数,每次加密都重新生成
CBCBlockCipher cipher = new CBCBlockCipher(new Sm4Engine());
cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] ciphertext = new byte[plaintext.length];
int offset = 0;
while (offset < plaintext.length) {
cipher.processBlock(plaintext, offset, ciphertext, offset);
offset += 16;
}
return ciphertext;
}
注意看区别:CBC模式需要额外传入一个IV(初始化向量)。IV必须是随机生成的,而且每次加密都要不同。IV不需要保密,但必须随密文一起传输——通常放在密文前面。
4.5 知识图谱:SM4模式选择决策流程
下面这张图帮你快速决策:
4.6 避坑指南
最后,分享几个我踩过的坑:
- 不要自己实现加密模式:我曾经见过有人手写CBC模式,结果IV处理错了,导致前16字节明文直接暴露。用现成的密码库,别造轮子。
- IV不是密钥:IV不需要保密,但必须唯一。把IV和密文一起存,别搞丢了。
- ECB模式不是完全不能用:如果你只加密一个块(16字节)的数据,比如一个密钥本身,ECB是可以的。但一旦数据超过一个块,立刻换CBC或CTR。
- 别忘了完整性校验:CBC和CTR只提供机密性,不提供完整性。你还需要加个MAC(消息认证码),否则攻击者仍然可以篡改密文。