4. FF1 算法总体架构:参数、密钥长度、分组大小
好,我们进入 FF1 算法的核心。说实话,当年我第一次看 NIST 的文档,感觉挺绕的。但真正动手做项目后,发现骨架就那么几个东西。你只要抓住三个要素——参数、密钥、分组大小——基本上就摸透了。
FF1 是基於 Feistel 网络设计的。它不像传统 AES 那种整块加密,而是把一个数字串拆成左右两半,然后来回搓揉。我觉得它的设计理念,说白了就是「用标准加密构件,搞定任意格式的数据」。
4.1 算法总体架构
先看一张图,我把它叫做「FF1 的黑盒剖面」。它不是你想象的那种复杂迷宫,其实输入输出很清晰。
你看,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,这样同一张卡在不同机构加密结果不同。
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 这类短数据场景里更受欢迎的原因。
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 的总体架构参数部分就讲到这里。密钥长度固定、分组长度有硬性门槛、Tweak 和 radix 控制数据域。这三个东西你记住了,后面设计加密方案时就不会走弯路。