对称加密基础:对称密码原理、分组密码概念
好,咱们正式进入加密技术的核心地带。对称加密,说白了就是一把钥匙开一把锁——加密和解密用同一把密钥。听起来很简单对吧?但实际项目中,这个“同一把钥匙”恰恰是最大的坑。我刚开始接触密码学时,觉得对称加密没啥难度,直到有一次在分布式系统里搞密钥分发,才真正体会到什么叫“钥匙越多,麻烦越大”。
对称加密的核心理念
你可以把对称加密想象成一个保险箱:
- 发送方把明文放进保险箱,用密钥锁上,变成密文
- 接收方拿到密文后,用同一把密钥打开锁,还原明文
为什么叫“对称”?因为加密和解密是同一个操作(或逆操作),密钥完全对称。这样做的好处很明显——算法效率极高,适合处理大量数据。但代价也很直接:密钥必须安全地分发给通信双方,一旦泄露,整个系统就完蛋了。我记得在早期做物联网项目时,设备数量上千,每个设备都要预置密钥,光密钥管理就占了项目三分之一的工时。
分组密码 vs 流密码
对称加密按处理方式分为两大类:分组密码和流密码。咱们这节课重点讲分组密码(因为 SM4 就是分组密码),但先放个对比,让你心里有底。
| 维度 | 分组密码 | 流密码 |
|---|---|---|
| 处理单位 | 固定大小的块(如 128 位) | 逐比特或逐字节 |
| 核心结构 | 多轮迭代,混淆与扩散 | 伪随机密钥流 |
| 典型算法 | AES, SM4, DES | RC4, ChaCha20 |
| 填充需求 | 是(明文不一定是块大小的整数倍) | 否(流式处理) |
| 错误传播 | 取决于工作模式(可能传播整个块) | 一般只影响当前位 |
你想想看,分组密码为什么要把数据切块?因为密码学算法内部做了很多复杂的替换和置换(比如 S 盒、P 盒),这些操作需要固定尺寸的输入才能高效实现。我建议你记住一句话:分组密码用“块”换安全性,流密码用“流”换速度。
分组密码的几个核心概念
在深入 SM4 之前,先搞清楚分组密码的几个通用要素,后面学起来就顺了。
- 块大小:最常见的 128 位(16 字节)。SM4 也是 128 位块,AES 也是。为什么是 128?因为 64 位块在现代密码分析下容易受到生日攻击,不安全了。
- 密钥长度:SM4 固定为 128 位。AES 支持 128/192/256。密钥越长,暴力破解越难,但轮数也会增加,速度下降。
- 轮函数:分组密码通常把加密过程重复多“轮”。每一轮包括非线性替换(混淆)和线性置换(扩散)。SM4 有 32 轮,AES-128 是 10 轮。轮数多的好处是更容易抵抗差分和线性密码分析。
- 密钥扩展:也叫密钥编排。主密钥经过算法生成多轮子密钥,每一轮用不同的子密钥。这是必须的——如果所有轮用同一个子密钥,安全性会大打折扣。
工作模式——分组不是简单的切块
分组密码只处理一个块的加解密,但实际数据可能很长。这时候就需要工作模式来定义如何把多个块串联起来。我见过不少新手直接拿 ECB 模式用,结果被破解得彻彻底底。咱们把常见的模式过一遍:
| 模式 | 需要 IV | 并行加密 | 错误传播 | 典型问题 |
|---|---|---|---|---|
| ECB(电子密码本) | 否 | 支持 | 无(块独立) | 相同的明文块产生相同密文块,易被识别图案 |
| CBC(密文分组链接) | 是 | 不支持 | 当前块错误影响当前块和下一块 | IV 必须随机且不重复 |
| CFB(密文反馈) | 是 | 不支持 | 错误块影响后续块直到移位结束 | 可以实时流式加密 |
| OFB(输出反馈) | 是 | 不支持 | 错误只影响当前块 | 密钥流可预计算 |
| CTR(计数器) | 是(Nonce + Counter) | 支持 | 错误影响当前块 | 需要保证计数器不重复 |
为什么 ECB 危险?我给你举个真实的例子。之前一个 APP 用 ECB 加密用户头像,结果头像的轮廓居然还能看出来,只是颜色被“打码”了——因为相同颜色区域的块加密后产生了相同的密文。攻击者直接描轮廓就能识别出是什么图片。这就是典型的图案泄露攻击。所以你想想看,ECB 只适合加密长度小于一个块的随机数据,比如加密单个密钥。
我个人习惯:只要数据量超过一个块,默认用 CBC 或 CTR。CBC 是最经典的选择,兼容性好;CTR 可以并行,适合高性能场景。但注意:CBC 解密时可以并行(因为解密需要前一个密文块),加密不行。CTR 加解密都可以并行。
填充方式——块长度不够怎么办?
分组密码要求输入是块大小的整数倍。如果最后一块不足 16 字节(SM4 的情况),就得补足。最常用的填充是 PKCS#7:缺多少字节就补多少字节,每个补的字节的值等于缺的字节数。举个例子:
原文长度: 10 字节(缺 6 字节)
填充后: 原数据 + 0x06 0x06 0x06 0x06 0x06 0x06
原文长度: 16 字节(刚好整数倍)
填充后: 原数据 + 0x10 x 16(补一整块 0x10)
注意最后一种情况——即使原文长度已经是 16 的倍数,也必须补一整块。为什么?因为解密后要去掉填充,如果没补,接收方无法区分最后那个字节到底是填充还是真实数据。这个细节我当年吃过亏:有个协议没有强制填充,结果短消息加密后用着没事,但长消息偶尔会解析出错,排查了一下午才发现是填充逻辑漏了边界情况。
来个图,梳理对称加密全过程
我画了一张图把对称加密的完整流程串起来,方便你理解各部件的关系。从明文字符串到密文,中间经历了分组、填充、工作模式、轮函数等步骤,最终变成一串无法识别的比特。
对称加密的共性要点
不管你用哪种分组密码,有几点是通用的:
- 必须保证密钥随机且机密——别用硬编码的“123456”之类的傻密钥,我在代码审计中见过不下十次这种问题。
- 工作模式的选择直接影响安全性——ECB 基本可以放弃,CBC 必须搭配随机 IV,CTR 要确保计数器不会重复。
- 填充不是小事——忘记填充、填充方式不一致、不验证填充,都会导致漏洞或兼容问题。
- 身份认证不可或缺——对称加密只保证机密性,不保证完整性。攻击者可以篡改密文(哪怕不知道密钥)导致解密出乱码,甚至利用填充预言攻击窃取信息。所以生产环境必须搭配消息认证码(MAC)或者直接使用认证加密模式。