第三章 FF1 算法的参数、符号与加解密流程
讲真,搞懂 FF1 算法的第一步,就是把那堆参数和符号记扎实。我最早看规范时也觉得符号有点多,后来在项目里踩了几次坑才明白——每个符号背后都有讲究。这一章咱们就把它掰开揉碎,然后把加密解密的完整流程过一遍。
3.1 算法参数
FF1 需要四个核心参数,少了哪个都不行。你想想看,这四个参数决定了你的格式空间有多大、怎么加解密。
| 参数 | 含义 | 典型取值 |
|---|---|---|
| radix | 字符集的基数 | 10(数字)、16(十六进制)、26(字母) |
| key | 加密密钥 | 128 / 192 / 256 位 AES 密钥 |
| tweak | 调整值(公开参数) | 长度建议 8~16 字节 |
| plaintext/ciphertext | 明文/密文字符串 | 长度 ≥ radixminLen 且 ≤ maxLen |
核心观点:radix 决定了每个位置有多少种可能。数字 radix=10,大写字母 radix=26,全数字+字母 radix=36。我自己做社保卡号加密项目时就犯过这个错——把 radix 设成了 10,结果客户需要保留字母,一跑就崩。后来老老实实按实际字符集来。
3.2 符号定义
规范里一堆拉丁字母和希腊符号,别被吓到。说白了就是几个关键记号:
n— 明文字符串长度t— tweak 长度(字节)u = ⌊n/2⌋— 左半部分长度v = n - u— 右半部分长度A, B— 左右两个子串P, Q, R— 中间计算变量
∑ 和 Δ 符号在 Feistel 轮函数里频繁出现。我记得第一次看规范的伪代码时,被 NUM 和 STR 两个转换函数绕了半天——其实就是一个把字符串转成整数,一个把整数转回字符串,base 就是 radix。
提示:个人习惯把符号定义做成一张速查表贴在办公室白板上。你开发时随时扫一眼,省得来回翻规范。
3.3 加密流程
FF1 用的是 8 轮 Feistel 网络。每一轮分成左半和右半,右半经过轮函数 F 加密后与左半异或(这里的异或是按 数值加减模 radixu 操作,不是比特异或)。
- 分割:把明文
X分成左A(前 u 个字符)和右B(后 v 个字符)。 - 迭代:执行 8 轮,每轮:
- 计算
P = [ key 派生出来的块 + 轮数 + tweak 相关信息 ] - 计算
Q = NUM(B)等中间值 - 调用 AES(或 CMAC)生成
R - 用
R更新A和B
- 计算
- 合并:最后
A || B就是密文。
警告:轮数固定 8 轮,不能瞎改。我曾经试过减到 6 轮想省性能,结果安全性评估直接挂了。NIST 设定这个数字是有道理的。
3.4 解密流程
解密就是加密的逆过程,注意轮函数和密钥顺序是相反的:
- 把密文分割成左
A和右B(与加密时相同)。 - 从第 8 轮开始向下迭代到第 1 轮,每轮用的密钥派生值和加密时相反。
- 最后合并得到原始明文。
嗯,这里要注意:解密时左右交换的逻辑和加密正好镜像。我在写代码时直接把加密的流程倒过来,结果最后两位总是不对——仔细排查才发现轮函数里有一个 tweak 的计数器方向搞反了。
3.5 完整加解密流程图
3.6 轮函数细节(简版)
轮函数 F 是 FF1 的心脏,可以拆成三步:
- 构造输入块:把当前轮号、tweak、右边子串 B 的数值拼成 16 字节输入。
- AES 加密:用 AES-ECB 模式加密这个块,得到 16 字节输出。
- 数值模运算:把 16 字节输出转成整数,然后对
radixv取模,再转回字符串加到左半上。
个人经验:最开始我用 OpenSSL 的 AES-ECB 接口,发现输出总是多两字节。仔细一看,规范要求的是「原始 AES 加密」而不是带填充的 ECB—数据块正好 16 字节,不需要填充。去掉填充选项后,结果就对了。
3.7 常见踩坑点
我把这些年在 FF1 实现中遇到的坑列一下,你写代码时多留个心眼:
- tweak 长度不一致:加密和解密用的 tweak 必须完全相同,有位差异都会导致解密失败。我曾经在数据库里存 tweak 时把最后一位截断了,查了两天才找到原因。
- radix 与字符集不匹配:radix 必须能覆盖输入字符里的所有可能值。比如你允许输入小写字母,但 radix 设成 26(只支持大写),那就等着解密异常吧。
- 明文长度上限:FF1 要求
n ≤ maxLen(由 radix 和 AES 密钥长度决定)。超出时算法会断言失败,不能自动截断。 - 多线程安全问题:密钥和 tweak 是共享的,但每个加解密实例内部状态独立。我一开始没注意,多线程并发跑时轮函数用到同一个临时缓冲区,结果互相覆盖。后来用线程局部存储解决。
3.8 小结
这一章我们把 FF1 的骨架搭起来了。记住四个参数、六个符号、八轮流程。下一节咱们会用实际代码把这些流程落下来——不过那是下一章的事了。你先消化好本章的内容,特别是那张流程图,理解加密和解密是怎样镜像对称的。
最后啰嗦一句:ff1 的数学公式看着复杂,但实现时百分之九十的 bug 都出在模运算的边界上。写代码时多写几个单元测试,用 NIST 的测试向量验证,能省下大量 Debug 时间。