🏠 总目录📚 本教程 05 · 流密码与 Nonce
📑 本页目录(点开跳转)

05 · 流密码与 Nonce 铁律

28 分钟 | ⭐ 一条规则打穿过无数系统


🎯 一句话

流密码就是「用 PRG 模拟 OTP」。 它继承了 OTP 的简洁,也继承了 OTP 最致命的那条禁忌 —— 而这条禁忌在工程上有一个具体的名字:nonce 绝不能重复。


🌊 一、基本结构

   密钥流 = PRG(K, nonce, 计数器)
   C = M ⊕ 密钥流

   ⭐ 和 OTP 一模一样,只是密钥流是【生成的】而不是【真随机的】
K (128 bit) nonce counter 0,1,2 PRF / 分组密码 确定性:同样输入同样输出 密钥流 C 密文 M 明文
密钥流只由 (K, nonce, counter) 这三个输入决定 —— 输入一样,密钥流就一模一样,Nonce 铁律的全部理由都在这条线上

优点

优点 说明
不需要填充 逐比特/逐字节加密,明文多长密文就多长
加解密是同一套代码 都是异或
可以流式处理 数据边来边加密,不用等齐一个分组
随机访问 想解密第 1000 字节?直接算那个位置的密钥流 ⭐

💀 二、Nonce 铁律

⭐⭐ 在同一个密钥下,nonce 绝对不能重复。

   如果 (K, nonce) 重复了:

   密钥流完全相同 → 这就是【密钥复用的 OTP】

   C₁ ⊕ C₂ = M₁ ⊕ M₂     ⭐ 第 3 章的灾难原样重演
   → crib dragging → 两条明文互相解开

💡 nonce 到底是什么,怎么用对

   nonce = "number used once"

   ✅ 它【可以公开】—— 通常直接拼在密文前面一起发出去
   ❌ 它【绝对不能重复】—— 同一个 K 下

   注意:nonce 不需要【随机】,只需要【唯一】
        (IV 在某些模式下需要随机,这是两回事,见第 7 章)

三种正确做法

做法 怎么做 风险
计数器 nonce = 0, 1, 2, 3... 持久化保存 状态丢失/回滚会重复(虚拟机快照!)
随机 每次随机生成 生日碰撞:96-bit nonce 用 2⁴⁸ 次后风险不可忽略
随机扩展版 ⭐⭐ 用 XChaCha20(192-bit nonce) 192 bit 随机碰撞概率可忽略,最省心
📐 为什么 96-bit 随机 nonce 只能用 2⁴⁸ 次(想看再点)

生日界(第 9 章会详讲):从 $N$ 个值里随机取 $q$ 个, 出现碰撞的概率约 $q^2/(2N)$。

对 96-bit nonce,$N = 2^{96}$。要让碰撞概率 ≤ $2^{-32}$:

$$\frac{q^2}{2\cdot 2^{96}} \le 2^{-32} \implies q \le 2^{32.5}$$

所以 NIST 建议同一密钥下随机 nonce 的使用次数不超过约 2³²。 超过就该换密钥了。

💡 这就是 XChaCha20 存在的理由:192-bit nonce 让这个限制彻底消失。

💥 真实事故:这条铁律的血债

事故 发生了什么
WEP(Wi-Fi 加密) IV 只有 24 bit,繁忙网络几小时就重复 → 无线网络被彻底打穿
PlayStation 3 ECDSA 签名的 k 值写死成常数 → 私钥被算出,主机被完全越狱 ⭐
微软 MS-CHAPv2 / PPTP 密钥流重用 → VPN 流量可解密
KRACK 攻击(2017) 通过重放握手包强制受害者把 nonce 重置,从而重用密钥流 ⭐
多个云存储 SDK 用文件名的哈希当 nonce,同名文件即重复

🔑 注意 KRACK 的思路有多毒: 它不攻击算法,它诱导系统自己把 nonce 重置 —— "确保 nonce 不重复"不只是生成时的事,还要防止被强制回滚。


🔧 三、一个反面教材:LFSR

线性反馈移位寄存器曾被大量用于流密码(GSM 的 A5/1、蓝牙的 E0)。

一串寄存器,每次移位,最左边的新位 = 若干位的异或 每次整体右移一位 → s₄ s₃ s₂ s₁ s₀ 输出 s₀ 新位 (抽头位置由“特征多项式”决定)
结构只有两样东西:一排移位寄存器,加一条把若干抽头异或起来的反馈线 —— 整条反馈都是线性的,这正是它后面被 Berlekamp–Massey 打穿的原因

它的优点

💥 但它在密码学上是彻底失败的

   ⭐ 致命弱点:它是【线性】的

   Berlekamp–Massey 算法:
   给我【2L 个连续输出位】,我能在 O(L²) 时间内
   完整重建出这个长度为 L 的 LFSR —— 包括它的抽头和当前状态

   → 然后你能预测【之后所有的输出】,也能回推之前的
   例:L = 128 的 LFSR
   看 256 位输出(32 字节!)→ 完全破解 💀

🔑 这印证了第 4 章的第一课: LFSR 的统计随机性完美,密码学价值为零因为"随机"的定义是"没有任何高效算法能占便宜",而这里有一个 O(L²) 的算法。

试图修补的尝试,以及它们怎么倒的

修补 想法 怎么被破的
非线性组合(Geffe 生成器) 多个 LFSR 的输出过一个非线性函数 相关攻击:如果输出和某个 LFSR 有统计相关,就能单独恢复它 ⭐
非线性滤波 对单个 LFSR 的状态取非线性函数 代数攻击、快速相关攻击
不规则钟控(A5/1) 让各个 LFSR 以不同速度移位 时间-存储折中攻击,几分钟破解 GSM 通话

💡 "相关攻击"是第 2 章那个教训的重演只要能把问题分解成独立的小块,难度就从乘法变成加法。 三个 LFSR 联合搜索是 2^(L₁+L₂+L₃),分开搜是 2^L₁ + 2^L₂ + 2^L₃。


✅ 四、现代该用什么

   ❌ 不要用:RC4(有统计偏差,已被禁用)、A5/1、E0、任何自制的 LFSR 方案

   ✅ 用这些:
   ├─ ChaCha20        ← 软件实现快,无需硬件加速,移动端首选
   ├─ AES-CTR         ← 有 AES-NI 硬件指令时最快
   └─ ⭐ 但实际上你应该用【认证加密】:
       ChaCha20-Poly1305  /  AES-GCM
       (见第 11 章 —— 光加密不够,必须带认证)

🔑 本章最实用的一条建议不要单独用流密码。 直接用 AEAD(认证加密), 它把"加密 + 认证 + nonce 管理"打包成一个不容易用错的接口。

# ✅ 现代的正确做法
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os

key = ChaCha20Poly1305.generate_key()
aead = ChaCha20Poly1305(key)

nonce = os.urandom(12)                       # ⚠️ 每条消息必须不同
ct = aead.encrypt(nonce, b"secret message", b"associated data")
pt = aead.decrypt(nonce, ct, b"associated data")   # 篡改会直接抛异常 ⭐

🔗 和站内其他章的关系

相关的地方 和这一章的关系
第 3 章 OTP 密钥不能复用 nonce 铁律就是它的工程版
第 4 章 统计随机 ≠ 密码学安全 LFSR 是这句话的最佳例证
第 2 章 分解成小块 相关攻击是同一个思路
第 7 章 CTR 模式 把分组密码变成流密码
第 11 章 AEAD 流密码正确的用法
AI 基础设施 · 训练稳定性与故障恢复 ⚠️ nonce 重用最常见的工程成因就在那一章的场景里:从 checkpoint / 虚拟机快照恢复,把计数器一起倒了回去
智能体教程 · 工具设计 那里的防呆设计(Poka-yoke)是这条铁律唯一靠谱的落地方式:nonce 不能靠"记得别复用",要让复用在接口上根本写不出来 ⭐

✅ 检查点

  1. 流密码的基本结构是什么?它的四个优点?
  2. Nonce 铁律是什么?违反了会发生什么?
  3. nonce 需要随机吗?需要保密吗?
  4. 为什么 96-bit 随机 nonce 有使用次数限制?XChaCha20 怎么解决的?
  5. KRACK 攻击的思路特别在哪?
  6. LFSR 统计性质很好,为什么密码学上失败?给我一个具体数字。
  7. 相关攻击的思路是什么?它和第 2 章哪个教训是同一个?
  8. 现代应该用什么?为什么不建议单独用流密码?
👀 答案
  1. 密钥流 = PRG(K, nonce, counter),C = M ⊕ 密钥流。优点:不需要填充、加解密同一套代码、可流式处理支持随机访问(想解第 1000 字节直接算那个位置)。
  2. 同一密钥下 nonce 绝对不能重复。违反则密钥流完全相同 = 密钥复用的 OTP,C₁⊕C₂ = M₁⊕M₂,crib dragging 让两条明文互相解开。
  3. 不需要随机,只需要唯一不需要保密,通常直接拼在密文前发出去。(IV 在某些模式下需要随机,那是两回事。)
  4. 生日碰撞:随机取 q 个值碰撞概率约 q²/2N,96-bit 下 q 超过约 2³² 风险就不可忽略。XChaCha20 用 192-bit nonce,让这个限制彻底消失。
  5. 不攻击算法,而是通过重放握手包诱导系统自己把 nonce 重置。教训:确保 nonce 不重复不只是生成时的事,还要防止被强制回滚
  6. 因为它是线性的。Berlekamp–Massey 算法只需 2L 个连续输出位,就能在 O(L²) 时间内完整重建 LFSR。L=128 时,看 256 位(32 字节)输出即可完全破解。
  7. 如果组合输出和某个 LFSR 的输出有统计相关性,就能单独恢复那一个 LFSR。和第 2 章 Kasiski 一样是"把问题分解成独立小块"——联合搜索 2^(L₁+L₂+L₃) 变成 2^L₁+2^L₂+2^L₃。
  8. ChaCha20-Poly1305 或 AES-GCM 这类 AEAD。不建议单独用流密码是因为光加密不提供完整性(可比特翻转),AEAD 把加密+认证+nonce 管理打包成不容易用错的接口。

🛑 可以停在这里

走神救援

流密码=用 PRG 模拟 OTP:密钥流=PRG(K,nonce,counter),C=M⊕密钥流。优点:不用填充、加解密同一套代码、可流式、支持随机访问。⭐⭐Nonce 铁律:同一密钥下 nonce 绝不能重复——重复则密钥流相同 = 密钥复用的 OTP,C₁⊕C₂=M₁⊕M₂。nonce 只需唯一不需随机,可以公开。三种做法:计数器(⚠️虚拟机快照回滚会重复)、随机(96-bit 只能用约 2³² 次,生日碰撞)、⭐XChaCha20 的 192-bit nonce 最省心。💥血债:WEP(IV仅24bit,几小时就重复)、PS3(k值写死成常数→私钥被算出,主机越狱)、MS-CHAPv2、⭐KRACK(不攻击算法,重放握手包诱导系统自己把 nonce 重置——防重复不只是生成时的事,还要防回滚)。LFSR 是反面教材:硬件便宜、周期达 2ⁿ−1、统计性质完美,但它是线性的——⭐Berlekamp–Massey 只要 2L 个输出位就能 O(L²) 重建整个 LFSR(L=128 时看 32 字节就破)。修补也没救:Geffe 生成器倒于相关攻击(输出与某个 LFSR 统计相关就能单独恢复它,联合搜索 2^ΣL 变成 Σ2^L),A5/1 几分钟破解 GSM。✅现代:ChaCha20 / AES-CTR,但⭐不要单独用流密码,直接用 AEAD(ChaCha20-Poly1305、AES-GCM),它把加密+认证+nonce管理打包成不易用错的接口。

下一节 👉 06-分组密码.md

打卡记录保存在你的浏览器里,首页能看到总进度