FPE 算法概述

咱们先聊一个最基础的问题:什么是保留格式加密?

说白了,就是加密之后,密文跟原文长得一模一样——长度相同、字符集相同、格式也相同。你想想看,一张信用卡号是 16 位纯数字,加密后还是 16 位纯数字。这就叫 Format-Preserving Encryption,简称 FPE。

我刚开始接触这个概念时,第一反应是:为啥要这么干?直接用 AES 加密成一段乱码不就完了吗?后来在项目中吃了亏才明白……嗯,现实世界里的数据库、接口、协议,往往对数据格式有硬性要求。你改不了格式,就只能用 FPE。

FPE 与 AES/GCM 的传统加密对比

传统加密(比如 AES-GCM)会把任意二进制数据变成看起来随机的字节串。而 FPE 把输入限定在某个特定格式里,输出也保持这个格式。下面这张表可以帮你快速看清区别:

对比维度 AES / GCM FPE
输出格式 二进制 / Base64 字符串 与输入完全一致
长度变化 通常会扩展(IV + 标签) 长度完全不变
字符集限制 无限制 必须指定(如数字、字母集)
可搜索性 不支持,密文无序 支持保持排序属性(部分方案)
密钥管理 标准 同 AES 标准,通常基于 AES 构造

我个人习惯在需要 保留字段格式 的场景里优先考虑 FPE。比如金融系统的卡号加密,如果数据库字段定义的是数字字符串,你用 AES 加密后插不回去——长度变了,类型也变了,数据库就得改表结构,牵扯一堆下游系统。我用 FPE 就省了这些麻烦。

典型应用场景

实际工作中经常遇到的应用有三个:

  • 信用卡号(PAN):卡号一般是 16 位数字,前 6 位是发卡行标识(BIN)。加密后需要保留 BIN,方便银行做路由和风控。FPE 可以做到只加密后 10 位,前 6 位明文保留。我在一个支付网关项目里就是这么干的。
  • 社保号(SSN / 身份证号):通常是固定长度的数字或数字+字母。数据库用来做关联查询,如果密文格式变了,索引就废了。FPE 能保持格式,索引照样生效。
  • 手机号:11 位数字,经常作为用户唯一标识。加密后还是 11 位数字,前端展示、短信发送都能无缝兼容。记得有一次,我把手机号直接 AES 加密,结果接口要求非空且符合手机号正则校验,整个调用链都断了……后来改用 FPE,一行代码都没改前端。
注意! 保留格式加密不是万能的。它牺牲了一部分安全强度(密文空间变小)来换取格式兼容。如果你的场景不依赖格式,直接用 AES-GCM 更安全。
经验之谈: FPE 通常用于脱敏或等保合规场景。我曾经帮一个银行做信用卡数据迁移,他们要求测试环境保留生产数据的格式但内容脱敏。FPE 一把梭,既满足合规,又不用改应用代码。

FPE 的核心逻辑

它的工作原理其实不复杂——把原始明文映射到与它同长度的密文上,而且这个映射是随机的、可逆的。常见的标准有 NIST SP 800-38G 推荐的 FF1 和 FF3 算法。它们是基于 AES 构造的 Feistel 网络,输入是一个字符串(比如数字串),输出还是一样长的字符串。

下面这张图帮你理清 FPE 在整个数据保护体系中的位置:

FPE 核心概念与对比 明文(格式) "4111 1111 1111 1111" 加密过程 FF1 / FF3 算法 密文(同格式) "6754 3210 9876 5432" AES/GCM 加密 输出二进制乱码 Base64 串 长度不定,格式变 为什么需要保留格式? 数据库字段约束、索引、正则校验 接口协议要求、历史兼容、合规脱敏

一句话总结: FPE = 加密后数据“看起来”和原文一模一样,但内容是保密的。它能无缝插进现存系统,不打破任何格式约束。