用一把短密钥"变"出一条无限长的伪随机比特流,拿它去 XOR 明文——这是把一次一密拉回现实的第一种办法。
上一章我们卡在一个矛盾上:一次一密完美,但密钥得和明文一样长。流密码的解法直截了当——不真的准备一条超长随机密钥,而是用一把短密钥当"种子",算出一条要多长有多长的伪随机比特流。
核心零件叫伪随机生成器(PRG):输入一把短种子(密钥),输出一条长得多、"看起来随机"的比特流。流密码 = PRG 生成密钥流 + 明文逐位 XOR 密钥流。
和 OTP 唯一的区别,就是密钥流从"真随机"降级为"伪随机"。这一降级把信息论安全换成了计算安全:密钥流不再是完美不可预测,但只要没人能在可行时间内把它和真随机区分开,加密就依然牢固。这正是现代密码学的通用交易——用"计算上做不到"替换"原理上做不到"。
nonce = "number used once"。既然同一把密钥要加密很多条消息,又不能让密钥流重复(还记得第 5 章的两次一密吗?),就给 PRG 再喂一个每条消息都不同的值——nonce。相同密钥 + 不同 nonce = 不同密钥流。nonce 可以公开,但绝不能在同一密钥下重复。这个"不能重复"会是本章和第 9 章反复敲打的重点。
下面的 Demo 用一个真实(但已过时)的流密码 RC4 来演示。改动密钥或 nonce,整条密钥流会面目全非(这是好的混淆);但只要密钥和 nonce 不变,密钥流就逐字节完全一样——这正是流密码可解密的原因,也正是它危险的地方。
RC4 曾经无处不在:SSL/TLS、WEP、WPA-TKIP……它小巧、快、代码只有几十行。但它的密钥流并非真的均匀随机——最著名的缺陷是输出的头几个字节存在统计偏差(某些值出现得比应有的更频繁)。攻击者收集海量用同一明文位置加密的密文,就能把这点偏差累积成对明文的还原。
RC4 的故事是密码学工程的一个缩影:一个算法可以流行十几年,然后因为一个"看起来很小"的统计偏差被彻底淘汰。这也解释了为什么密码学界如此保守——今天没被攻破,不代表明天不会。
WEP 不只是选了 RC4,还犯了第 5 章的大忌:它的 nonce(叫 IV)只有 24 位,而且明文传输、还常常复位。24 位意味着大约 1600 万个值,一个繁忙网络几小时就会把 nonce 用完并开始重复——于是两次一密攻击叠加 RC4 的偏差,让 WEP 成了密码学教科书里"如何把每件事都做错"的标准反面教材。上面 Demo 里那个"制造重用"的勾选框,就是在还原这个灾难。
如果说 RC4 是流密码的过去,ChaCha20(由 Daniel Bernstein 设计)就是它的现在。你手机上的 TLS、WireGuard VPN、很多 App 的加密都在用它,尤其在没有 AES 硬件加速的设备上,它比 AES 更快。
ChaCha20 的密钥流不是像 RC4 那样"边走边生成一个状态数组",而是:
关键设计:每个 64 字节块的密钥流是独立算出来的(靠块计数器区分),这让 ChaCha20 天然支持随机访问和并行——这也是它能作为 AEAD(ChaCha20-Poly1305,第 9 章)高效运行的基础。它没有 RC4 那样的已知统计偏差,经受住了十余年公开分析。
流密码逐位/逐字节地把明文和密钥流 XOR,天然处理任意长度、无需填充,适合流式数据。下一章的分组密码(AES)则一次处理固定大小(16 字节)的一整块,是另一种范式。有趣的是:分组密码配上合适的"模式"(第 8 章的 CTR 模式)后,也能变成一个流密码。两条路最终交汇——但要先看懂 AES 内部那台精密的机器。
无论 RC4 还是 ChaCha20,流密码都建立在同一条铁律上:同一密钥下,(nonce, 计数器) 绝不能重复。一旦重复,密钥流重复,两次一密攻击(第 5 章)立刻生效。而且流密码本身不提供完整性——攻击者翻转密文某一位,解密后明文对应位就翻转,且不会报错。所以现实中流密码几乎从不单独使用,而是包在 AEAD 里(第 9 章)。