第2章 格式保持与传统加密:你真正该关注的区别

我记得刚带团队做第一个金融数据加密项目时,产品经理跑过来说:“密文别太长,我数据库字段就设了20位。”我当时差点没笑出来——AES输出固定16字节,转Hex就是32个字符,怎么可能控制在20位?那哥们儿脸都绿了。后来我才真正意识到,传统加密和FPE之间的那道鸿沟,不只是算法层面的,更是业务落地时的硬伤。

这一章,咱们就把对称加密、非对称加密和FPE放在一起,掰开揉碎看看。你想想看,为什么非得搞一个格式保持出来?它到底解决了什么问题?

2.1 对称加密:又快又直接,但格式呢?

对称加密是大家最熟悉的——比如AES、SM4。加密和解密用同一个密钥,速度极快。PC端跑个AES-128,每秒几GB没问题。

可一旦涉及业务字段,问题就来了。

核心痛点:对称加密输出的是二进制或者十六进制串,长度固定且格式随机。你存个19位的信用卡号,加密后变成32位Hex乱码,数据库字段得改,接口得改,显示也得改。改完一轮,测试说“历史数据怎么兼容?”——嗯,头疼。

我在一个支付系统里就吃过这个亏。当时为了加密银行卡号,选了AES-ECB(虽然ECB不安全,但早期图省事)。结果密文长度从19位跳到32位,上游对接的清算系统直接崩掉,因为对方固定收19位数字。后来重建了整条管道,代价不小。

2.2 非对称加密:安全与效率的权衡

非对称加密(RSA、SM2、ECC)用公钥/私钥对,安全性更高,但速度慢、密文膨胀更夸张。RSA-2048加密19位数字,输出256字节(2048位),你算算,膨胀了十几倍。而且输出是字节流,转Base64更没法看。

说实话,在数据量大的场景里,非对称加密基本只用来交换密钥或签名,没人会把整张表字段都RSA加密——太慢了,存储也受不了。

特性 对称加密(AES) 非对称加密(RSA) FPE
密钥 单一密钥 公钥/私钥对 对称密钥(通常基于AES)
速度 极快(>1GB/s) 慢(<10MB/s) 与对称相当或略慢
密文长度 固定,与明文长度无关 等于密钥长度(膨胀严重) 与明文长度一致
格式保持 ❌ ❌ ✅(数字、字母、自定义字符集)
典型应用 文件、通道加密 密钥交换、数字签名 数据库字段、信用卡号、身份证

你看这张表,FPE那一列的关键字就是「格式保持」。说白了,加密后数据长什么样,调用方完全不关心——只要长度对、字符集对,系统就能无痛替换。

2.3 FPE做了什么不一样?

FPE(Format-Preserving Encryption)本质上是一种基于块密码的构造方法。它不发明新算法,而是用已有的AES或SM4,通过Feistel网络(例如FF1、FF3-1标准)把明文映射到同样空间的密文。

个人习惯:我一般把FPE理解为“嫁接技术”——把AES的随机性嫁接到一个有限集合的排列上。输出空间就是输入空间,一个萝卜一个坑。

拿信用卡号举例:假设明文是16位数字,FPE加密后仍然是16位数字。字符集是0-9,长度16。解密后还原,整个过程数据库字段不用改,接口校验逻辑也不需要动。你想想看,这迁移成本有多低。

我曾经在银行的一个数据脱敏项目里用FPE替换了原来的Hash方案。Hash虽然也是定长,但不可逆,业务需要的部分场景(比如风控查询)无法还原。FPE既能解密,又保持格式,一石二鸟。

# 伪代码对比
# 对称加密(AES-128)
plain = "1234567890123456"          # 16位数字
key = b'this_is_a_secret_key_16'
cipher = AES_encrypt(plain, key)    # 输出16字节乱码
print(len(cipher))                  # 16字节,但转Hex变32位字符

# FPE加密(FF1算法,字符集=0-9,长度=16)
from fpe import FPE
fpe = FPE(key, radix=10, max_len=16)
cipher_fpe = fpe.encrypt(plain)     # 输出"9876543210987654",还是16位数字
print(cipher_fpe)                   # 完全可读,格式一致

2.4 格式保持为什么重要?——真实案例

咱们聊点实际的。我这里有两个场景,你感受下。

  1. 数据库列迁移:一个老系统,订单表有个字段存19位银行卡号,类型是varchar(19)。用AES加密后,得改成varchar(32)甚至varbinary(16),历史数据要批量转换,关联索引要重建,业务代码要逐个排查。改完上线,一个晚上没睡稳。如果用FPE,varchar(19)不用动,原地加密,连DDL命令都不用发。
  2. 接口兼容:对外提供的API定义字段长度是固定值。上游合作方只要长度不对,校验直接拒绝。我用FPE加密后,长度不变,上游完全无感。商务那边不用跟合作方解释“我们升级加密了” —— 零沟通成本。

我曾经在一个跨境支付项目中,第三方要求交易流水号必须是32位字母数字。用AES加密后是二进制,转Hex变64位,对方直接抛异常。后来临时改成FPE,32位字母数字完美保持 —— 这救了我的周末。

2.5 一张图看懂三者的定位

下面这张SVG图,把对称、非对称和FPE的核心特征画清楚。你一眼就能看出FPE的位置:它不在取代传统加密,而是在“格式敏感”的场景里补上最后一块拼图。

三种加密方式的定位对比 对称加密 密钥:单一 速度:极高 密文:二进制/Hex 长度:固定 ≠ 明文长 适用:通道加密、文件 非对称加密 密钥:公钥/私钥 速度:慢 密文:二进制/Base64 长度:膨胀数倍 适用:密钥交换、签名 FPE 密钥:对称(基于AES) 速度:与对称相当 密文:明文同字符集 长度:与明文一致 ✅ 适用:数据库字段、API 输出格式对比(16位数字明文为例) 对称加密AES:输出16字节→Hex32位字符(e.g. 3a4b...) 非对称RSA:输出256字节→Base64超长字符 FPE:输出16位数字(e.g. 9876 5432 1098 7654) 格式不变 → 数据库、接口、展示层零改动 通道/文件/密钥 密钥交换/签名 数据库字段/API
⚠️ 注意:FPE虽然好用,但不要拿来加密随机数或者密钥这类本身无格式需求的数据。它额外的计算开销(Feistel轮数)和有限的域大小(比如字符集太大、长度太长会导致效率下降)都是要考虑的。我自己踩过一个坑:用FF1加密长度200的ASCII文本,结果每次加密要几百毫秒——因为内部需要多次AES运算。后来改用先压缩再加密的策略才优化回来。

好了,这一章的重点就是一个“格式”二字。对称加密快但不保留格式,非对称安全但膨胀严重,FPE就在中间那条线上——在保证安全性的同时,为业务系统省去了大量改造工作。你做设计的时候,心里要有这根弦:不是万能的,但恰当的场景下,它能让你的架构优雅不少。