FPE 与传统加密:到底差在哪?

我在刚开始接触 FPE 的时候,脑子里第一个冒出来的问题就是:不就是加个密嘛,AES 不够用?干嘛非要用这个叫格式保留加密的东西?

后来踩了几个坑,才发现这俩其实是完全不同的路子。传统加密是“我要保密,其他不管”。FPE 是“保密是基础,但数据格式你得给我留着”。

下面我带你细细拆开看,这三个核心差异到底意味着什么。

一、数据格式保持 —— 密文还能像明文一样

先讲最直观的。你用 AES 加密一个身份证号:110101199001011234,出来的结果是啥?

嗯,像 9a2f8c...b3e4 这样的十六进制串。长度变长、字母数字混一块,而且符号都可能带出来。放到数据库里,原来存身份证号的字段完全用不了——类型得改,校验规则得重写,下游系统根本认不出来。

但用 FPE 加密同一个身份证号呢?结果可能长这样:556729200305042987。

你看,还是 18 位纯数字,一眼看去就是个“合法的”身份证格式。就像给它换了个身份,但外形完全没变。

核心区别:传统加密输出的密文不关心字符集合和格式;FPE 输出的密文严格限制在与明文相同的格式域内。

场景明文AES 密文(示例)FPE 密文(示例)
身份证号1101011990010112343f7a2b9c...e4f1556729200305042987
信用卡卡号62220212345678908d1e2f...ab906222009876543211
手机号13912345678c4b5a6...7d8e13876543210

一般这种需求在金融和医疗行业最常见。我曾经帮一家银行改造信用卡号脱敏,他们原来的方案是用 AES 加密后,转存到一个新字段,业务查询时还得等着解密。后来换成 FPE,直接在原字段上替换密文——查询、索引、报表,全都不需要动。

小提示:格式保持是 FPE 的核心卖点,但前提是你得明确“格式”的定义。比如“18位数字”和“带有校验位的18位数字”是两种格式。定义错了,密文可能被下游业务系统当成无效数据。

二、长度保持 —— 字段永远不溢出

很多传统加密算法会带来长度膨胀。比如 AES-128 对 16 字节数据加密,输出还是 16 字节。可如果你的数据不是 16 字节整数倍,就得做填充。填完之后密文长度就变长了。

更头疼的是,有时候你的明文只有 8 位数字(比如银行卡 PIN),AES 加密后变成 16 字节乱码,你是截断还是扩展?哪种都破坏原有数据结构。

FPE 就没有这个烦恼。它保证加密前后长度完全一致。10 位数字进去,10 位数字出来。3 个字母进去,3 个字母出来。不多不少。

注意:FPE 的“长度保持”是严格等长的,但它不支持任意长度。比如 FF1 算法要求明文字符串长度至少为 2(某些场景是 4),且最大长度受限于你选择的加密轮次和中间状态位数。设计时一定要检查你的数据长度落在 FPE 算法的合法范围里。

为什么这个特性这么重要?你想想看,你的数据库表里有一个 varchar(18) 字段存身份证号。如果换成传统加密,长度变成 32 甚至 44,那你就要改表结构、改存储过程、改 ORM 映射。万一耦合了几十个微服务……简直是灾难。

三、加解密一致性 —— 你用的密钥和算法必须对

传统加密和 FPE 在加解密上的逻辑其实一样:用同一个密钥,用同一个算法,就能互相转换。这点上 FPE 并没有搞特殊。

但有个坑特别容易踩:FPE 的算法参数设计比 AES 多了个 tweak(调整值)。

// 传统 AES 加密
cipher_text = AES_encrypt(key, plain_text)

// FPE 加密(FF1算法)
cipher_text = FF1_encrypt(key, plain_text, tweak, radix)

这个 tweak 可以理解成“额外的盐”,用来增加算法的多样性。比如你用同一个密钥加密“张三”和“李四”的身份证号,如果没有 tweak,密文可能会呈现出某种统计规律。加上不同的 tweak(比如每个人的用户 ID),就能打散这种规律。

但 tweak 必须保证加密和解密时完全一致,否则解密后会得到一堆垃圾。

加解密一致性口诀:密钥相同、算法相同、tweak 相同、格式(radix 和字符集合)相同,则加密解密才能正确。

我记得有一次陪客户做 POC,他们拿测试数据跑了一遍,加密成功,解密却报错。排查了半天——发现他们加密时用的 tweak 是 "test_tweak",解密时却传了个空字符串。这种小问题在传统加密里很少遇到,但 FPE 的多了一个参数,就容易犯错。

四、一张图看明白核心差异

我画了个对比图,把三个区别放到一起。竖着看是三种特征维度,横着看是传统加密和 FPE 的做法差异。

FPE vs 传统加密:三个核心区别 维度 传统加密(如AES) FPE 格式保持 密文格式随意 字母+数字+符号混排 无法保持格式 密文与明文格式一致 数字→数字,字母→字母 格式完全保留 长度保持 可能长度膨胀 padding导致增加字节 长度不保证相等 密文长度 = 明文长度 无需padding 长度严格相等 加解密一致性 密钥+算法一致即可 额外参数少 实现简单,不易出错 密钥+算法+tweak 三者必须全部一致 tweak管理是额外负担 小结:FPE 擅长在不动数据库结构的前提下实现加密,但需要仔细处理 tweak 和格式定义。

五、我的一点选择建议

什么时候该用 FPE?我列了几个判断条件,你对照一下自己的场景:

  • 数据库字段不可改 —— 字段长度、类型、校验规则全都固定死了,只能用 FPE。
  • 下游系统无法处理乱码 —— 比如日志、报表、第三方接口只接受特定格式。
  • 需要可搜索加密 —— 有时候 Ciphertext 需要支持比较、排序,传统加密做不到。
  • 合规要求保留格式 —— 像 PCI DSS 中信用卡号的格式保持脱敏,FPE 是标准做法。

反过来,如果只是离线存储、数据不展示给业务系统,或者你能自由改字段结构,那用 AES 就够了。毕竟 FPE 的算法开销比 AES 更大,而且 tweak 的管理确实会增加复杂度。

另一个我踩过的坑:FPE 并不适合加密短随机数或者密码哈希值,因为那些数据本身没有“格式”可言。另外,如果你的数据长度恰好是算法边界(比如 FF1 要求长度 ≥ 2),实测中要注意边界测试。

好了,总结一下:FPE 和传统加密不是谁替代谁的关系。核心区别就在格式保持、长度保持、加解密一致性这三个点上。你在选型的时候,盯着“我的数据格式必须保留吗”这个关键问题来,答案就很清楚了。