3. FPE标准体系:NIST SP 800-38G 与三大算法
聊FPE,绕不开这个文档——NIST SP 800-38G。说白了,它就是FPE领域的“标准圣经”。2016年发布,后来更新了修订版1,定义了三种正式认可的加密模式:FF1、FF2、FF3。我个人第一次接触这份文档是在一个金融客户的项目里,对方要求把自己的16位银行卡号做令牌化,又不准改格式。我当时翻完标准,心里就一个感觉:嗯,这事儿总算有据可依了。
标准体系的骨架
NIST SP 800-38G 不是什么新发明,它实际是把之前散落在学界和工业界的几种构造方法收编了。核心目标很简单:给定任意有限集合(比如0-9数字、字母、汉字编码),输出一个同样格式的密文,且无法被还原为原始数据。
这个目标听起来容易,但做起来坑不少。比如你直接拿AES加密一个10位数字,结果会变成128位二进制,根本不符合格式要求。所以标准里提出了“格式保护加密模式”的通用框架,它由底层分组密码(比如AES或DES,我习惯用AES-256)加上一个可调加密算法组合而成。整个体系大概长这样:
三大算法逐个拆解
FF1 (原名 FFX)
这是业界用得最广的。我记得17年在某支付平台做PCI合规时,选的方案就是FF1+AES-256。为什么?因为它支持任意字符集——数字、字母、中文都行。标准定义了 radix(基数),比如数字就是10,小写字母就是26。FF1最少跑10轮,你可以通过增加轮数来提升安全边际(但别超过16轮,我试过,性能下降太明显)。
FF2 (原名 VAES3)
这个算法是为字母数字混合场景专门优化的。我见过有人在社保号令牌化里用它。它要求最小字符长度12,且在内部把字符集处理成高效二进制。轮数最少12轮,比FF1多两轮。说实话,如果FF1能满足需求,我不太建议用FF2——性能上差个10%~15%。
FF3 (原名 BPS)
FF3的特点是轻量、快速。它只要求7轮,适用于长域(比如64位字符串)。但是!这里有个坑——短域攻击。我曾经看过一篇,FF3在文本域小于2^16时存在信息泄露风险。NIST后来在修订版里专门加了安全限制:FF3只允许用在minlen ≥ 2的场景(通常建议实际保护域长度至少4个字符)。
| 属性 | FF1 | FF2 | FF3 |
|---|---|---|---|
| 标准轮数 | 10 | 12 | 7 |
| 支持的基数范围 | 2 ~ 216 | 2 ~ 36 | 2 ~ 216 |
| 最小域长度 | 2 字符 | 12 字符 | 2 字符 (但有安全警告) |
| 典型场景 | 身份证、信用卡号、任意数据 | 社保号、字母数字编码 | 长令牌、UUID 的格式保护 |
| 官方安全状态 | ✅ 推荐 | ✅ 推荐 | ⚠️ 有限制推荐 |
实际选型建议
你可能要问了:三个算法,我到底选哪个?我个人的经验法则就一句话:能用FF1就用FF1。FF1是标准里最稳妥的,安全分析最充分,社区支持最广。只有当你对性能有极致要求,且域长度超过16个字符时,才考虑FF3。至于FF2……除非你的客户明确要求,否则别碰,实现少,坑多。
避坑经验
我曾经在FF3上栽过一次跟头。当时做门禁卡令牌化,卡号是8位数字,我用FF3加密。上线后某天安全团队扫描发现:同样的原始卡号,在不同的tweak下居然生成了相同的令牌?我查了三天,最后发现FF3的tweak处理方式在短域下会导致碰撞概率显著上升。从那以后,我对所有短域应用都强制加了“tweak + 计数器”的组合前缀,用了类似FF1的构造才解决。
最后说一句,NIST SP 800-38G 里还包括了可调加密的规范和底层分组密码的选取建议。如果你做产品级实现,建议直接读原版PDF,别只看二手博客。我当年就是靠逐行啃标准,才发现原来FF2的非平衡Feistel里有个关于“奇偶位数”的陷阱。
嗯,算法标准就先聊到这儿。去写代码吧,记得选对算法。