对 CBC 模式的一些攻击

Table of Contents

我在超过10个土法炼钢项目里看到有问题 CBC 用法了。

Github 上有一个叫 crypto101 的项目,里面讲了很多经典的密码学话题,很有意思,这篇文章上的攻击方式基本上在那里也能找到。

这篇文章假设使用一种叫做 AES 的加密算法,并使用 CBC 作为加密模式。

1 CBC 加密模式

CBC 的加密模式看起来时下面这个样子:

cbc001.png

要加密 P, 先将分组,每组大小是固定的,这里分为三组 \( P=P_{1}P_{2}P_{3} \),第一组 \( P_{1} \) 首先和 IV 异或,后以 k 为密钥使用加密算法加密,生成第一组密文 \( C_{1} \), 生成的密文作为下一组加密使用的 IV,直到最后一组加密完成。

而 CBC 模式的解密与此类似:

cbc002.png

CBC 模式的参数有三个,\( P, k, IV \),C 是要加密的明文,k 是加密算法的密钥, IV 是 CBC 的初始向量。

2 对可预测的 IV 的攻击

如果这个加密的 IV 是可预测的,那么攻击者可以猜测某个记录的一个字段 \( P_{i} \) 的值为 \( P_{x} \)。首先构造一个数据

\[ P_{M} = IV \oplus C_{i-1} \oplus P_{x} \]

发给服务端,服务端仍然将其加密:

\begin{align} C_{M} & = E(k, IV \oplus P_{M}) \\ & = E(k, C_{i-1} \oplus P_{x}) \\ \end{align}

比较 CM 与 Ci 就知道猜测的明文是否正确。

3 对使用 key 作为 IV 的攻击

假设使用 key 作为 IV, 密文为 \( C=C_{1}C_{2}C_{3} \),攻击者可以构造密文 \( C'=C_{1}ZC_{1} \) 给解密程序解密,其中 Z 值为 0。解开的明文为:

\begin{align} P'_{1}&= D(k, C_{1}) \oplus IV \\ &= P_{1} \\ \end{align} \begin{align} P'_{3} &= D(k, C_{1}) \oplus Z \\ &= D(k, C_{1}) \\ &= P_{1} \oplus IV \\ \end{align}

攻击者获得 \( P'_{1} \) 和 \( P'_{3} \) 后, 可以计算

\begin{align} P'_{1} \oplus P'_{3} &= P_{1} \oplus P_{1} \oplus IV \\ &= IV \end{align}

而 IV 就是 key。

4 CBC 位反转攻击

因为 CBC 使用上一个密文块与解密算法结果亦或得到明文,而密文块在传输过程中是可以被修改的,那么如果猜到 \( P_{i+1} \) 的值,就可以通过构造 X,令 \( C'_{i}=C_{i} \oplus X \) 来修改解密得到的 \( P_{i+1} \),从而产生预想的行为。比如将某个字段修改为 ";admin=1"。

cbc004.png

5 CBC 填充攻击

CBC 模式要求每个加密块都是固定大小的,如果不是固定大小,则将其填充到固定大小。如果使用 PKCS#5 或者 PKCS#7 算法填充,那么填充的值将是缺少的位数。

比如,如果每个块大小为 8 字节,那么填充的字节将是:

块长度 填充字节
0 08 08 08 08 08 08 08 08
1 07 07 07 07 07 07 07
2 06 06 06 06 06 06
3 05 05 05 05 05
4 04 04 04 04
5 03 03 03
6 02 02
7 01

如果接收端可以通知发送者解密是否失败,攻击者可以利用这个特征进行攻击。假设接收端如果发现填充的数字不符合长度要求,则不返回,而解密成功则有返回,返回内容为应用层数据出错或者返回执行成功。

cbc005.png

攻击者可以构造一个 R 作为密文,使得接收端解密后发现填充值是正确的。这很容易办到,只要枚举 R 的最后一个字节,直到接收端有返回为止。这是这里的主要攻击手段。

首先令 R=C, 然后依次尝试改变 R 的每个字节,直到没有返回。假设明文为

\[ p_0p_1p_2p_3p_4030303 \]

则依次改变字节,直到第一个 03 的位置,接收端没有返回: \[ p'_0p'_1p'_2p'_3p'_4050303 \]

这样就可以得到最后一个字节的值。下面假设最后一个字节为 01。那么

\[ D(C_i)[b] \oplus r_b = 01 \]

两边同时亦或 \( r_{b} \) 得

\[ D(C_i)[b] = 01 \oplus r_b \]

这样改变最后一个字节,使得 padding 增加一个字节:

\[ D(C_i)[b] \oplus r_b\oplus 01 \oplus 02 = 01 \oplus 01 \oplus 02 = 02 \]

随后枚举 \( r_{b-1} \) 直到有返回。此时

\[ D(C_i)[b-1] \oplus r_{b-1} = 02 \]

可以换算出

\[ D(C_i)[b-1] = 02 \oplus r_{b-1} \]

如此反复可以逐步获得明文。


By .