一、FF1 算法简介:为什么需要它?
1.1 从“格式保留”这个怪需求说起
做密码学的人都知道,AES、DES 这类经典算法,加密后输出的是乱码。但实际业务中,很多时候你没法用乱码。
举个例子:你手上有 16 位信用卡卡号,数据库要存加密版本。如果用 AES 加密,出来是 32 字节的二进制串,你的表结构得大改,索引全废,业务接口也得重写。这不现实。
所以就有了格式保留加密(Format-Preserving Encryption,FPE)。说白了,就是加密后数据格式跟原来一模一样——信用卡号加密完还是 16 位数字,身份证号加密完还是 18 位带“X”的串。
核心目标:密文在语义格式上与明文相同,但内容在计算上不可区分。
1.2 FF1 的标准化之路
我最早接触 FPE 是在 2010 年左右,那时候各家实现都是自己写的,调个参数就能整出兼容性问题。
后来美国国家标准与技术研究院(NIST)看不下去了。他们在 2016 年正式发布了 SP 800-38G,把 FF1 和 FF3 定为标准算法。其中 FF1 是主力。
为什么要叫“FF”?其实是“Feistel-based FPE”的意思。FF1 用了七轮 Feistel 网络,轮函数里塞的是 AES 或 TDES。
我的一个经验:如果你在合规场景(比如 PCI DSS、GDPR 敏感字段脱敏)里做 FPE,FF1 几乎是唯一的选择。因为它是 NIST 批准的,能过审计。
1.3 标准规范有哪些?
搞 FF1 用的主要参考文档就这三份:
| 文档 | 发布机构 | 说明 |
|---|---|---|
| NIST SP 800-38G | NIST | FF1 和 FF3 的正式定义,必读 |
| ISO/IEC 24727-3 | ISO | 身份认证领域对 FF1 的引用 |
| ANSI X9.119 | ANSI | 金融支付领域的 FPE 规范 |
嗯,我建议你在做项目前,先把 SP 800-38G 的参数字段 过一遍。因为里面有些边界条件,比如 radix 的取值范围、minlen 和 maxlen 的约束,一旦踩坑就是兼容性问题。
曾经有同事把 radix 设为 36(字母+数字)然后传了个全小写字符串做测试,结果生产环境里数据带了大写字母,加密长度算出来不对,直接崩溃。所以啊,字符集和 radix 必须严格对应。
1.4 FF1 的参数结构(知识体系图)
下面的 SVG 图把 FF1 的核心参数和标准来源串了一下。你仔细看,一层一层往下拆,就是整个算法配置的主线。
1.5 标准化流程总结:模式级与元素级
我习惯把 FF1 的参数配置分成两层:模式级配置 和 元素级配置。这样做的好处是——你在做系统集成时,可以先固定模式级,然后把元素级暴露给业务方。
- 模式级:密钥、轮函数选择、Tweak 长度。一个系统通常只定一套。
- 元素级:radix、minlen、maxlen、字符集映射。每类敏感字段各一套。
一个小技巧:Tweak 别用固定值。你可以把表的行 ID 或时间戳放进去做分隔,这样就算两条内容完全相同的明文,加密出来结果也不一样——嗯,你想得到,这是语义安全的基本要求。
1.6 我踩过的坑:FF1 的版本兼容
说实话,FF1 算法在 2016 年之后其实有过一次隐性修订。NIST 在 2019 年发布了 SP 800-38G Rev. 1,把 FF1 的 numRounds 从“至少 7 轮”改成了“精确 7 轮”。
为什么改?因为早期有些实现偷懒用了 6 轮,结果安全性评估没通过。我遇到过一家第三方加密机,固件里写的 FF1 用了 8 轮,跟标准 7 轮互解不了……后来才知道是他们自己拍脑袋加的。
教训:跨系统做 FF1 对接时,必须两边拿同一份参考实现(比如 NIST 官方的 FF1 Python 测试向量)互相验证一遍。否则联调时才暴露兼容问题,比重新改表结构还痛苦。
1.7 现在你该怎么用 FF1?
- 确定使用场景:数据库脱敏、日志脱敏、API 响应字段加密
- 选择底层加密算法:多数场景用 AES-128,合规要求高的用 AES-256
- 定义 radix 和字符集:数字就用 radix 10,数字+字母就用 radix 36
- 设定 Tweak:至少 8 字节,建议结合业务主键
- 测试向量验证:NIST 官网有供下载的 SP 800-38G 测试用例
这一章我就给你把 FF1 的历史、标准、核心参数和常见坑过完了。记得把 SVN 图上那几项参数记清楚——配置 FF1 其实就是把这些参数填对、让它们之间不打架。