4. FF1 算法总体架构:参数、密钥长度、分组大小

好,我们进入 FF1 算法的核心。说实话,当年我第一次看 NIST 的文档,感觉挺绕的。但真正动手做项目后,发现骨架就那么几个东西。你只要抓住三个要素——参数、密钥、分组大小——基本上就摸透了。

FF1 是基於 Feistel 网络设计的。它不像传统 AES 那种整块加密,而是把一个数字串拆成左右两半,然后来回搓揉。我觉得它的设计理念,说白了就是「用标准加密构件,搞定任意格式的数据」。

4.1 算法总体架构

先看一张图,我把它叫做「FF1 的黑盒剖面」。它不是你想象的那种复杂迷宫,其实输入输出很清晰。

FF1 算法核心架构与参数关系 主密钥 K Tweak T 明文 X Radix (基数) 2 ≤ radix ≤ 2¹⁶ FF1 四轮 Feistel + 分组调整 10 轮循环 · 每轮使用 CMAC 生成子密钥 约束: 分组长度 > 100, 且 n > 2 * minlen(radix) 密文 Y 分组长度 n n 来自 X 长度 参数构成:K + T + radix + X + n 密钥长度固定 128/192/256 bit · 分组长度 ≥ 100 · radix 由实际数据集决定

你看,FF1 接收五个核心参数:主密钥 K、Tweak T、明文 X、基数 radix,以及隐含在数据中的分组长度 n。这里我想强调一个点,明文 X 不是任意二进制串,而是一个基数为 radix 的数字序列。比如 radix=10,那 X 就是十进制数字串。

4.2 关键参数详解

4.2.1 主密钥 K

密钥长度这块,FF1 支持 128、192、256 位三种,跟 AES 的密钥长度完全一致。为什么这样设计?说白了,FF1 内部调用的底层加密就是 AES。我遇到过有同事问「能不能用 DES 做底层?」——嗯,标准不支持,别想了。

我个人习惯在项目里统一用 128 位密钥,除非有合规要求。因为 FF1 的瓶颈往往不是密钥长度,而是分组大小限制和轮数,你把密钥加长对性能影响不大,但没必要给自己找麻烦。

4.2.2 Tweak(调整值)

Tweak 是个很有意思的设计。它相当于一个「公开的一次性盐值」,可以在不改变密钥的情况下,产生不同的加密输出。官方规定 Tweak 长度可以在 0 到 2³² bits 之间,也就是最大 512 MB。但真的会有人用这么大吗?我反正是没见过。

实际项目中,Tweak 通常取 8 字节或 16 字节。比如在金融卡号加密场景里,我们会把卡号的 BIN 位或部分 PAN 塞进 Tweak,这样同一张卡在不同机构加密结果不同。

我的经验分享: Tweak 虽然可以很长,但每个字节都要参与 CMAC 计算。我建议别超过 16 字节,否则性能会明显下降。曾经一个项目里有人设了 128 字节的 Tweak,加密耗时直接翻了 3 倍。

4.2.3 Radix(基数)

Radix 决定了你处理的数据域。标准规定 radix 的取值范围是 2 ≤ radix ≤ 2¹⁶。常见值:

  • radix = 10:十进制数字,比如信用卡卡号、身份证号
  • radix = 16:十六进制串,比如 MAC 地址
  • radix = 36:数字 + 小写字母,用于字母数字混合编码
  • radix = 62:数字 + 大小写字母,最常用的字母数字集

这里有个坑:radix 越大,单字符携带的信息量越大,但 Feistel 网络中数字运算的复杂度也会增加。你别想着用 radix=2^16 去处理整个 Unicode 集,虽然标准允许,但实现起来会很痛苦。

4.3 分组长度约束

FF1 最让人头疼的其实不是密钥,而是分组长度限制。我当年第一次落地就栽在这个坑里,拿个 12 位的卡号直接加密,结果返回了一个错误。

标准规定:

  • 分组的元素个数 n 必须满足 n ≥ 100(再次强调,不是比特位,是元素个数)
  • 同时 n > 2 × minlen(radix),其中 minlen(radix) 是满足 2^minlen ≥ radix 的最小整数

举个例子。radix=10 时,minlen(10) = 4(因为 2³=8 < 10, 2⁴=16 ≥ 10)。所以 n 必须大于 2×4=8,同时 n ≥ 100。取大值,最终要求 n ≥ 100。

这意味着你要加密的数据串至少有 100 个字符。如果你要加密的是一个 16 位的信用卡卡号,对不起,不够格。你需要先填充到 100 位,或者分段处理。

4.4 FF1 与 FF3-1 的参数对比

我们做个表对比一下,这样你就能直观看到差距。

参数 FF1 FF3-1
密钥长度 128 / 192 / 256 bit 128 / 192 / 256 bit
分组长度 (元素个数) ≥ 100 ≥ 2
Tweak 长度 0 ~ 2³² bits 0 ~ 2³² bits
Radix 范围 2 ~ 2¹⁶ 2 ~ 2¹⁶
轮数 10 8
底层密码 AES / 3DES AES / 3DES

看到没?最大的区别其实是分组长度下限和轮数。FF3-1 支持小到 2 个元素的分组,这就是为什么它在身份证号、信用卡 CVV 这类短数据场景里更受欢迎的原因。

避坑提示: 我当年做一个医保卡号加密项目,卡号只有 8 位数字。我用了 FF1,死活加密不了。后来查手册才发现,FF1 要求 n ≥ 100。最后切换到 FF3-1 才搞定。如果你遇到短数据,比如 4~20 位数字,别犹豫,直接上 FF3-1。

4.5 初始化过程的代码示意

说了这么多抽象概念,不如看一段实际初始化代码。下面是一个简化的 FF1 上下文构造逻辑,基于 Python 的 pyffx 库风格(实际生产环境请用标准库)。

# FF1 初始化示例(伪代码风格)
from cryptography.ff1 import FF1

key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")  # 128-bit key
tweak = b"2024-FF1"                                      # 8 bytes tweak
radix = 10                                                # 十进制数
plaintext = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"  # 100位数字

cipher = FF1(key, tweak, radix)
ciphertext = cipher.encrypt(plaintext)
print(ciphertext)  # 输出也是100位数字

注意,这里 plaintext 长度是 100 位。如果你传 16 位,库会抛出 ValueError,提示 "msg length must be at least 100"。这就是我刚才说的那个坑,你得提前处理好。

警告: 不要在生产环境里用自己实现的 FF1。标准对 Feistel 轮函数的 PRF 构造有严格的安全要求。我见过有人用简单的 XOR 代替 CMAC,那跟没加密差不多。请务必使用通过 NIST 验证的加密库,比如 OpenSSL 的 FF1 模块或 Python 的 cryptography 库。

好了,FF1 的总体架构参数部分就讲到这里。密钥长度固定、分组长度有硬性门槛、Tweak 和 radix 控制数据域。这三个东西你记住了,后面设计加密方案时就不会走弯路。