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网络一轮迭代流程图 Feistel 一轮迭代结构 Li Ri 轮函数 F (含密钥) ⊕ (字符集内模加) 交换:旧左→新右 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轮函数的选择了。