在 CBC 模式里,IV(初始化向量)的作用是让第一个块的加密结果不可预测。很多实现把它当成一个可以固定的常量,或者干脆硬编码在配置里。这样做加密解密的往返测试都能通过,看起来没问题,但实际上已经把加密的保护能力削弱了一大截。
IV 的作用是什么
CBC 的加密流程是:当前明文块先与前一块的密文异或,再送去加密。第一个块没有「前一块密文」,所以需要一个初始值来代替,这个初始值就是 IV。
IV 的引入解决了一个问题:如果不用 IV,或者所有加密都用同一个 IV,那么相同的明文在相同密钥下仍然会产生相同的密文——ECB 的缺陷会以另一种形式回来。
所以 IV 的职责是让相同的明文在每次加密时产生不同的密文。这要求 IV 必须满足两个条件:随机生成,且每次加密都不同。
IV 重复会发生什么
IV 重复的后果取决于模式,CBC 和 CTR 的表现不同,但都很严重。
在 CBC 模式下,如果两次加密使用了同一个 IV 和同一个密钥,那么两个密文的第一块相同,就意味着对应的明文第一块也相同。攻击者虽然不知道明文内容,但能判断出「这两条记录的开头是一样的」。
更糟的是,如果明文里有一部分内容攻击者已知(比如固定的文件头、已知的字段名),他可以利用这部分已知明文和密文的对应关系,推导出后续块的明文。这是选择明文攻击的一种形式,攻击者只要有办法让系统加密他指定的内容,就可能逐块推断出其他数据。
在 CTR / OFB* 模式下,问题更直接。这两个模式把 AES 当作密钥流生成器:用「IV + 计数器」加密得到密钥流,再与明文异或。如果两次加密使用了相同的 IV 和密钥,就会生成*完全相同的密钥流。
而密钥流相同的后果是可以直接计算的:两段密文异或的结果,等于两段明文异或的结果。因为密文 = 明文 ⊕ 密钥流,两次异或时密钥流被抵消了。
C1 = P1 ⊕ KS
C2 = P2 ⊕ KS
C1 ⊕ C2 = P1 ⊕ P2
攻击者拿到 C1 ⊕ C2 就等于拿到 P1 ⊕ P2。如果其中一段明文已知,另一段就能直接推出来。这就是著名的「两次一密」(two-time pad)问题——曾经用于加密的流密码因为密钥流重用而被彻底破解。
应该怎么生成和传递 IV
正确的做法是三条。
第一,用密码学安全的随机源生成 IV。不要用时间戳、不要用递增整数、不要用固定的字符串。IV 不需要保密,但必须不可预测——攻击者如果能猜到 IV,就可以做针对性的分析。
第二,每次加密都用新的 IV。IV 不能跨次复用,也不能在同一份数据的不同分段之间复用。
第三,IV 要和密文一起传递。因为解密端需要它。常见的做法是把 IV 明文拼接在密文前面,接收方读取固定长度的前缀作为 IV,剩下的作为密文。IV 不需要加密,它本来就允许公开。
需要强调的是,IV 的长度必须与分组长度一致,AES 是 16 字节。长度不对会导致加密失败或结果错误。
为什么不把 IV 当密钥保管
有些人出于「少传一个参数」的考虑,把 IV 硬编码在代码里,或者当成第二个密钥保管。这是对 IV 作用的误解。
密钥需要保密,因为知道密钥就能解密任何用该密钥加密的数据。IV 只需要不可预测,因为它只影响单次加密的结果——即使攻击者知道了 IV,只要密钥安全,他也无法解密。反过来,即使 IV 完全保密,只要它被重复使用,前面说的那些攻击仍然成立。
所以 IV 的价值在「随机且不重复」,而不在「保密」。把精力花在保管 IV 上是搞错了重点,真正的关键是每次生成新的随机 IV。
检查清单
- IV 的作用是让相同的明文每次加密产生不同的密文
- IV 必须随机生成,且每次加密都不同,长度等于分组长度(AES 为 16 字节)
- CBC 下 IV 重复会泄露「明文前缀是否相同」,并可能被用于选择明文攻击
- CTR 与 OFB 下 IV 重复会导致密钥流重用,两段密文异或即得两段明文异或
- IV 不需要保密,但必须不可预测,应与密文一起传递
- 不要把 IV 硬编码或当密钥保管,重点是保证随机与不重复