Blowfish加密算法的密钥扩展过程
Blowfish 是一种对称分组密码算法,由 Bruce Schneier 于 1993 年设计。它使用可变长度的密钥(32 位到 448 位)和 64 位分组大小,采用 Feistel 网络结构。Blowfish 的密钥扩展过程相对复杂,因为它需要生成一个较大的子密钥数组(包括 18 个 32 位的 P 数组和 4 个 32×256 的 S 盒)。下面我们将详细讲解密钥扩展的每一步。
1. 算法背景与结构概述
Blowfish 包含两个主要部分:
- 数据加密部分:16 轮的 Feistel 网络,每轮使用一个来自 P 数组的子密钥和一个 S 盒的替换。
- 密钥扩展部分:将用户提供的可变长度密钥(最多 448 位)扩展为:
- 18 个 32 位的子密钥(P₁ 到 P₁₈),用于每轮的轮密钥加。
- 4 个 S 盒(每个 S 盒包含 256 个 32 位条目),用于轮函数中的替换操作。
密钥扩展的核心思想是:用用户密钥初始化 P 数组和 S 盒,然后通过迭代加密一个全零的 64 位分组,用结果逐步替换 P 数组和 S 盒的条目,使得最终的子密钥与用户密钥相关。
2. 密钥扩展的详细步骤
我们逐步分解密钥扩展过程:
步骤 1:初始化 P 数组和 S 盒
Blowfish 预先定义了固定的初始值:
- P 数组的初始值由圆周率 π 的小数部分的前 18×32 位组成(十六进制表示)。例如:
P₁ = 0x243f6a88, P₂ = 0x85a308d3, ..., P₁₈ = 0x8979fb1b。 - 4 个 S 盒(每个 256 个 32 位条目)同样从 π 的小数部分派生,例如 S₁[0] = 0xd1310ba6, ..., S₄[255] = 0x2a8b7a4a。
这些初始值是公开的常数,与用户密钥无关。
步骤 2:用用户密钥异或更新 P 数组
用户密钥是一个字节数组,长度为 1 到 56 字节(8 到 448 位)。设用户密钥为 K[0], K[1], ..., K[k-1](k 为字节数,1 ≤ k ≤ 56)。
- 将用户密钥循环重复,直到覆盖整个 P 数组(18 个 32 位值)。具体操作:
- 将用户密钥的字节重新解释为一个 32 位字的数组。例如,如果密钥是 "ABCD",则四个字节 'A'(0x41)、'B'(0x42)、'C'(0x43)、'D'(0x44) 可以组成一个 32 位字 0x41424344。
- 对于 i 从 1 到 18:
Pᵢ = Pᵢ XOR (密钥的第 ((i-1) mod 密钥字数) 个字)。
注意:如果密钥长度不是 4 字节的整数倍,最后一个字可能用零填充或循环填充(取决于实现,但标准方法是循环重复密钥字节直到填满 32 位)。
步骤 3:用全零分组加密更新 P 数组
现在,我们用一个 64 位全零分组(左半部分 L=0x00000000,右半部分 R=0x00000000)进行迭代加密,并用加密结果更新 P 数组。这个过程称为“自修改”:
- 用当前的 P 数组和 S 盒对全零分组执行完整的 Blowfish 加密(16 轮 Feistel 网络,包含轮函数 F 和每轮的 P 数组异或)。
- 加密结果得到两个 32 位输出 L' 和 R'。
- 用 L' 替换 P₁,用 R' 替换 P₂。
- 再次用当前的 P 数组和 S 盒加密新的 L' 和 R'(即上一步的输出作为输入)。
- 用新的加密结果的左半部分替换 P₃,右半部分替换 P₄。
- 重复这个过程,直到更新完 P₁ 到 P₁₈。总共需要 9 次加密(因为每次加密更新两个 P 条目)。
步骤 4:用更新后的 P 数组加密更新 S 盒
在 P 数组全部更新后,用类似的方法更新 4 个 S 盒(每个 S 盒有 256 个条目):
- 继续使用当前的 P 数组和 S 盒加密全零分组(但注意此时 P 数组已经更新过,不再是初始值)。
- 用加密结果的左半部分替换 S₁[0],右半部分替换 S₁[1]。
- 再次加密上一次的输出,用结果替换 S₁[2] 和 S₁[3]。
- 重复这个过程,直到 S₁ 的所有 256 个条目都被更新。
- 然后以相同的方式更新 S₂、S₃ 和 S₄。
总共需要 512 次加密(因为每个 S 盒 256 个条目,每次加密更新两个条目,4×256/2 = 512 次加密)。加上更新 P 数组的 9 次加密,整个密钥扩展过程需要 521 次加密操作。
步骤 5:完成扩展
当所有 P 数组和 S 盒更新完毕后,密钥扩展过程结束。此时的 P 数组和 S 盒与用户密钥相关,并用于后续的数据加密。
3. 关键点与注意事项
- 计算开销大:Blowfish 的密钥扩展需要 521 次加密,相对较慢。这增加了暴力破解的难度,但也使得密钥切换成本较高(不适合频繁更换密钥的场景)。
- 密钥长度可变:用户密钥长度可以在 32 位到 448 位之间。如果密钥长度不足 4 字节的倍数,需要循环重复字节来填充。
- 安全性依赖 S 盒:S 盒在扩展过程中被密钥材料“随机化”,增强了算法的抗攻击能力。
- 不可逆性:从最终的 P 数组和 S 盒无法反推出用户密钥,这提供了额外的安全保护。
4. 示例演示(简化)
假设用户密钥是 4 字节的 0x00000000(仅为示例,实际密钥应随机):
- 初始 P 数组为固定值,如 P₁=0x243f6a88, P₂=0x85a308d3, ...
- 用密钥异或更新 P 数组:P₁ = P₁ XOR 0x00000000 = 0x243f6a88(不变,因为密钥全零)。
- 用全零分组加密:加密结果假设为 (L', R') = (0x4ef99745, 0x6198dd78)(实际值取决于初始 P 和 S 盒)。
- 更新 P₁=0x4ef99745, P₂=0x6198dd78。
- 继续加密和更新,直到 P 数组和 S 盒全部更新。
通过这个过程,即使简单的密钥也能产生复杂的子密钥集合。
5. 总结
Blowfish 的密钥扩展通过迭代加密和替换,将短的用户密钥扩展为大量的子密钥材料(4168 字节的 S 盒和 72 字节的 P 数组)。这种设计增加了密钥设置的复杂度,提高了对穷举攻击的抵抗力,但也导致密钥调度较慢。理解这一过程有助于深入掌握 Blowfish 算法的安全性和实现细节。