FF1 算法:NIST 标准详解与实战
FF1 是目前业界最成熟的 FPE 方案。我自己的项目中,大部分敏感数据保护场景都优先选它。为什么?因为它是 NIST SP 800-38G 标准,经过严格的安全性分析,不用自己造轮子。
说白了,FF1 就是个双调结构的 Feistel 网络。你想想看,它怎么保证密文跟明文格式一致?核心就一句话:在同一个字符集里做置换,不改变长度,不引入非法字符。
算法核心:Feistel 网络 + 域调整
FF1 的结构其实不复杂。它把输入分成左右两半,然后反复做「将右半部分代入轮函数,结果与左半部分异或」这个操作。轮函数里使用了 AES 或 TDES 分组密码,再结合一个叫「数字化中间值」的技巧,保证输出整数在正确的数值范围内。
我在实现时最头疼的是那个「域调整」步骤。说白了,就是每次加密前要根据当前轮数和 tweak 计算一个调整值,再把它塞进分组密码里。这个环节搞错了,密文根本解不回来。
参数设置:五个核心参数
调用 FF1 需要五个参数,少了哪个都不行。我列个表方便你对照:
| 参数 | 含义 | 典型值 | 我踩过的坑 |
|---|---|---|---|
radix | 字符集基数 | 10(数字)、36(字母数字) | 银行卡号用 10,别设成 16 |
minlen | 最小明文长度 | 2 或 4 | 太小的长度会降低安全性 |
maxlen | 最大明文长度 | 根据场景定,如 19 | 必须 ≥ minlen + 2 |
key | 对称加密密钥 | 16/24/32 字节 (AES-128/192/256) | 别用弱密钥! |
tweak | 附加调整值 | 0~7 字节的二进制数据 | 长度固定后不可随意更改 |
我的经验:radix 一旦定下来就不要变。tweak 可以当作「应用场景号」,比如同一个数据库里多个表,我用不同 tweak 来区分,这样明文相同但密文不同。
使用示例:Python 实现
我一般用 ff1 这个 Python 库,简单直接。但记得自行实现字符集的编码解码。下面是一个处理 16 位数字账号的例子:
from ff1 import FF1
from Crypto.Cipher import AES
# 参数准备
key = b'\x01\x23\x45\x67\x89\xab\xcd\xef' * 2 # AES-128
tweak = b'\x00\x00\x00\x00'
radix = 10
min_len = 2
max_len = 19
ff1 = FF1(key=key, tweak=tweak, radix=radix, min_len=min_len, max_len=max_len)
plaintext = "1234567890123456" # 16位数字
ciphertext = ff1.encrypt(plaintext)
decrypted = ff1.decrypt(ciphertext)
print(f"明文:{plaintext}")
print(f"密文:{ciphertext}")
print(f"解密:{decrypted}")
注意:radix=10 时,输入必须是纯数字字符串。我曾误传了字母,程序直接崩溃。后来我加了校验,不在参数里解决的坑,就提前拦住。
我曾经犯过的错:最开始我用同一个 key 处理多个业务表,结果两个表里有相同明文,密文也相同,被安全审计发现。后来加了不同的 tweak(比如表名哈希),问题解决。
FF1 加密流程详解
下图是我自己画的一张核心流程图,概括了 FF1 的一次加密调用:
嗯,这里要注意:图中展示的是 10 轮迭代,但实际轮数取决于 maxlen 的具体值。标准规定至少 10 轮,但 maxlen 大于 2^10 时需要增加。我一般直接取 16 轮确保安全。
避坑指南:我亲手踩过的三个坑
- tweak 长度固定:FF1 的 tweak 必须是 0~7 字节。我当初以为可以无限长,结果库报错说 tweak 超出范围。后来改用 hash 截断解决。
- 字符集转化:FF1 内部用整数运算,外面传字符串。你自己写
str_to_int和int_to_str时,必须保证一一对应。我试过数字前补零的问题,解密后变短了,后来强制固定长度。 - 密钥管理:别把 key 硬编码在代码里。建议用 KMS 或者环境变量,最起码也得用
getpass读取。我曾经在 Git 仓库里不小心提交了测试 key,被同事发现。
性能与安全性权衡
FF1 的计算量主要来自轮函数里的分组密码调用。10 轮就要调用 10 次 AES。我测过,用 Python 处理 16 位数字,每秒大概能跑 2000 次加密。如果你需要更高吞吐,建议用 C/C++ 实现,或切换到 ff3(但安全性争议较大)。
记得吗?我在那个支付系统的项目中,所有卡号走 FF1 加密,每秒上万笔交易。后来换用了 OpenSSL 引擎加速 AES,延迟降低了 70%。
重点总结:FF1 的核心优势是标准化、安全边界清晰。你只要把 radix、minlen、maxlen、key、tweak 五个参数设置合理,其余交给算法就行。我在多个合规审计中,FF1 方案一次过关。