38.12 隐私计算

一句话

隐私计算不是“一种加密算法”,而是在不看见明文的前提下完成计算的四条路线——联邦学习、安全多方计算、可信执行环境、差分隐私;选型只看三件事:数据能不能出域、要做哪种计算、能接受多少精度与性能损失。

四个误区,先拆掉

误区 现实
隐私计算 = 加密 加密只解决“存储与传输”,解决不了“计算时必须有明文”。四条路线各自用不同办法绕开这一点,信任假设完全不同
用了联邦学习,数据就没出域 梯度与模型参数本身就携带信息。没有安全聚合或差分隐私的联邦学习,可以从不更新的参数反推出参与方的样本特征
差分隐私 = 加噪声 加噪声只是手段,真正的约束是隐私预算:总 ε 花完了,再问一次就不私密了(1000 次 ε=0.1 等价于一次 ε=100)
TEE 万能 它把信任从“云厂商运维”转移到“CPU 厂商 + 侧信道防护”。可信内存有限、性能有折损,且漏洞(侧信道)一旦出现影响面是整个平台

四条路线:先看“信任谁”,再看性能

路线 数据出域? 信任假设 擅长 开销 精度损失 不适用
联邦学习 不出域(只传参数/梯度) 参与方诚实但好奇 → 需配合安全聚合或 DP 联合训练模型(风控、推荐、医疗影像) 通信量 = 模型大小 × 轮数 × 客户端数 低于集中式训练(非独立同分布时明显) 需要精确统计、样本极少
安全多方计算 (MPC) 不出域(密文分片) 参与方不完全互信,无单一可信方 求交(PSI)、求和、比较、联合统计 通信量比明文高 1–3 个数量级(经验量级) 无(精确结果,除非用定点近似) 大规模深度学习、低延迟场景
可信执行环境 (TEE) 可以出域(进 enclave) 信任 CPU 厂商 + 硬件无侧信道漏洞 通用计算(几乎任意代码) 比明文慢(内存加密 + 远程证明),可信内存受平台规格限制 无 需要超大内存、强侧信道威胁模型
差分隐私 (DP) 可以出域,但只出“加噪结果” 只信任数学,不信任任何参与方 统计发布、看板、A/B 指标 可忽略(只是加噪) 有,且随 ε 变小而变大 需要逐行精确结果、小样本

🔑 选型顺序:先问“能不能不出域”(联邦/MPC),再问“能不能接受明文进硬件”(TEE),最后才问“能不能接受精度损失”(DP)。反过来先上 DP,通常是因为前三条都做不到——这时要确认“精度损失是否让业务结论失效”,而不是直接接受。

三个必须能算的数字

① 联邦学习的通信量——它常常是项目死掉的原因,不是算法:

通信量 = 模型大小 × 2(上行+下行)× 轮数 × 客户端数

100 MB 模型、1000 轮、100 个客户端:100 MB × 2 × 1000 × 100 = 20 TB。把它压到 10 MB 模型,通信量降到 2 TB;把轮数压到 100,降到 200 GB。模型大小与轮数是唯一能动的两个旋钮,加带宽不如减模型。

② MPC 的通信放大——明文算 1 GB 数据,MPC 可能要传几十到几百 GB。经验上,参与方越多、轮数越多,放大越厉害;所以能用两方 PSI 解决的,不要上三方通用 MPC。

③ DP 的预算——这是唯一有严格数学定义的。基本组合定理:ε_total = Σ ε_i。1000 次 ε=0.1 的查询,等于一次 ε=100 的查询。下面用代码把这件事量出来。

可运行的隐私预算实验

拉普拉斯机制:对计数查询,敏感度 Δ=1,噪声尺度 b = Δ/ε;95% 误差上界约 ±b·ln(20) ≈ ±3b。

package main

import (
	"fmt"
	"math"
	"math/rand"
)

// 拉普拉斯机制:对计数查询,敏感度 Δ=1,噪声尺度 b=Δ/ε。
// ε 越小越私密,噪声越大;但隐私预算是"会花完的"——每次查询都在扣余额。
func laplace(r *rand.Rand, b float64) float64 {
	u := r.Float64() - 0.5
	return -b * math.Copysign(math.Log(1-2*math.Abs(u)), u)
}

func main() {
	const total = 10000.0
	const positive = 3000.0

	fmt.Println("【一】一次计数查询:ε 与噪声幅度的关系(Δ=1,b=1/ε)")
	fmt.Printf("%-6s %10s %14s %14s\n", "ε", "噪声尺度b", "95%误差上界", "一次发布结果")
	r := rand.New(rand.NewSource(42))
	for _, eps := range []float64{0.1, 1, 10} {
		b := 1 / eps
		bound := b * math.Log(20) // 拉普拉斯 95% 区间:±b·ln(20)
		fmt.Printf("%-6.2f %10.1f %14.1f %14.0f\n", eps, b, bound, positive+laplace(r, b))
	}

	fmt.Println("\n【二】隐私预算会被花完:基本组合定理 ε_total = Σε_i")
	fmt.Printf("%-10s %10s %14s %14s\n", "单次ε", "查询次数", "累计ε", "噪声尺度")
	for _, c := range []struct {
		eps float64
		k   int
	}{{0.1, 10}, {0.1, 100}, {0.1, 1000}, {0.01, 100}} {
		fmt.Printf("%-10.2f %10d %14.1f %14.1f\n", c.eps, c.k, c.eps*float64(c.k), 1/(c.eps*float64(c.k)))
	}
	fmt.Println("读法:1000 次 ε=0.1 的查询,等价于一次 ε=100 的查询——此时噪声几乎为零,隐私等于没有。")

	fmt.Println("\n【三】成员推断实验:数据集只差一条记录,能不能问出来")
	fmt.Println("A 有 3000 个阳性,B 有 2999 个(目标记录在 A 不在 B)。")
	fmt.Println("攻击者:把 k 次回答取平均,超过 2999.5 就判定\"目标在里面\"。")
	fmt.Printf("%-8s %6s %10s %14s\n", "单次ε", "次数k", "累计ε", "判定成功率")
	for _, c := range []struct {
		eps float64
		k   int
	}{{0.01, 100}, {0.05, 100}, {0.1, 100}, {0.1, 1000}} {
		r := rand.New(rand.NewSource(7))
		trials, ok := 400, 0
		for i := 0; i < trials; i++ {
			var sumA, sumB float64
			for j := 0; j < c.k; j++ {
				sumA += positive + laplace(r, 1/c.eps)
				sumB += (positive - 1) + laplace(r, 1/c.eps)
			}
			if sumA/float64(c.k)-sumB/float64(c.k) > 0 {
				ok++
			}
		}
		fmt.Printf("%-8.2f %6d %10.2f %13.1f%%\n", c.eps, c.k, c.eps*float64(c.k), 100*float64(ok)/float64(trials))
	}

	fmt.Println("\n【四】噪声怎么随次数收敛(平均 k 次回答后的噪声标准差)")
	fmt.Printf("%-6s %10s %10s %10s\n", "ε单次", "k=10", "k=100", "k=1000")
	for _, eps := range []float64{0.1, 0.01} {
		b := 1 / eps
		fmt.Printf("%-6.2f", eps)
		for _, k := range []float64{10, 100, 1000} {
			fmt.Printf(" %10.2f", math.Sqrt(2)*b/math.Sqrt(k))
		}
		fmt.Println()
	}
}

本机运行结果(GO111MODULE=off go run main.go,Go 1.27.1 darwin/arm64):

【一】一次计数查询:ε 与噪声幅度的关系(Δ=1,b=1/ε)
ε           噪声尺度b        95%误差上界         一次发布结果
0.10         10.0           30.0           3003
1.00          1.0            3.0           3002
10.00         0.1            0.3           3000

【二】隐私预算会被花完:基本组合定理 ε_total = Σε_i
单次ε              查询次数            累计ε           噪声尺度
0.10               10            1.0            1.0
0.10              100           10.0            0.1
0.10             1000          100.0            0.0
0.01              100            1.0            1.0
读法:1000 次 ε=0.1 的查询,等价于一次 ε=100 的查询——此时噪声几乎为零,隐私等于没有。

【三】成员推断实验:数据集只差一条记录,能不能问出来
A 有 3000 个阳性,B 有 2999 个(目标记录在 A 不在 B)。
攻击者:把 k 次回答取平均,超过 2999.5 就判定"目标在里面"。
单次ε         次数k        累计ε          判定成功率
0.01        100       1.00          52.2%
0.05        100       5.00          59.8%
0.10        100      10.00          70.2%
0.10       1000     100.00          95.2%

【四】噪声怎么随次数收敛(平均 k 次回答后的噪声标准差)
ε单次          k=10      k=100     k=1000
0.10         4.47       1.41       0.45
0.01        44.72      14.14       4.47

逐条读这份输出:

  1. ε=0.1 时一次发布的结果是 3003,真值是 3000,误差 3——单次看起来很“安全”(95% 上界 30)。但安全是按“总预算”算的,不是按单次。
  2. 1000 次 ε=0.1 = 一次 ε=100,噪声尺度从 10 掉到 0.01。此时发布结果几乎等于真值,差分隐私形同虚设。这就是“同一个看板允许无限次查询”最危险的地方:每次看起来都合规,累计起来已经泄露。
  3. 成员推断成功率从 52.2% 升到 95.2%:累计 ε=1(约等于随机猜)时只比瞎猜高 2.2 个百分点;累计 ε=10 时 70.2%;ε=100 时 95.2%——攻击者只需要重复问。图中 52.2% 就是“隐私还在”的样子:它离 50% 只差 2.2 个点,统计上几乎无法区分。
  4. 平均 k 次后噪声标准差按 1/√k 收敛(ε=0.1 时 4.47 → 1.41 → 0.45)。攻击者不需要更聪明的算法,只需要更多次查询;防线只能建在“总预算”上:同一数据集、同一用户群体的查询要记账,超过预算就拒绝或加噪。

⚠️ 这三个数字说明一件事:差分隐私的安全性来自“预算管理”,不来自“加噪”。工程上必须有一个隐私预算账本(谁、对哪个数据集、花了多少 ε),否则加噪只是让第一次查询看起来无害。

决策表:你的情况选哪条

你的情况 选 关键动作 主要坑
两家机构联合建模,样本不能出域,模型可以共享 联邦学习 安全聚合或 DP-SGD;先算通信量 非独立同分布导致精度骤降;梯度泄露
只想求交集/求和/统计,结果要精确 MPC(PSI / 秘密共享) 限定两方、限定量级,先做小规模 POC 通信量放大 1–3 个数量级,跨公网基本不可行
计算逻辑复杂、想复用现有代码,信任云厂商但不想让它看数据 TEE 远程证明 + 代码度量白名单 可信内存限制、侧信道、性能折损
要对外发布统计结果/看板,可接受误差 差分隐私 建预算账本,按查询记账 预算叠加、多次查询、差分攻击
只是内部数据要给分析师用 脱敏 / 假名化 / 最小权限(38.5 数据分级与脱敏) 先做分级与访问控制 脱敏不可逆性误判、准标识符重识别

什么时候别用隐私计算

  • 数据量极小(< 1000 条):DP 的噪声会淹没信号,MPC 的通信开销占比离谱。此时更该做的是“不出域、不共享”。
  • 需要逐行精确结果(对账、结算):DP 不适用;MPC 只在参与方少、数据量小时可行。
  • 合规只要求“去标识化 + 访问控制”:先做分级、脱敏、最小权限,成本低一个数量级;隐私计算是“必须联合计算”时才付的溢价。
  • 为了投标而做 POC:没有真实数据方与真实业务问题的隐私计算项目,99% 停在演示阶段——它解决的是“多方数据不能出域”的问题,不是“想用 AI”的问题。

反模式

反模式 后果
只看单次 ε,不做预算账本 累计 ε 无限增长,成员推断成功率可达 95% 以上(见输出)
联邦学习不做安全聚合 梯度反演可还原参与方样本特征,等于数据出了域
用 TEE 代替数据分级 硬件可信不等于“该给的数据给了、不该给的也给了”
MPC 全量替换现有计算 通信量放大 1–3 个数量级,延迟与成本双双失控
把“脱敏”当隐私计算 准标识符组合可重识别,且不可逆性无法证明
项目只验收“跑通” 没有精度损失、性能、预算消耗三个数字,无法判断能不能上线

关联

自检

Q1 某看板对同一用户群体提供 100 次 ε=0.1 的统计查询,产品经理说“每次都加了噪声,肯定安全”。错在哪?用数字说明。

▸ 答案:错在把单次预算当总预算。基本组合定理给出 ε_total = 100 × 0.1 = 10;而成员推断实验显示:累计 ε=10 时,判定成功率是 70.2%,累计 ε=100 时是 95.2%——每多一次查询,攻击者的判断就准一点。正确做法是建预算账本:同一数据集、同一群体的查询总量上限(例如 ε_total ≤ 1 或 ≤ 2),超了就拒绝或降精度。

Q2 联邦学习的通信量怎么估?100 MB 模型、1000 轮、100 个客户端,量级是多少?如果通信成为瓶颈,先动哪个参数?

▸ 答案:100 MB × 2(上下行)× 1000 轮 × 100 客户端 = 20 TB。先动模型大小(100 MB → 10 MB 就降到 2 TB,降 90%),其次动轮数(1000 → 100 降到 2 TB);动客户端数通常不可行(那是业务规模)。经验判据:若单轮通信时间 > 单轮计算时间,先做模型压缩(剪枝/量化/蒸馏),而不是加带宽。

Q3 客户要求“联合计算,但结果必须精确到个位数”,且两家数据不能出域。你会推荐什么?说出它的两个硬约束。

▸ 答案:推荐 MPC(两方 PSI 或秘密共享求和),因为 DP 会引入误差,TEE 需要明文进硬件且依赖厂商。两个硬约束:① 通信量放大 1–3 个数量级——明文 1 GB 的数据,跨公网做 MPC 基本不可行,通常要求同城专线或同机房;② 参与方与轮数受限——两方、简单算子(交、和、比较)可行;三方以上或深度计算会让轮数与通信爆炸。先做“小数据量 + 精确算子”的 POC,再评估是否值得扩面。

Q4 为什么说“TEE 把信任从云厂商转移到 CPU 厂商”?在企业选型时,这个转移意味着哪些新的尽职调查?

▸ 答案:因为 TEE 的安全性由硬件与固件保证:数据在 enclave 内明文,云厂商运维看不到,但 CPU 厂商(及其微码)理论上能看到,侧信道漏洞也由它修补。转移带来的尽调:① 远程证明链是否可验证(谁来签、度量值怎么白名单化);② 漏洞响应 SLA(历史上多起侧信道漏洞需要微码/内核配合修复);③ 可信内存规格是否够装下你的工作集(不够就要分片,性能与复杂度上升);④ 合规认可度(部分行业不接受“数据明文进第三方硬件”的模型,需法务确认)。