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 份额生成(分发阶段)

  1. 选择素数模数p:p必须大于S和所有系数。比如p = 1613。
  2. 构造多项式:随机选k-1个系数a₁, a₂, ..., aₖ₋₁。这里k=3,所以选2个系数:
    a₁ = 166, a₂ = 94
    多项式:f(x) = 1234 + 166x + 94x² (mod 1613)
  3. 计算份额:取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
  4. 分发份额:每人拿到一个(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秘密共享在密钥封存中的完整流程:

Shamir秘密共享在密钥封存中的应用流程 Shamir秘密共享密钥封存流程 主密钥 S f(x) = S + a₁x + a₂x² + ... (k-1次多项式,模p) 计算 n 个份额 (xᵢ, f(xᵢ)) 份额持有者 1 份额持有者 2 份额持有者 3 份额持有者 4 份额持有者 5 至少 k 个份额参与恢复(本例 k=3) 恢复主密钥 S 少于 k 人无法恢复

这张图把整个流程串起来了。从主密钥出发,构造多项式,生成n个份额分发给不同的人。恢复时,只要凑够k个人,就能把主密钥拿回来。少于k个人——对不起,数学上就不可能。

最后提醒一句:Shamir秘密共享保护的是密钥的静态安全,不是动态安全。份额在传输过程中、在恢复过程中,都可能被截获。所以一定要配合TLS、HSM等传输和计算保护措施。别把份额明文发邮件——我见过有人这么干过,后果很严重。