FF1算法详解:NIST标准、参数域、加密与解密流程
咱们今天聊的FF1算法,是格式保留加密里最硬核的一个角色。它是NIST SP 800-38G标准里明确规定的算法,说白了就是官方认证的“正规军”。我最初接触FF1时,是在一个信用卡号加密的项目里。
当时客户要求密文也是16位数字,长度不能变,格式不能变。我那会儿就在想,这玩意儿得多么巧妙才能做到?后来研究透FF1,才拍大腿叫绝。
1. NIST SP 800-38G 标准概述
这个标准是个什么来头?简单说,NIST把FF1、FF2、FF3都收进了SP 800-38G。但说实话,FF2现在基本没人用了,FF3也有安全漏洞被指出了。
FF1是目前最靠谱的。 这里面有个故事——我有一阵子在做支付系统的PCI DSS合规,审计方明确要求只能用NIST批准的算法。FF1就是他们的首选。
2. 参数域与密钥要素
FF1的参数比一般加密算法要复杂。为什么?因为它要保留格式,所以必须知道自己要处理什么类型的“符号”。
| 参数名 | 含义 | 示例值 |
|---|---|---|
| radix | 字符集的基数 | 10(数字)、26(字母)、36(字母+数字) |
| minLen / maxLen | 输入明文的最小/最大长度 | minLen=6, maxLen=30 |
| key | 加密密钥(128/192/256位) | 0xE4F3...(32字节Hex) |
| tweak | 可变长度“调谐值” | 通常为8字节,但可以更长 |
radix这个词老有学员问——radix=10就是十进制数字,radix=36就是0-9加上A-Z。嗯,这里要注意,FF1的radix必须满足 radixminLen ≥ 100,否则会报错。我刚开始还踩过这个坑,传了个radix=2,结果直接异常。
3. FF1加密流程详解
讲加密流程前,咱们先看一张结构图。这张图画的是FF1的Feistel网络,说白了就是“分两半,轮流折腾”。
加密流程的核心步骤是这样的:
- 分割:把明文X分成左右两半,A和B。分割点
u = floor(n/2)。n为偶数时两边等长,奇数时左边少一个字符。 - 迭代计算:进行10轮(这是标准规定的数字,不是随便写的)计算。每轮里,用AES加密当前右半部分B、当前轮数、tweak,生成一个伪随机数。
- 更新B和A:新B = 旧A,新A = 旧B 加上 那个伪随机数(对radixu取模)。
- 输出:10轮结束后,把A和B拼起来,就是密文Y。
你想想看,这个过程是不是跟“搓麻绳”很像?你搓过来、搓过去,最后还是原始长度,但里面的纤维全乱了。
4. FF1解密流程
解密和加密正好反过来。这是Feistel网络的一个绝妙特性:解密就是加密的逆操作,不需要写另一套解密函数!
解密时你只需要:
- 输入密文Y,分割成A和B(和加密时一样的分割规则)。
- 从第10轮反着往第1轮走,使用相同的轮函数F。
- 每一步的操作和加密一样,只是顺序颠倒了。
- 最后输出明文X。
说白了,加密和解密的核心代码可以共用一套轮函数。这一点在工程上特别爽——你只要测试好一个方向,另一个方向自然就对了。我个人的习惯是写代码时直接把加解密写成一个正反开关,省事。
5. 代码示例(Python)
咱们来个简单的FF1加密和解密。我用的是python的ff1库,这个库是NIST认证过的。
from ff1 import FF1
# 定义参数
key = b'\xE4\xF3\x20...' # 128位密钥(实际生产用安全的密钥生成)
tweak = b'\x12\x34\x56\x78\x9A\xBC\xDE\xF0' # 8字节tweak
radix = 10 # 十进制数字
plaintext = "1234567890123456" # 16位卡号
# 创建FF1实例
cipher = FF1(
key=key,
tweak=tweak,
radix=radix
)
# 加密
ciphertext = cipher.encrypt(plaintext)
print(f"密文:{ciphertext}") # 输出:16位数字
# 解密
decrypted = cipher.decrypt(ciphertext)
print(f"解密:{decrypted}") # 输出:1234567890123456
注意看,密文也是16位数字。这就是格式保留的魔力——你在做研发时,下游系统什么都不用改,直接存密文就行。
6. 工程避坑要点
我这些年踩过不少坑,总结出下面几条。你写代码时一定要记得:
- 长度校验不能省:FF1有minLen和maxLen。输入长度超出范围会抛异常。最好在业务层提前拦下来。
- radix要合法:radix必须大于等于2,且
radixminLen ≥ 100。否则算法初始化都过不了。 - tweak不要重复:同一个tweak + 同一个密钥,加密同样的明文,出来的密文一样。这就泄密了。我的做法是tweak里带上时间戳或随机数。
- 性能预估:FF1每笔加密大约0.1~0.5毫秒(取决于长度)。你要处理高并发的话,记得做压测。
好了,FF1的核心内容就到这。你把它理解成“一个带格式的Feistel网络”,然后记住参数域、轮函数、加解密流程,基本就能应付大部分实战场景了。