对称加密基础:对称密码原理、分组密码概念

好,咱们正式进入加密技术的核心地带。对称加密,说白了就是一把钥匙开一把锁——加密和解密用同一把密钥。听起来很简单对吧?但实际项目中,这个“同一把钥匙”恰恰是最大的坑。我刚开始接触密码学时,觉得对称加密没啥难度,直到有一次在分布式系统里搞密钥分发,才真正体会到什么叫“钥匙越多,麻烦越大”。

对称加密的核心理念

你可以把对称加密想象成一个保险箱:

  • 发送方把明文放进保险箱,用密钥锁上,变成密文
  • 接收方拿到密文后,用同一把密钥打开锁,还原明文

为什么叫“对称”?因为加密和解密是同一个操作(或逆操作),密钥完全对称。这样做的好处很明显——算法效率极高,适合处理大量数据。但代价也很直接:密钥必须安全地分发给通信双方,一旦泄露,整个系统就完蛋了。我记得在早期做物联网项目时,设备数量上千,每个设备都要预置密钥,光密钥管理就占了项目三分之一的工时。

注意:对称加密的安全性不依赖算法保密,而是依赖密钥保密。即使攻击者知道用了什么算法,只要拿不到密钥,就解不出明文。业界有一个原则叫“柯克霍夫原则”——系统应该无需保密,密钥保密即可。说白了,你的算法越透明越好,让全世界来审查,这样漏洞才藏不住。

分组密码 vs 流密码

对称加密按处理方式分为两大类:分组密码和流密码。咱们这节课重点讲分组密码(因为 SM4 就是分组密码),但先放个对比,让你心里有底。

维度 分组密码 流密码
处理单位 固定大小的块(如 128 位) 逐比特或逐字节
核心结构 多轮迭代,混淆与扩散 伪随机密钥流
典型算法 AES, SM4, DES RC4, ChaCha20
填充需求 是(明文不一定是块大小的整数倍) 否(流式处理)
错误传播 取决于工作模式(可能传播整个块) 一般只影响当前位

你想想看,分组密码为什么要把数据切块?因为密码学算法内部做了很多复杂的替换和置换(比如 S 盒、P 盒),这些操作需要固定尺寸的输入才能高效实现。我建议你记住一句话:分组密码用“块”换安全性,流密码用“流”换速度。

分组密码的几个核心概念

在深入 SM4 之前,先搞清楚分组密码的几个通用要素,后面学起来就顺了。

  1. 块大小:最常见的 128 位(16 字节)。SM4 也是 128 位块,AES 也是。为什么是 128?因为 64 位块在现代密码分析下容易受到生日攻击,不安全了。
  2. 密钥长度:SM4 固定为 128 位。AES 支持 128/192/256。密钥越长,暴力破解越难,但轮数也会增加,速度下降。
  3. 轮函数:分组密码通常把加密过程重复多“轮”。每一轮包括非线性替换(混淆)和线性置换(扩散)。SM4 有 32 轮,AES-128 是 10 轮。轮数多的好处是更容易抵抗差分和线性密码分析。
  4. 密钥扩展:也叫密钥编排。主密钥经过算法生成多轮子密钥,每一轮用不同的子密钥。这是必须的——如果所有轮用同一个子密钥,安全性会大打折扣。
这里有个我踩过的坑:一开始我写 SM4 的密钥扩展时,直接复制了 AES 的流程,结果算出来的密文完全不对。后来发现 SM4 的密钥扩展逻辑跟 AES 完全不同——它是系统循环加异或,而不是字替换加轮常量。所以千万别把算法混用,哪怕是经典算法也要仔细读标准文档。

工作模式——分组不是简单的切块

分组密码只处理一个块的加解密,但实际数据可能很长。这时候就需要工作模式来定义如何把多个块串联起来。我见过不少新手直接拿 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 的倍数,也必须补一整块。为什么?因为解密后要去掉填充,如果没补,接收方无法区分最后那个字节到底是填充还是真实数据。这个细节我当年吃过亏:有个协议没有强制填充,结果短消息加密后用着没事,但长消息偶尔会解析出错,排查了一下午才发现是填充逻辑漏了边界情况。

SM4 官方推荐使用 PKCS#7 填充。你在实现时一定要校验填充格式,防止填充预言攻击——攻击者可以通过修改密文并观察服务端是否返回“填充错误”来逐步解出明文。解决办法:加消息认证码(MAC)或者使用认证加密模式(如 GCM),咱们后面章节会细讲。

来个图,梳理对称加密全过程

我画了一张图把对称加密的完整流程串起来,方便你理解各部件的关系。从明文字符串到密文,中间经历了分组、填充、工作模式、轮函数等步骤,最终变成一串无法识别的比特。

对称加密完整流程(分组密码视角) 原始明文 分组 + 填充 分组块 (每块128位) Block₀,Block₁,… 工作模式 (如CBC、CTR) 需要IV + 子密钥 轮函数(32轮) S盒置换 线性变换L 密文块 密钥扩展(生成子密钥) (来自主密钥) 解密时流程相反:密文块→轮函数(逆运算)→工作模式→去除填充→原始明文

对称加密的共性要点

不管你用哪种分组密码,有几点是通用的:

  • 必须保证密钥随机且机密——别用硬编码的“123456”之类的傻密钥,我在代码审计中见过不下十次这种问题。
  • 工作模式的选择直接影响安全性——ECB 基本可以放弃,CBC 必须搭配随机 IV,CTR 要确保计数器不会重复。
  • 填充不是小事——忘记填充、填充方式不一致、不验证填充,都会导致漏洞或兼容问题。
  • 身份认证不可或缺——对称加密只保证机密性,不保证完整性。攻击者可以篡改密文(哪怕不知道密钥)导致解密出乱码,甚至利用填充预言攻击窃取信息。所以生产环境必须搭配消息认证码(MAC)或者直接使用认证加密模式。