4. Feistel网络:FPE算法的核心结构
聊到FPE,绕不开一个经典结构——Feistel网络。说实话,我刚开始接触FPE的时候,脑子里全是DES的影子。但实际用起来才发现,FPE里的Feistel已经被改造得面目全非了。今天咱们就把它拆开看看,到底哪些地方变了,哪些地方没变。
经典Feistel vs FPE中的Feistel
经典Feistel长什么样?你想想看,DES的每一步都是把数据切成左右两半,右半部分送进轮函数,输出和左半部分做XOR,然后交换左右位置。一轮完了再进下一轮。这个结构最妙的地方在于:加解密共用同一个轮函数,只是轮密钥顺序反过来。
但到了FPE里,情况变了。FPE处理的数据往往不是二进制位,而是数字、字母,甚至是一段电话号码。经典的DES块大小是64位,FPE的块可能只有16位,也可能大到128位。更关键的是,FPE必须保证输出的字符集和输入一致——你不能把数字变成了乱码字母。
我在做银行卡号令牌化项目时,就遇到了这个问题。卡号是纯数字,16位长度,如果直接套用DES,加密后变成二进制,再转成十进制,很可能出现前导零、首字母变成了字母。所以FPE的Feistel网络必须支持任意字符集,并且输出严格落在相同字符集内。
平衡与非平衡Feistel
经典Feistel是平衡的,左右两半长度相等。但在FPE里,有时候输入长度是奇数,就只能切成左半比右半长一位,或者相反。这就是非平衡Feistel(Unbalanced Feistel)。我建议你记住这个关键点:当字符集不是2的幂次时,几乎必然用到非平衡Feistel。
实战小贴士:FF1和FF3算法都使用非平衡Feistel。FF1支持的最小块长度是2个字符(甚至1个字符也能处理,但需要特殊机制),FF3最小块长度是4个字符。如果你处理的是4位数PIN码,FF3会更合适。
轮函数:FPE的灵魂
轮函数F是Feistel网络里唯一非线性的地方。经典DES的轮函数包含了S盒、扩展置换等,但FPE的轮函数通常基于分组密码(比如AES)或者哈希函数。
咱们拿FF1举个例子:每一轮,右半部分会和当前轮密钥一起,通过AES加密得到一个中间值,然后跟左半部分做某种形式的加法(在字符集范围内)。注意,这里不是二进制XOR,而是字符集内的模加。为什么?因为XOR只在二进制下是保结构的,对于十进制数字,你需要做“模10加法”。
说白了,就是把左右两半的数据当作整数,在模 radix 的运算下相加。这个radix就是字符集大小,比如数字是10,小写字母是26。
一个简单的Feistel实现(Python伪代码)
def feistel_round(L, R, K, radix):
# 轮函数:用AES加密R和K,得到一个字节串
temp = AES_encrypt(pad(R), K)
# 将temp转换为整数,模radix
f_output = bytes_to_int(temp) % radix
# 左半部分和函数输出做模加
new_L = (L + f_output) % radix
# 交换左右
return (R, new_L)
def feistel_encrypt(plaintext, rounds, radix, keys):
# 假设plaintext已经分割为L和R
L, R = plaintext
for i in range(rounds):
L, R = feistel_round(L, R, keys[i], radix)
# 最后一轮通常不交换(或者交换,看标准)
return L, R
注意:实际FF1/FF3还有额外的格式保留处理,比如对右半部分的长度调整、轮密钥生成等。上面这个代码只是为了展示Feistel的核心逻辑。
我曾经踩过的坑:一度以为轮数越多越安全,所以测试时用了10轮。结果在解密时发现轮密钥顺序反了,但多出来的轮导致边界条件出错。后来看标准,FF1规定至少8轮(对于64位块),而FF3是8轮(块长度≥4)。不要自己瞎加轮数,除非你重新分析了安全界。
Feistel网络的一次迭代流程
下面这张SVG图展示了一次Feistel迭代的核心步骤。你可以看到左半Li和右半Ri如何经过轮函数、模加、交换得到下一轮的Li+1和Ri+1。
经典Feistel与FPE Feistel关键对比
说了这么多,咱用一张表把不同点列清楚,方便你以后对照。
| 维度 | 经典Feistel (DES) | FPE Feistel (FF1/FF3) |
|---|---|---|
| 处理单位 | 二进制位 (bit) | 字符(数字、字母等) |
| 块长度 | 固定64位 (8字节) | 可变(2~2³²字符) |
| 左右长度 | 严格平衡(32+32) | 可非平衡(如左 len=⌊n/2⌋,右 len=⌈n/2⌉) |
| 轮函数基础 | 专有S盒、置换 | AES、CMAC、SHA等可证明安全原语 |
| 组合操作 | XOR (异或) | 模加 (mod radix) 或异或(取决于字符集) |
| 轮数 | 16轮 | FF1: 8轮 (64位) / 10轮 (128位),FF3: 8轮 |
核心要点:所有FPE算法(FF1、FF3、VAES3等)本质上都跑在Feistel网络上。理解Feistel,就等于拿到了理解FPE的钥匙。你不需要重新发明轮子,只要把经典结构里的二进制XOR换成模加,把固定S盒换成AES,再处理好非平衡分割,就能搭建自己的FPE实现。
最后说句体己话:要是你在项目里正好需要自己做FPE实现,我强烈建议不要从零写Feistel。直接用算法标准(NIST SP 800-38G)里的参考实现。我自己曾经为了省空间手工搞了一个简化的Feistel,结果安全性分析没做透,差点出安全事故。嗯…从那以后,我再也不敢低估Feistel轮函数的选择了。