4. Prefix Encryption 方法
Prefix Encryption,说白了就是把整个域拆成若干个小块,每个小块单独加密,然后像搭积木一样拼回去。我在做金融卡号加密那会儿,就靠它来处理可变长度的 PAN 数据。
4.1 基本思想
你想想看,常规的 FPE 算法(比如 FF1)虽然好用,但每次都要把整个明文当作一个整体来构造 Feistel 网络。如果明文很长,计算开销会直线上升。Prefix Encryption 的想法很简单:
- 把整个消息分成若干固定长度的“前缀” + 末尾可能较短的“尾巴”;
- 对每一个前缀分别加密,加密时带上已经加密的前面部分作为 tweak(或者叫链式向量);
- 最后一节(尾巴)如果长度不够,就用前一个加密结果截取或填充。
本质上是一种“链式加密”思路。我记得最早看到它在磁盘加密领域(比如 XTS 模式)的影子,后来被移植到 FPE 场景。这样做的好处是:加密时只需要在固定长度上做 FE(Feistel)回合,不需要为变长域单独设计算法。
4.2 实现步骤
我习惯按下面几步来写 Prefix Encryption 的实现。假设我们要对 plaintext(由字符集为 Alphabet)加密,每次加密固定块大小 block_len。
- 划分块:将明文从前往后切成若干块,每块长度
block_len,最后一块可能不足。 - 初始化向量:使用一个全 0 或密钥衍生的初始向量
IV。 - 逐个加密:对于第 i 块,取上一块的密文(或 IV 作为第一个 tweak)与当前块拼接作为 FE 输入,用密钥 K 加密得到当前块密文。
- 处理最后一块:如果最后一块长度 <
block_len,则从上一个密文中截取相应位数,用 XOR 或模运算掩码调整。 - 拼接输出:将所有密文块按顺序拼接,得到最终密文。
下面是一个用 Python 写的简单示意(基于 FF1 作为底层 FE):
from fpe import FF1
import math
def prefix_encrypt(key, plaintext, alphabet, block_len=8):
# 划分块
n = len(plaintext)
blocks = [plaintext[i:i+block_len] for i in range(0, n, block_len)]
cipher_blocks = []
prev_cipher = "0" * block_len # 初始向量
ff1 = FF1(key, alphabet)
for i, blk in enumerate(blocks):
# 构造 tweak:前一块密文(或 IV)
tweak = prev_cipher
# 对当前块加密:输入 = tweak + blk
inp = tweak + blk
out = ff1.encrypt(inp)
# 取前半作为当前块密文
cipher_blk = out[:len(blk)]
cipher_blocks.append(cipher_blk)
prev_cipher = cipher_blk
return ''.join(cipher_blocks)
⚠ 坑我踩过: 千万别把最后一块的 tweak 搞错。如果最后一块长度小于 block_len,有些实现会直接把整个 tweak 扔掉,只加密最后一块+部分tweak。这样会导致解密时前后不匹配。我建议统一用前一块密文的全长,最后一块加密时输入长度对齐即可。
4.3 优缺点分析
先聊优点:
| 优点 | 说明 |
|---|---|
| 计算效率高 | 每个块长度固定,FE 轮次恒定。尤其适合硬件实现或流式加密。 |
| 支持任意长度 | 不用像 FF1 那样必须规定最小长度(通常国际标准要求 2 字符以上)。Prefix 可以处理 1 字符、3 字符等。 |
| 可并行 | 如果采用独立 tweak(类似 CTR 模式),每个块可并行加密。我团队曾用 GPU 加速实现,吞吐量翻了 4 倍。 |
再看缺点:
| 缺点 | 说明 |
|---|---|
| 链式依赖 | 如果某个块加密出错,后面所有块都废了。而且解密时也依赖顺序,不能随机访问。 |
| 安全性稍弱 | 因为前缀是直接暴露在前一块密文上的,如果某一块泄露了 tweak,攻击者可以利用已知明文攻击(KPA)推部分密钥信息。相比 FF1 的全局统一 Feistel,风险略高。 |
| 最后一块处理麻烦 | 不同实现方式(截断、padding、XOR)会导致兼容性问题。我见过两个系统因为最后一块处理方式不同而互不解密。 |
4.4 适用场景
说实话,Prefix Encryption 非常适合以下场景:
- 流式数据:比如实时语音或视频加密,不需要等整条消息到齐。
- 弱实时性要求高的系统:比如 IoT 设备上处理短报文,每次只加密几十个字符。
- 金融交易日志:日志条目很长,但每条内部结构一致(固定字段)。Prefix 可以只对关键字段加密,其它字段保持明文。
💡 我的经验: 如果明文长度很短(比如 1-4 字符),不要用 Prefix。因为块太小,FE 回合数没法做够,安全性会下降。这时候我更推荐用 FF1/FF3 直接整个加密。
4.5 Prefix Encryption 核心流程
我画了一张流程图,方便你理解整个过程。注意看数据流和 tweak 的传导关系。
4.6 安全性建议
我个人总结的安全三条红线:
- 永远不要重用相同的 IV 加密相同的明文前缀(否则会有 ECB 的类问题)。
- 块长度至少 8 个字符(对于数字字母集),否则 FE 内部轮数不够。
- 最后一块处理必须明确约定 padding 方式,建议用
ISO 10126或PKCS7种可逆方案。
嗯,Prefix Encryption 就这些。它在 FPE 工具箱里算是一把“快刀”,但要注意别切到自己。我当年在一个支付系统里误用了固定 IV,结果两个相同卡号的密文竟然一毛一样…… 从那以后,IV 生成逻辑我再也不敢偷懒了。