格式保留加密、同态加密与零知识证明
作者:林 | 系列:密码学 | 适合读者:后端开发 / 对前沿密码学感兴趣的工程师
一、前言
前六篇覆盖了工程中更常用的密码学技术。这篇聊几个"高级货",它们目前在特定场景已有落地,未来几年会越来越重要:
| 技术 | 一句话 | 典型场景 |
|---|---|---|
| 格式保留加密(FPE) | 加密后格式不变 | 数据脱敏、存量系统改造 |
| 同态加密(HE/FHE) | 密文上直接计算 | 隐私计算、联邦学习 |
| 零知识证明(ZKP) | 证明我知道但不告诉你 | 区块链隐私、身份认证 |
| 后量子密码(PQC) | 抗量子计算机攻击 | 未来标准迁移 |
二、格式保留加密(FPE)
2.1 什么是 FPE
FPE(Format-Preserving Encryption):加密后的密文与明文格式完全相同。
普通 AES 加密:
"13800001111" → "a3Kf8bQ2xR7m9p..." (Base64 乱码,长度也变了)
FPE 加密:
"13800001111" → "15927463850" (还是 11 位数字)
"张三" → "李七" (还是 2 个汉字)
"320106199001011234" → "410523198712255678" (还是 18 位身份证号格式)2.2 为什么需要 FPE
存量系统改造时,数据库字段类型、长度、校验规则都不能改。用 AES 加密后字段变成了长 Base64 字符串,需要改表结构、改校验逻辑、改前端展示,改动太大。
FPE 让加密对上下游透明:字段类型不变,长度不变,甚至格式校验(如身份证校验位)都能保留。
2.3 核心算法:FF1 和 FF3-1
NIST SP 800-38G 标准定义了两种 FPE 算法:
| 算法 | 全称 | 特点 |
|---|---|---|
| FF1 | Format-preserving, Feistel-based encryption 1 | 10 轮 Feistel,支持 tweak |
| FF3-1 | FF3 修订版 | 8 轮 Feistel,tweak 缩短为 56 bit |
FF1 结构(简化):
输入:明文 X(radix 进制表示),密钥 K,Tweak T
1. 将 X 拆成左右两半:A, B
2. 10 轮 Feistel 迭代:
for i = 0 to 9:
C = A + F(K, T, i, B) mod radix^m
A = B
B = C
3. 输出密文 = A || B
其中 F 是基于 AES 的伪随机函数
radix = 基数(数字=10,字母=26,etc.)2.4 Java 示例(BouncyCastle)
import org.bouncycastle.crypto.fpe.FPEFF1Engine;
import org.bouncycastle.crypto.fpe.FPEEngine;
import org.bouncycastle.crypto.params.FPEParameters;
import org.bouncycastle.crypto.params.KeyParameter;
/**
* FF1 格式保留加密示例
* 对数字字符串加密,结果仍是相同长度的数字字符串
*/
public class FpeExample {
private static final int RADIX = 10; // 数字:0-9
/**
* FPE 加密数字字符串
* @param plaintext 明文数字字符串(如 "13800001111")
* @param key AES 密钥(16/24/32 字节)
* @param tweak 调柄(可用于区分不同字段)
* @return 密文数字字符串(格式相同)
*/
public static String encrypt(String plaintext, byte[] key, byte[] tweak) {
FPEEngine engine = new FPEFF1Engine();
engine.init(true, new FPEParameters(
new KeyParameter(key), RADIX, tweak));
// 将数字字符串转为 short 数组
short[] input = toShortArray(plaintext);
short[] output = new short[input.length];
engine.processBlock(input, 0, input.length, output, 0);
return fromShortArray(output);
}
/**
* FPE 解密
*/
public static String decrypt(String ciphertext, byte[] key, byte[] tweak) {
FPEEngine engine = new FPEFF1Engine();
engine.init(false, new FPEParameters(
new KeyParameter(key), RADIX, tweak));
short[] input = toShortArray(ciphertext);
short[] output = new short[input.length];
engine.processBlock(input, 0, input.length, output, 0);
return fromShortArray(output);
}
private static short[] toShortArray(String digits) {
short[] arr = new short[digits.length()];
for (int i = 0; i < digits.length(); i++) {
arr[i] = (short) (digits.charAt(i) - '0');
}
return arr;
}
private static String fromShortArray(short[] arr) {
StringBuilder sb = new StringBuilder(arr.length);
for (short s : arr) {
sb.append((char) ('0' + s));
}
return sb.toString();
}
}// 使用示例
byte[] key = "0123456789abcdef".getBytes(); // 16 字节 AES 密钥
byte[] tweak = "phone".getBytes(); // tweak 区分字段
String phone = "13800001111";
String encrypted = FpeExample.encrypt(phone, key, tweak);
// encrypted = "15927463850" (还是 11 位数字)
String decrypted = FpeExample.decrypt(encrypted, key, tweak);
// decrypted = "13800001111" (正确还原)2.5 FPE 的局限
| 限制 | 说明 |
|---|---|
| 不支持模糊搜索 | “张三” 加密后第一个字和 “张” 单独加密结果不同 |
| 确定性加密 | 同密钥 + 同 tweak 下,相同明文→相同密文(泄露相等关系) |
| 安全性弱于 AES-GCM | 格式保留本身就是在安全性上的妥协 |
| 短输入安全性差 | 输入越短(如 2 位数字),安全性越低 |
适用场景:数据脱敏(测试环境)、存量系统改造(改不动表结构)、合规展示(只显示部分,但存储全加密)。
三、同态加密(Homomorphic Encryption)
3.1 什么是同态加密
普通加密:先解密,再计算,再加密回去,计算方必须看到明文。
同态加密:直接在密文上计算,结果解密后等于对明文计算的结果。计算方全程看不到明文。
普通流程:
密文 → 解密 → 明文 → 计算 → 结果 → 加密 → 密文结果
(计算方看到了明文 ❌)
同态加密:
密文 → 直接在密文上计算 → 密文结果 → 解密 → 明文结果
(计算方全程只看到密文 ✅)
数学表达:
Enc(a) ⊕ Enc(b) = Enc(a + b) -- 加法同态
Enc(a) ⊗ Enc(b) = Enc(a × b) -- 乘法同态3.2 同态加密的分类
| 类型 | 支持的运算 | 性能 | 代表方案 |
|---|---|---|---|
| 部分同态(PHE) | 仅加法或仅乘法 | 快(ms级) | Paillier(加法)、RSA(乘法) |
| 有限同态(SHE) | 有限次加法和乘法 | 中等 | BGV、BFV |
| 全同态(FHE) | 任意次加法和乘法 | 极慢(万倍开销) | CKKS、TFHE |
3.3 Paillier 加法同态示例
Paillier 是更实用的部分同态方案,支持密文加法:
场景:多方工资求和,任何一方都不想暴露自己的工资
Alice 工资:10000 → Enc(10000)
Bob 工资: 15000 → Enc(15000)
Carol 工资:12000 → Enc(12000)
服务端计算(全程不解密):
Enc(10000) ⊕ Enc(15000) ⊕ Enc(12000) = Enc(37000)
只有拥有私钥的一方能解密得到总和 37000
没有人看到其他人的工资/**
* Paillier 同态加密示意
* 生产环境推荐使用 java-paillier 库或 SEAL/OpenFHE
*/
public class PaillierDemo {
// Paillier 密钥生成
public static PaillierKeyPair generateKeyPair(int bitLength) {
BigInteger p = BigInteger.probablePrime(bitLength / 2, new SecureRandom());
BigInteger q = BigInteger.probablePrime(bitLength / 2, new SecureRandom());
BigInteger n = p.multiply(q);
BigInteger nSquared = n.multiply(n);
BigInteger lambda = lcm(p.subtract(ONE), q.subtract(ONE));
BigInteger g = n.add(ONE); // 简化选择 g = n+1
BigInteger mu = lambda.modInverse(n); // L(g^lambda mod n^2)^-1 mod n
return new PaillierKeyPair(
new PaillierPublicKey(n, g),
new PaillierPrivateKey(lambda, mu, n));
}
// 加密:Enc(m) = g^m * r^n mod n^2
public static BigInteger encrypt(BigInteger m, PaillierPublicKey pub) {
BigInteger r = randomCoprime(pub.n);
BigInteger nSquared = pub.n.multiply(pub.n);
return pub.g.modPow(m, nSquared)
.multiply(r.modPow(pub.n, nSquared))
.mod(nSquared);
}
// 同态加法:Enc(a) * Enc(b) mod n^2 = Enc(a+b)
public static BigInteger add(BigInteger encA, BigInteger encB,
PaillierPublicKey pub) {
BigInteger nSquared = pub.n.multiply(pub.n);
return encA.multiply(encB).mod(nSquared);
}
// 密文标量乘:Enc(a)^k mod n^2 = Enc(a*k)
public static BigInteger scalarMultiply(BigInteger encA, BigInteger k,
PaillierPublicKey pub) {
BigInteger nSquared = pub.n.multiply(pub.n);
return encA.modPow(k, nSquared);
}
// 解密:Dec(c) = L(c^lambda mod n^2) * mu mod n
public static BigInteger decrypt(BigInteger c, PaillierPrivateKey priv) {
BigInteger nSquared = priv.n.multiply(priv.n);
BigInteger x = c.modPow(priv.lambda, nSquared);
BigInteger l = x.subtract(ONE).divide(priv.n); // L函数
return l.multiply(priv.mu).mod(priv.n);
}
}3.4 全同态加密(FHE)的现状
FHE 的性能开销(2024 年水平):
操作 | 明文耗时 | FHE 密文耗时 | 倍率
加法 | ~1 ns | ~0.1 ms | 100,000×
乘法 | ~1 ns | ~10 ms | 10,000,000×
比较/排序 | ~5 ns | ~100 ms | 20,000,000×
密文大小 | 4 bytes | ~32 KB | 8,000×FHE 目前的实际应用:
- 隐私保护的机器学习推理(模型在密文上运行)
- 基因组数据分析(医院间共享加密数据)
- 金融风控联合建模
FHE 离普及还有多远
FHE 的性能在过去 10 年提升了约 10 万倍(从秒级单次加法到微秒级),但距离通用场景仍有 4-5 个数量级的差距。目前只适合计算量小、隐私要求极高的场景。预计 5-10 年内在特定领域(医疗、金融)会有更多落地。
四、零知识证明(ZKP)
4.1 什么是零知识证明
零知识证明:证明者向验证者证明某个陈述为真,但不泄露除"陈述为真"之外的任何信息。
经典比喻——阿里巴巴洞穴:
洞穴有两条路(A 和 B),中间有一扇密码门连通。
证明者知道密码,验证者不知道。
1. 证明者随机选一条路进去(验证者看不到选了哪条)
2. 验证者喊:"从 A 路出来!"(或 B)
3. 如果证明者知道密码 → 无论要求哪条路都能出来
如果证明者不知道密码 → 50% 概率出不来
重复 20 次,每次都能正确出来 → 99.9999% 证明者知道密码
但验证者全程没学到密码本身4.2 ZKP 的三个性质
| 性质 | 含义 |
|---|---|
| 完备性 | 如果陈述为真,诚实的证明者总能说服验证者 |
| 可靠性 | 如果陈述为假,作弊的证明者无法说服验证者(概率可忽略) |
| 零知识性 | 验证者除了"陈述为真"之外学不到任何额外信息 |
4.3 实际应用
| 场景 | 做法 |
|---|---|
| 区块链隐私交易 | 证明"我有足够余额"但不暴露余额(Zcash) |
| 身份认证 | 证明"我年满 18 岁"但不暴露出生日期 |
| 密码验证 | 证明"我知道密码"但不传输密码 |
| 合规审计 | 证明"资产 > 负债"但不暴露具体数字 |
| zkRollup | 证明"1000 笔交易都合法",链上只验证一个证明 |
4.4 主流 ZKP 方案对比
| 方案 | 类型 | 证明大小 | 验证时间 | 是否需要可信设置 |
|---|---|---|---|---|
| Groth16 | zk-SNARK | ~200 bytes | ~ms | ✅ |
| PLONK | zk-SNARK | ~500 bytes | ~ms | ✅(通用) |
| STARK | zk-STARK | ~50 KB | ~ms | ❌ |
| Bulletproofs | — | ~1 KB | ~100ms | ❌ |
zk-SNARK:证明小、验证快,但需要可信设置(有人要生成参数然后销毁)
zk-STARK:无需可信设置、抗量子,但证明较大4.5 简单示例:证明知道哈希原像
场景:我知道一个 x 使得 SHA256(x) = H,但我不想告诉你 x 是什么
传统方式:
把 x 给你,你自己算 SHA256(x) 看是不是等于 H
→ x 暴露了 ❌
零知识方式(简化思路):
1. 将 SHA256 电路化(转为算术电路)
2. 我用 x 作为秘密输入,生成一个证明 π
3. 你验证 π 对于公开输入 H 是否成立
4. 验证通过 → 你确信我知道 x,但你不知道 x 是什么# 概念示例(使用 snarkjs 风格的伪代码)
# 实际 ZKP 开发通常用 Circom/Rust/Go 框架
# 电路定义(Circom 风格)
"""
template HashPreimage() {
signal private input preimage; // 秘密输入
signal input hash; // 公开输入
// 约束:SHA256(preimage) == hash
component hasher = SHA256();
hasher.in <== preimage;
hash === hasher.out;
}
"""
# 证明者(知道 preimage)
proof = generate_proof(circuit, private_input=preimage, public_input=hash)
# 验证者(不知道 preimage)
is_valid = verify_proof(proof, public_input=hash) # true/false五、后量子密码(PQC)
5.1 量子计算机的威胁
Shor 算法(1994):
量子计算机上可以在多项式时间内分解大整数、求解离散对数
影响:
RSA → 被破解(基于大整数分解)
ECDSA → 被破解(基于椭圆曲线离散对数)
SM2 → 被破解(同上)
AES → 安全性减半(Grover 算法,AES-256 → 128 bit 安全)
SHA → 安全性减半(同上)
结论:
非对称加密全军覆没,对称加密和哈希加倍密钥长度即可5.2 NIST 后量子标准(2024 年发布)
| 算法 | 用途 | 基于的数学难题 | 标准编号 |
|---|---|---|---|
| ML-KEM (Kyber) | 密钥封装 | 格上模块学习问题(M-LWE) | FIPS 203 |
| ML-DSA (Dilithium) | 数字签名 | 格上模块学习问题 | FIPS 204 |
| SLH-DSA (SPHINCS+) | 数字签名 | 哈希函数安全性 | FIPS 205 |
5.3 后量子 vs 经典算法
| 指标 | ECDSA P-256 | ML-DSA-65 (Dilithium) | 倍率 |
|---|---|---|---|
| 公钥大小 | 64 bytes | 1,952 bytes | 30× |
| 签名大小 | 64 bytes | 3,293 bytes | 51× |
| 密钥生成 | ~0.05 ms | ~0.1 ms | 2× |
| 签名速度 | ~0.1 ms | ~0.3 ms | 3× |
| 验签速度 | ~0.3 ms | ~0.3 ms | 1× |
后量子算法的主要代价是密钥和签名变大,计算速度差距不大。
5.4 迁移建议
当前(2024-2026):
- 新系统可以开始支持"混合模式"(经典 + 后量子双算法)
- TLS 已支持混合密钥交换(X25519 + ML-KEM)
- 不需要急着全面切换
中期(2027-2030):
- NIST 标准成熟,库和硬件支持完善
- 高安全需求系统开始迁移
长期(2030+):
- 大规模量子计算机可能出现
- 全面迁移到后量子算法Harvest Now, Decrypt Later
攻击者可以现在录制加密流量,等未来量子计算机成熟后再解密。如果你的数据需要保密 10 年以上(政府、军事、医疗),现在就应该开始使用后量子算法或混合方案。
六、技术对比总结
| 技术 | 成熟度 | 性能 | 落地程度 | 适合谁关注 |
|---|---|---|---|---|
| FPE | 成熟 | 好 | 已广泛使用 | 做数据脱敏/存量改造的 |
| 部分同态(Paillier) | 成熟 | 好 | 联邦学习已用 | 做隐私计算的 |
| 全同态(FHE) | 研究→工程 | 差 | 少量落地 | 研究方向/未来 |
| ZKP(SNARK/STARK) | 工程化中 | 中 | 区块链广泛使用 | 做区块链/隐私的 |
| 后量子密码 | 标准刚发布 | 中 | 开始试点 | 所有人(长期) |
七、总结
对于日常工程开发:
- FPE 是更实用的"高级"密码学技术,数据脱敏、测试环境、存量改造都能用
- 同态加密 目前只需了解概念,除非你做隐私计算/联邦学习方向
- 零知识证明 在区块链领域已经是核心基础设施,其他领域还在早期
- 后量子密码 所有人都需要关注,但不需要现在行动(除非数据需保密 10 年+)
密码学的发展方向:从"保护数据不被看到"到"数据可用但不可见",在不暴露原始数据的前提下完成计算和验证,这是隐私计算的核心命题。
八、系列回顾
- 密码学基础:Hash、对称加密、非对称加密
- 散列函数:MD5、SHA、SM3 与 HMAC
- 对称加密:AES 与 SM4 模式选择指南
- 非对称加密与签名:RSA、ECC、SM2
- 数字信封与密钥协商:SM2+SM4、ECDH、TLS
- 可搜索加密:加密数据的模糊检索方案
- 格式保留加密、同态加密与零知识证明(本文)