4. 密钥拆分与门限方案:Shamir秘密共享原理、实现步骤、在封存中的应用
密钥封存有个很现实的问题:单点故障。
你把主密钥放在一个保险箱里,保险箱钥匙只有一个人管。这人出差了怎么办?他离职了怎么办?更糟的是,如果他被胁迫了呢?
所以我们需要把密钥拆开,分给多个人保管。但拆开不是切蛋糕,切碎了就拼不回来了。我们需要一种数学方法:任意凑够一定数量的人,就能恢复密钥;少于这个数,谁也得不到完整密钥。
这就是Shamir秘密共享要解决的问题。我个人认为,这是密码学里最优雅的发明之一。
4.1 核心思想:多项式插值
Shamir的秘密共享基于一个简单的数学事实:k个点可以唯一确定一个k-1次多项式。
举个例子:
- 两点确定一条直线(1次多项式)
- 三点确定一个抛物线(2次多项式)
- k点确定一个k-1次多项式
我们把秘密S编码成多项式的常数项。然后在这个多项式上取n个点,分给n个人。只要凑够k个人,就能用拉格朗日插值把多项式还原出来,拿到常数项——也就是秘密。
关键参数:
- n:总份额数(分给多少人)
- k:门限值(至少需要多少人才能恢复)
- k-1:多项式的次数
只要少于k个人,信息论上就得不到关于秘密的任何信息。注意,我说的是信息论安全,不是计算安全。这是Shamir方案最厉害的地方。
4.2 实现步骤:一个完整的例子
我们用一个具体例子走一遍。假设秘密S = 1234,我们要分给5个人,门限值k=3(任意3人可恢复)。
4.2.1 份额生成(分发阶段)
- 选择素数模数p:p必须大于S和所有系数。比如p = 1613。
- 构造多项式:随机选k-1个系数a₁, a₂, ..., aₖ₋₁。这里k=3,所以选2个系数:
a₁ = 166, a₂ = 94 多项式:f(x) = 1234 + 166x + 94x² (mod 1613) - 计算份额:取x=1,2,3,4,5,计算f(x):
f(1) = 1234 + 166 + 94 = 1494 mod 1613 = 1494 f(2) = 1234 + 332 + 376 = 1942 mod 1613 = 329 f(3) = 1234 + 498 + 846 = 2578 mod 1613 = 965 f(4) = 1234 + 664 + 1504 = 3402 mod 1613 = 176 f(5) = 1234 + 830 + 2350 = 4414 mod 1613 = 1188 - 分发份额:每人拿到一个(x, f(x))对。比如第1个人拿到(1, 1494),第2个人拿到(2, 329),以此类推。
我个人的习惯:在实际项目中,我会把x值也随机化,而不是用1,2,3,4,5这种连续整数。这样可以防止攻击者通过份额编号推断出谁和谁是一组的。
4.2.2 秘密恢复(重构阶段)
假设我们收集到3个人的份额:第1人(1, 1494)、第3人(3, 965)、第5人(5, 1188)。
用拉格朗日插值公式:
f(0) = Σᵢ [ yᵢ · Πⱼ≠ᵢ (0 - xⱼ) / (xᵢ - xⱼ) ] mod p
代入计算:
L₁ = 1494 · [(0-3)/(1-3)] · [(0-5)/(1-5)]
= 1494 · [(-3)/(-2)] · [(-5)/(-4)]
= 1494 · (3/2) · (5/4)
= 1494 · 15/8
= 1494 · 15 · 8⁻¹ mod 1613
8⁻¹ mod 1613 = 1417(扩展欧几里得算法求得)
L₁ = 1494 · 15 · 1417 mod 1613 = 1494 · 21255 mod 1613
= 1494 · 286 mod 1613 = 427284 mod 1613 = 1554
L₃ = 965 · [(0-1)/(3-1)] · [(0-5)/(3-5)]
= 965 · [(-1)/2] · [(-5)/(-2)]
= 965 · (-1/2) · (5/2)
= 965 · (-5/4)
= 965 · (-5) · 4⁻¹ mod 1613
4⁻¹ mod 1613 = 1210
L₃ = 965 · (-5) · 1210 mod 1613 = 965 · (-6050) mod 1613
= 965 · 1002 mod 1613 = 966930 mod 1613 = 755
L₅ = 1188 · [(0-1)/(5-1)] · [(0-3)/(5-3)]
= 1188 · [(-1)/4] · [(-3)/2]
= 1188 · (3/8)
= 1188 · 3 · 8⁻¹ mod 1613
= 1188 · 3 · 1417 mod 1613 = 1188 · 4251 mod 1613
= 1188 · 1025 mod 1613 = 1217700 mod 1613 = 41
S = L₁ + L₃ + L₅ mod 1613 = 1554 + 755 + 41 = 2350 mod 1613 = 1234 ✓
恢复成功。秘密就是1234。
我曾经踩过的坑:模逆元计算一定要用扩展欧几里得算法,不要用费马小定理硬算。费马小定理要求模数是素数,但p不一定是素数——虽然我们通常选素数,但有些实现里为了性能会选合数。扩展欧几里得算法更通用,也更安全。
4.3 在密钥封存中的应用
Shamir秘密共享在企业级密钥封存里,主要用在三个场景:
| 场景 | 典型配置 | 说明 |
|---|---|---|
| 根密钥拆分 | n=7, k=4 | HSM主密钥拆成7份,4人可恢复。董事会成员各持一份。 |
| 灾难恢复密钥 | n=5, k=3 | 异地灾备密钥,3个区域负责人可恢复。 |
| 操作员密钥 | n=3, k=2 | 日常操作密钥,2人同时在场才能执行敏感操作。 |
你想想看,如果根密钥只放在一个HSM里,HSM坏了怎么办?如果分给7个人,只要凑够4个就能恢复。而且这4个人可以是不同部门、不同地域的——攻击者要同时控制4个人,难度比控制1个人大得多。
实际部署建议:
- 份额持有者之间不能互相知道对方是谁。我见过一个项目,因为人事部门把份额持有者名单公开了,结果社会工程攻击直接锁定了目标。
- 份额要物理隔离存储。有人把份额存在同一个U盘里——那还不如不拆。
- 定期验证份额有效性。份额可能因为介质损坏而丢失,定期验证可以提前发现问题。
4.4 方案对比:Shamir vs 其他拆分方法
| 方案 | 安全性 | 灵活性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| Shamir秘密共享 | 信息论安全 | 高(可调n和k) | 中 | 通用密钥封存 |
| XOR拆分 | 信息论安全 | 低(仅2选1) | 低 | 简单双人控制 |
| 逐位拆分 | 计算安全 | 低 | 低 | 不推荐 |
| 门限RSA | 计算安全 | 高 | 高 | 分布式签名 |
说白了,Shamir方案在安全性和灵活性之间取得了最好的平衡。XOR拆分虽然简单,但只能做到2选1。逐位拆分更不靠谱——你想想看,如果攻击者拿到一部分位,他就能猜出剩余位的大致范围。
4.5 避坑指南
我做了这么多年密钥管理,踩过的坑不少。这里列几个最常见的:
- 模数p选太小:p必须大于秘密S,否则恢复时会丢失信息。我见过有人用8位素数,结果秘密比p还大,恢复出来全是错的。
- 系数随机性不够:随机数生成器如果被预测,攻击者就能算出多项式。一定要用密码学安全的随机数生成器(CSPRNG)。
- 份额没有完整性保护:攻击者篡改份额后,恢复出来的秘密是错的。建议对每个份额附加HMAC,或者用可验证秘密共享(VSS)。
- 门限值k设置不合理:k太接近n,容错性差;k太小,安全性差。一般建议k = n/2 + 1。
嗯,这里要注意:Shamir秘密共享是信息论安全的,但前提是随机数生成器足够好。如果随机数生成器被攻破,整个方案就废了。所以我在项目中,会用硬件随机数生成器来生成多项式系数,而不是用软件伪随机数。
4.6 核心流程示意图
下面这张图展示了Shamir秘密共享在密钥封存中的完整流程:
这张图把整个流程串起来了。从主密钥出发,构造多项式,生成n个份额分发给不同的人。恢复时,只要凑够k个人,就能把主密钥拿回来。少于k个人——对不起,数学上就不可能。
最后提醒一句:Shamir秘密共享保护的是密钥的静态安全,不是动态安全。份额在传输过程中、在恢复过程中,都可能被截获。所以一定要配合TLS、HSM等传输和计算保护措施。别把份额明文发邮件——我见过有人这么干过,后果很严重。