3. SM4 加密模式:ECB、CBC、CFB、OFB、CTR、GCM
聊完密钥扩展和加解密算法,咱们得直面一个现实问题:SM4 一次只处理 16 字节。你想想,实际接口里传输的数据少则几十、多则上千个字节,怎么用这个“16 字节小盒子”去装下整条数据?这就引出了加密模式。
说白了,加密模式就是定义了“如何把 SM4 这个分组密码,应用于任意长度的明文”。我刚开始接触时也懵,总觉得 ECB 和 CBC 看着差不多。后来在项目中踩了坑,才明白这里面的门道有多深。
核心原则:选择加密模式,本质上是选择“安全性”与“性能/复杂度”之间的平衡点。没有万能的模式,只有最适合场景的模式。
3.1 ECB 模式:简单但危险的“电子密码本”
ECB 是最直白的模式——把明文分成 16 字节一块,每块独立用 SM4 加密。我当年在学校做课程设计时,第一个实现的就是它,因为逻辑太简单了。但真正在接口项目里用它,那就是给自己挖坑。
为什么?因为相同的明文块会加密成相同的密文块。你想想看,如果接口报文里有一段固定的协议头,每次加密后产生一段完全一样的密文头。攻击者看一眼就知道:“哦,这段是协议头。” 这就是明文模式泄露。
避坑指南:我曾经在一个 IoT 项目的设备认证接口里,被前辈发现用了 ECB。对方直接说:“你们这加密跟没加一样,波形我都猜得到。” 从那天起,我就把 ECB 拉进了黑名单。除非你只是做做测试、或者加密的明文压根没有重复特征(比如单次随机数),否则别用 ECB。
| 特性 | ECB | 说明 |
|---|---|---|
| 并行性 | 支持 | 每块独立加解密,适合硬件加速 |
| 错误扩散 | 无 | 一块出错了不影响其他块 |
| 初始化向量 (IV) | 不需要 | - |
| 安全性 | 低 | 相同明文 => 相同密文,泄露模式 |
| 适用场景 | 简短、随机的数据包 | 比如加密一个单次访问令牌 |
3.2 CBC 模式:用“链条”打破重复
CBC 解决了 ECB 的核心问题。它让每个明文块先与上一个密文块做异或,然后再加密。第一个块没有“上一个”,所以需要一个随机生成的初始化向量 (IV)。
我个人习惯,设计接口安全方案时,CBC 是保底选择。它不追求极致性能,但安全性比 ECB 高了一个台阶。不过要注意:IV 必须是随机的,且每次加密用不同的 IV。这是个老生常谈的坑:有人把 IV 写死在代码里,那等于没用。
经验提示:我曾经接手过一个项目,发现对方把 IV 设为全零字节。我问为什么,他说“为了方便”。方便了谁?方便了攻击者。IV 等同于第二个密钥,务必随机生成,并在密文前明文传输给解密端。
- 加解密流程: 明文块1 → 异或 IV → SM4加密 → 密文块1;密文块1 → 与明文块2异或 → SM4加密 → 密文块2……
- 注意: 解密必须从第一块开始,串行执行。无法并行解密。
- 错误扩散: 密文块传输中损坏一位,会导致当前块和下一块解密出错。
3.3 CFB、OFB、CTR:三种流密码模式
为什么要搞出这么多流密码模式?核心原因就一个:把 SM4 变成一个流加密器。这样加密长度可以不是 16 的整数倍,不需要填充。你想想看,接口里有些字段长度是 3 字节、5 字节,用 CBC 还得补到 16 字节,多出来的填充数据还容易被利用。
CFB(密文反馈模式):它把上一个密文块喂进 SM4 加密器,输出结果与当前明文异或。我记着有一个国家标准的金融接口协议就推荐用 SM4-CFB。
OFB(输出反馈模式):和 CFB 类似,但它反馈的是加密器输出值,而不是密文。好处是加密器可以预先运算,适合对延迟敏感的场景。不过我个人觉得 OFB 用得不多,因为 CTR 在几乎所有方面都比它强。
CTR(计数器模式):这才是现代流密码模式里的“王”。它用一个递增的计数器(通常是非加 1,也可以是随机 nonce 拼接计数器)作为输入,加密后与明文异或。每个块之间完全独立,可以并行加解密,还能随机访问任意位置的数据。
核心对比:CFB/OFB 都是串行结构,无法并行;CTR 是并行结构。如果你的接口服务需要高吞吐量(比如每秒处理 10 万条请求),CTR 是首选。
3.4 GCM:CTR 的“安全加强版”
讲完 CTR,必须得提 GCM。它本质上就是CTR + GMAC。CTR 负责加密,GMAC(Galois 消息认证码)负责完整性校验。为什么需要这个?
你想想,CTR 模式本身只保证机密性,不保证完整性。如果攻击者篡改了密文,解密端没任何办法检测出来。我曾在某个支付接口的测试中发现,使用 CTR 加密一条金额报文,攻击者翻转密文的几个比特位,解密后金额就变了。这就是比特翻转攻击。
GCM 用一个 Galois 域上的乘法哈希函数,对密文和附加数据(AAD)算出一个 128 位的认证标签。解密时同时校验标签,标签不对就直接拒绝。这样既保证了机密性,也保证了完整性。
| 特性 | CTR | GCM |
|---|---|---|
| 机密性 | ✓ | ✓ |
| 完整性校验 | ✗ | ✓(GMAC) |
| 性能 | 高(可并行) | 略低(多了 Auth 运算) |
| 适用场景 | 有独立完整性保护的场景 | 需要“一包搞定”加密+完整性验证 |
避坑指南:GCM 的 nonce(也就是 IV)必须是 12 字节(96 位)。我曾经见过有人传 16 字节 nonce,导致内部出 bug。如果实在想用 16 字节,也得保证是 12 字节 nonce + 4 字节 fixed field。别在这个细节上栽跟头。
3.5 实战选择建议
说了这么多,到底用哪个?我个人给个不成文的排序,优先选 GCM,其次 CTR,再次 CBC。ECB、CFB、OFB 除非有强约束(比如必须兼容某个老协议),否则就别碰了。
- API 接口/微服务通信: GCM。一次性搞定加密+校验,还能带上 AAD。
- 大量流数据(如视频、日志): CTR。需要高吞吐,完整性可以在外层用 HMAC。
- 兼顾兼容性: CBC。标准得不能再标准,几乎所有 SM4 库都支持。
嗯,到这你应该明白了,加密模式不是随便选的。每个模式背后都有一堆血的教训。下回听到别人说“我就用 ECB 方便”,你可以善意地提醒他一句:小心明文模式泄露。