简介

Blowfish是由Bruce Schneier在1993年发明的对称密钥分组加密算法,类似的DES和AES都是分组加密算法,Blowfish是用来替代DES算法出现的,并且Blowfish是没有商用限制的,任何人都可以自由使用。

对比而言,虽然AES也是一种密码强度很高的对称密码算法,但是如果需要商用的话要向NIST支付授权费用

加密流程

Blowfish算法流程目录


一、密钥扩展

  1. 初始化静态参数

    • P数组(18个元素)
    • S盒(4×256元素)
  2. 融合用户密钥

    • 密钥循环填充 → 异或修改P数组
  3. 动态生成参数

    • 加密全零块 → 更新P数组
    • 重复加密 → 更新4个S盒

二、加密流程

  1. 拆分明文

    • 64位分组 → 左32位(L) + 右32位(R)
  2. 16轮Feistel迭代

    • 每轮操作:
      • L异或P值 → 输入F函数
      • R异或F结果 → 交换L/R
  3. 最终输出

    • 末轮不交换 → 异或P[16]/P[17]
    • 合并为64位密文

循环流程

F函数内部流程

三、核心模块

  1. F函数

    • 拆分4字节 → 查4个S盒
    • 加法+异或混合运算
  2. 动态S盒

    • 每个密钥生成唯一S盒
    • 通过迭代加密随机化

实现过程

一、密钥扩展过程(密钥→P数组+S盒)

输入:用户密钥(4~56字节)
输出:初始化后的P数组(18个32位字)和4个S盒(每个256个32位字)

步骤1:初始化P数组和S盒

  • P数组:初始包含18个固定32位值(来自圆周率π的小数部分)
    1
    2
    3
    4
    5
    6
    7
    P = [
    0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344,
    0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89,
    0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
    0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
    0x9216D5D9, 0x8979FB1B # 注意共18个元素
    ]
  • S盒:4个盒,每个盒256个固定32位值(同样来自π)

步骤2:处理用户密钥

  • 示例密钥:假设用户密钥为 b"SecretKey"(8字节)
  • 密钥转换:将密钥拆分为32位字数组
    1
    2
    3
    4
    5
    key_bytes = b"S(0x53) e(0x65) c(0x63) r(0x72)..."
    key_words = [
    0x53656372, # 前4字节 "Secr"
    0x65744B65 # 后4字节 "etKe"
    ]

步骤3:异或P数组

  • 循环扩展密钥:将密钥循环重复至18个元素
    1
    expanded_key = [0x53656372, 0x65744B65, 0x53656372, 0x65744B65, ...] (共18个)
  • 逐元素异或P[i] = P[i] XOR expanded_key[i]
    1
    2
    3
    P[0] = 0x243F6A88 ^ 0x536563720x775A09FA
    P[1] = 0x85A308D3 ^ 0x65744B650xE0D743B6
    ...

步骤4:初始化P数组和S盒

使用全0数据块加密生成最终P和S:

在上面操作,我们生成了初始化的P数组和S-box。

blowfish认为这样还不够安全,不够随机。

我们还需要进行一些操作来生成最终的K数组。

首先我们取一个全为0的64bits,然后P数组和S-box,应用blowfish算法,生成一个64bits。

将这个64bits分成两部分,分别作为新的P1 和 P2。

然后将这个64bits作为输入,再次调用blowfish算法,作为新的P3 和 P4。

依次类推,最终生成所有P数组中的元素。

4个S-box的数组也按照上面的流程来进行生成。从而得到最终的S-box。

  1. 初始化加密器

    • 左半部分 L = 0x00000000
    • 右半部分 R = 0x00000000
  2. 加密过程

    • 用当前P数组加密 (L, R)
    • 结果更新P数组前两个元素
      1
      2
      3
      4
      5
      6
      7
      8
      # 第一次加密
      new_L, new_R = encrypt(L, R)
      P[0], P[1] = new_L, new_R

      # 第二次加密
      new_L, new_R = encrypt(new_L, new_R)
      P[2], P[3] = new_L, new_R
      ...
  3. 循环处理

    • 重复这个过程直到更新完所有18个P元素
    • 接着用相同方式更新4个S盒(每个S盒256元素)
      UnIIS

二、加密全流程详解(64位分组)

示例明文0x0123456789ABCDEF

步骤1:初始拆分

  • 拆分为左右两部分(各32位):
    1
    2
    L = 0x01234567  # 高32位
    R = 0x89ABCDEF # 低32位

步骤2:16轮Feistel循环

循环流程

F函数内部流程

  1. 输入处理与轮操作

    • 输入为一个64位数据块 ( X_m ),将其均分为高32位 ( X_L ) 和低32位 ( X_R )。
    • 在每一轮中:
      • 将 ( XL ) 与当前轮的轮密钥 ( P_r ) 进行异或操作,得到中间结果 ( X{L1} )。
      • 将 ( X{L1} ) 输入 F函数 计算,输出结果与 ( X_R ) 进行异或操作,得到中间结果 ( X{R1} )。
      • 交换 ( X{L1} ) 和 ( X{R1} ) 的位置,作为下一轮的输入 ( X_L ) 和 ( X_R )。
  2. 最终轮处理

    • 完成16轮迭代后,不再交换 ( X_L ) 和 ( X_R ) 的位置。
    • 将 ( XL ) 与 ( P{18} ) 异或,( XR ) 与 ( P{17} ) 异或。
    • 将异或后的结果按顺序拼接(高32位为处理后的 ( X_L ),低32位为处理后的 ( X_R )),生成最终的64位加密数据块 ( X_c )。

伪代码

每一轮操作(i从0到15):

1
2
3
4
5
6
7
8
9
10
for i in 0..15:
# (1) 左半部分异或当前P值
L = L ^ P[i]

# (2) 右半部分异或F函数结果
R = R ^ F(L)

# (3) 交换左右部分(最后一轮不交换)
if i != 15:
L, R = R, L

第1轮示例(i=0):

  1. L = 0x01234567 ^ P[0]

    • 假设 P[0] = 0x775A09FA
    • L = 0x01234567 ^ 0x775A09FA = 0x76794C9D
  2. 计算 F(L)

    • 0x76794C9D 拆分为4字节:0x76, 0x79, 0x4C, 0x9D
    • 查S盒:
      • S0[0x76] → 假设为 0xA1B2C3D4
      • S1[0x79]0xE5F6A7B8
      • S2[0x4C]0x9E8D7C6B
      • S3[0x9D]0x5A4B3C2D
    • 计算:
      1
      2
      3
      4
      F = (S0[0x76] + S1[0x79]) ^ S2[0x4C] + S3[0x9D]
      = (0xA1B2C3D4 + 0xE5F6A7B8) → 0x187A96B8C (取后32位: 0x87A96B8C)
      ^ 0x9E8D7C6B0x192417E7
      + 0x5A4B3C2D0x736F5414
  3. R = 0x89ABCDEF ^ F(L)

    • R = 0x89ABCDEF ^ 0x736F5414 = 0xFAE499FB
  4. 交换L和R

    • 新L = 0xFAE499FB
    • 新R = 0x76794C9D

后续轮次:重复上述过程,使用P[1]到P[15]

步骤3:最终处理

  1. 交换最后一次结果(补偿第16轮未交换):

    1
    L, R = R, L  # 交换回来
  2. 异或最后两个P值

    1
    2
    L = L ^ P[17]
    R = R ^ P[16]
  3. 合并为密文

    1
    密文 = (L << 32) | R

三、完整流程示意图

1
2
3
4
5
6
7
8
初始L/R

├─轮1: L^=P[0], R^=F(L), 交换
├─轮2: L^=P[1], R^=F(L), 交换
│ ...
├─轮16:L^=P[15], R^=F(L), 不交换

└─最终: 交换L/R → L^=P[17], R^=P[16] → 合并

四、为什么这样设计?

  1. 密钥扩展

    • 通过多次加密生成P/S,使每个密钥的S盒不同
    • 密钥参与修改P数组,增强密钥相关性
  2. Feistel结构

    • 每轮只加密一半数据,解密结构相同(只需逆序P数组)
    • 交换操作确保充分扩散
  3. F函数设计

    • S盒提供非线性,加法/异或提供线性混合
    • 4次查表+2次加法+1次异或,平衡安全与效率

五、实战示例

假设某轮计算后:

  • P[0] = 0x243F6A88
  • L = 0x00000000
  • R = 0x00000000

加密过程

  1. L ^= P[0]0x243F6A88
  2. 计算 F(L)
    • 拆分:0x24, 0x3F, 0x6A, 0x88
    • S0[0x24] = 0x85A308D3
    • S1[0x3F] = 0x13198A2E
    • S2[0x6A] = 0x03707344
    • S3[0x88] = 0xA4093822
    • 计算:
      1
      2
      3
      h = (0x85A308D3 + 0x13198A2E) & 0xFFFFFFFF0x98BC9301
      h ^= 0x037073440x9BCCE045
      h = (h + 0xA4093822) & 0xFFFFFFFF0x3FD61867
  3. R ^= h0x00000000 ^ 0x3FD61867 = 0x3FD61867
  4. 交换 → L=0x3FD61867, R=0x243F6A88

六、常见疑问解答

Q1:为什么需要18个P值?
A:16轮加密每轮用1个P值(P[0]~P[15]),最后交换后用P[16]和P[17]

Q2:S盒如何保证安全性?
A:S盒通过π生成,具有高度非线性,且每个密钥生成不同的S盒

Q3:如果密钥全为0会怎样?
A:P数组初始异或后不变,但后续通过加密生成新的P/S,仍具备安全性