对称加密算法:AES、DES、流密码基础
说到对称加密,你可以把它想象成一把钥匙开一把锁——加密和解密用的都是同一个密钥。我在刚接触密码学的时候,总觉得这个概念太简单了,后来才发现真正落地的时候坑比想象中多得多。
对称加密分两大类:分组密码 和 流密码。分组密码把明文切成固定大小的块,逐块加密;流密码则是一个字节一个字节地跟密钥流异或。今天咱们就把这两类里最常用的几个算法捋一遍。
DES:曾经的加密标准
DES(Data Encryption Standard)是上世纪70年代的产物。它把数据分成64位的块,密钥只有56位有效位。我当时看到这个数字心里就咯噔一下——56位密钥在现代计算能力面前,暴力破解就是分分钟的事。
DES 用的 Feistel 网络结构,加密和解密流程几乎一样,只是密钥调度顺序反了。这个设计在硬件上特别省资源,但轮数只有16轮,安全性已经过时了。
后来有了 3DES——就是对同一组数据用 DES 连续加密三次。密钥长度变成112或168位,安全性上去了,但处理速度慢得可怜。我记得有一次测3DES吞吐量,连我们那台老旧的路由器都嫌它拖后腿。所以除非是兼容古董设备,我建议你直接跳过3DES。
AES:现代加密标准
AES 是 NIST 在2001年选定的标准,原名叫 Rijndael。它一出场就干掉了 DES 和 3DES,现在几乎所有加密场景都能看到它的影子。
分组固定128位,密钥支持128、192、256位。轮数根据密钥长度变化:10/12/14轮。结构是 SPN(Substitution-Permutation Network),跟 Feistel 那套完全不一样——每一轮都会把整个数据块彻底打乱。
我个人的习惯是:如果只做数据传输加密,AES-128 已经足够安全;如果数据要保存很多年且担心量子计算,那就上 AES-256。多出来的轮数代价不大,心里踏实。
下面是一段用 Python 调用 AES 做 GCM 模式加密的简单例子:
from Crypto.Cipher import AES
import os
key = os.urandom(32) # 256 位密钥
nonce = os.urandom(12) # 96 位 nonce
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(b"Hello, searchable encryption!")
# 实际使用时需要把 nonce、ciphertext、tag 一起传输
流密码:与分组密码不同
流密码的核心思路是:用密钥生成一个伪随机的密钥流,然后跟明文逐比特异或。加密和解密完全一样,因为异或操作是自逆的。
最常见的流密码是 RC4。RC4 曾经火遍全球(TLS/SSL 早期、WEP 都在用),但它有严重的偏置问题——密钥流的前几个字节有明显的统计特性。我在做 WPA 破解分析的时候发现,很多工具抓到一个足够长的 RC4 密钥流就能反推出密钥。现在主流浏览器和协议已经全面禁用 RC4 了。
现代推荐的是 ChaCha20(Google 在 TLS 里主推)和 Salsa20。它们的设计者是 Daniel Bernstein,结构简单,而且纯软件实现比 AES 还快。我在移动端项目里测试过,ChaCha20 的加密速度比 AES-128-CTR 快30%以上,对 ARM 处理器特别友好。
流密码和分组密码的区别我用一张表总结:
| 对比项 | 分组密码(AES) | 流密码(ChaCha20) |
|---|---|---|
| 处理单位 | 固定块(128位) | 比特/字节流 |
| 内存占用 | 需要整块缓冲区 | 极低,适合嵌入式 |
| 实现速度 | 硬件加速快 | 纯软件快 |
| 模式复杂性 | 需要搭配认证模式 | 通常自带认证(Poly1305) |
| 错误扩散 | CBC模式会扩散错误 | 错误只影响当前位 |
到底选哪个?我的建议是:如果能在硬件上跑 AES-NI 指令,用 AES-GCM;如果场景偏软件且对性能敏感,用 ChaCha20-Poly1305。两种都是经过权威验证的好选择。
对称加密在可搜索加密中的位置
咱这门课叫可搜索加密,你可能会问:讲这些底层算法跟搜索有什么关系?关系大了。可搜索加密方案里,数据本身要对加密密钥保护的中间结构做加密,比如构建倒排索引时需要加密每个词对应的文档列表。如果底层加密算法选错了,整棵树的根就歪了。
举个具体例子:在很多对称可搜索加密(SSE)方案中,需要用伪随机函数(PRF)来生成陷门。而 PRF 的典型实现就是 AES 固定密钥、固定输入,输出一个看似随机的值。所以你先把 AES 理解透,后面的陷门构造、安全定义就顺了。