四、经典流密码:RC4 算法原理、Python 实现与弱点分析

RC4 是由 Ron Rivest 于 1987 年为 RSA Security 设计的一种流密码算法。因其实现简单、运算速度快(尤其适合软件实现),曾广泛应用于 SSL/TLS、WEP、WPA 和 PDF 加密等协议中。然而,随着密码分析技术的发展,RC4 的多种弱点被揭示,目前已被主流安全标准弃用。本章将深入剖析 RC4 的算法原理、Python 实现,并重点演示其最致命的弱点——密钥重用攻击。

4.1 RC4 算法原理

RC4 是一种基于伪随机数生成器(PRNG)的流密码。它通过一个密钥调度算法(KSA)初始化一个 256 字节的状态向量 S,然后通过伪随机生成算法(PRGA)生成密钥流,最后将密钥流与明文进行异或运算得到密文。

RC4 流密码加密/解密流程 密钥 K KSA 初始化 S 盒 PRGA 生成密钥流 明文 P XOR 异或运算 密文 C 说明: 解密过程与加密完全相同:使用相同的密钥 K 生成相同的密钥流,将密文 C 与密钥流异或即可恢复明文 P。

4.1.1 密钥调度算法(KSA)

KSA 负责将可变长度的密钥(通常为 40~256 位)扩展为 256 字节的初始状态向量 S。其核心步骤包括:

  1. 初始化 S 盒:将 S[0] 到 S[255] 依次赋值为 0 到 255。
  2. 密钥填充:将密钥 K 重复填充到 256 字节的临时数组 T 中。
  3. 置换打乱:使用 T 数组对 S 盒进行 256 次置换操作,使 S 盒呈现伪随机状态。

KSA 伪代码:

for i from 0 to 255:
    S[i] = i
    T[i] = K[i mod keylen]

j = 0
for i from 0 to 255:
    j = (j + S[i] + T[i]) mod 256
    swap(S[i], S[j])

4.1.2 伪随机生成算法(PRGA)

PRGA 从 KSA 生成的 S 盒中不断输出伪随机字节,形成密钥流。每输出一个字节,S 盒的状态都会更新。

PRGA 伪代码:

i = 0
j = 0
while True:
    i = (i + 1) mod 256
    j = (j + S[i]) mod 256
    swap(S[i], S[j])
    t = (S[i] + S[j]) mod 256
    output S[t]

4.2 Python 实现 RC4

以下是一个完整的 RC4 加密/解密 Python 实现。该实现严格遵循 KSA 和 PRGA 算法,支持任意长度的密钥和明文。

class RC4:
    def __init__(self, key: bytes):
        """初始化 RC4 密码对象,执行 KSA 算法"""
        if not key:
            raise ValueError("密钥不能为空")
        self.S = list(range(256))
        j = 0
        keylen = len(key)
        for i in range(256):
            j = (j + self.S[i] + key[i % keylen]) & 0xFF
            self.S[i], self.S[j] = self.S[j], self.S[i]

    def _prga(self) -> int:
        """PRGA 算法:生成下一个密钥流字节"""
        # 注意:这里使用实例变量保持状态,实际使用中应重置
        if not hasattr(self, '_i'):
            self._i = 0
            self._j = 0
        self._i = (self._i + 1) & 0xFF
        self._j = (self._j + self.S[self._i]) & 0xFF
        self.S[self._i], self.S[self._j] = self.S[self._j], self.S[self._i]
        t = (self.S[self._i] + self.S[self._j]) & 0xFF
        return self.S[t]

    def encrypt(self, plaintext: bytes) -> bytes:
        """加密:生成密钥流并与明文异或"""
        # 重置 PRGA 状态(每次加密/解密重新开始)
        self._i = 0
        self._j = 0
        ciphertext = bytearray()
        for byte in plaintext:
            keystream_byte = self._prga()
            ciphertext.append(byte ^ keystream_byte)
        return bytes(ciphertext)

    def decrypt(self, ciphertext: bytes) -> bytes:
        """解密:与加密操作完全相同"""
        return self.encrypt(ciphertext)

# 使用示例
if __name__ == "__main__":
    key = b"SecretKey"
    plaintext = b"Hello, RC4 Stream Cipher!"

    rc4 = RC4(key)
    ciphertext = rc4.encrypt(plaintext)
    print(f"密文 (十六进制): {ciphertext.hex()}")

    # 解密
    rc4_dec = RC4(key)
    decrypted = rc4_dec.decrypt(ciphertext)
    print(f"解密结果: {decrypted.decode('utf-8')}")

实现要点:

  • 每次加密或解密操作前,必须重置 PRGA 的 i 和 j 计数器(即重新创建 RC4 对象或显式重置状态)。
  • 密钥长度建议至少 128 位(16 字节),以提供基本安全强度。
  • RC4 的加密和解密使用完全相同的函数,这是流密码的对称特性。

4.3 RC4 的弱点分析:密钥重用攻击

RC4 最致命的弱点在于密钥重用。如果使用相同的密钥加密两个不同的明文,攻击者可以通过异或两个密文直接消除密钥流,从而获得两个明文的异或值。结合语言统计特性,可以进一步恢复出原始明文。

4.3.1 攻击原理

假设使用相同的密钥 K 加密两个明文 P1 和 P2:

  • C1 = P1 ⊕ KS
  • C2 = P2 ⊕ KS

攻击者计算 C1 ⊕ C2 = (P1 ⊕ KS) ⊕ (P2 ⊕ KS) = P1 ⊕ P2

一旦得到 P1 ⊕ P2,攻击者可以利用自然语言中的空格、字母频率等统计特征,逐步恢复出 P1 和 P2 的完整内容。

密钥重用攻击原理 相同密钥 K 相同密钥流 KS 明文 P1 明文 P2 C1 = P1⊕KS C2 = P2⊕KS C1 ⊕ C2 = P1 ⊕ P2

4.3.2 攻击演示代码

以下 Python 代码演示了密钥重用攻击的基本过程:

def rc4_keystream_reuse_attack(ciphertext1: bytes, ciphertext2: bytes):
    """
    演示密钥重用攻击:计算两个密文的异或,得到明文的异或值。
    在实际攻击中,攻击者会利用语言统计特性从 P1⊕P2 中恢复明文。
    """
    if len(ciphertext1) != len(ciphertext2):
        raise ValueError("两个密文长度必须相同")
    
    xor_result = bytearray()
    for b1, b2 in zip(ciphertext1, ciphertext2):
        xor_result.append(b1 ^ b2)
    return bytes(xor_result)

# 模拟攻击场景
key = b"FixedKey123"
plaintext1 = b"Hello Alice, this is a secret message."
plaintext2 = b"Hello Bob,   this is another secret."

rc4_1 = RC4(key)
rc4_2 = RC4(key)

c1 = rc4_1.encrypt(plaintext1)
c2 = rc4_2.encrypt(plaintext2)

xor_plaintexts = rc4_keystream_reuse_attack(c1, c2)
print(f"P1 ⊕ P2 (十六进制): {xor_plaintexts.hex()}")

# 验证:如果知道 P1,可以恢复 P2
recovered_p2 = bytes([p1 ^ x for p1, x in zip(plaintext1, xor_plaintexts)])
print(f"恢复的 P2: {recovered_p2.decode('utf-8')}")

4.3.3 其他已知弱点

弱点类型 描述 影响
初始输出偏差 PRGA 输出的前几个字节存在统计偏差,某些字节出现的概率更高 攻击者可以预测密钥流的前 256 字节,降低有效密钥强度
密钥相关攻击 存在弱密钥对,使得 S 盒初始化后状态可预测 WEP 协议中利用此弱点可在几分钟内破解密钥
状态恢复攻击 通过分析大量密钥流输出,可以部分或完全恢复 S 盒状态 理论上可预测后续密钥流,实现完全破解

安全警示:由于上述弱点的存在,RC4 已被 IETF(RFC 7465)明确禁止在 TLS 中使用。任何新的安全通信系统都不应再采用 RC4 算法。对于遗留系统,应尽快迁移到 AES-CTR 或 ChaCha20 等现代流密码。

4.4 本章小结

  • RC4 是一种简单高效的流密码,由 KSA 和 PRGA 两部分组成。
  • Python 实现 RC4 时需注意 PRGA 状态的正确重置。
  • 密钥重用是 RC4 最致命的弱点,攻击者可以通过异或两个密文消除密钥流,进而恢复明文。
  • RC4 还存在初始输出偏差、弱密钥等安全缺陷,已被现代密码标准弃用。