量子就绪:混合密钥交换技术详解

本文深入探讨了混合密钥交换机制,为抵御量子计算威胁提供解决方案。文章详细解析了密钥封装机制的定义、多种KEM组合方法及其安全性考量,并介绍了IETF的复合ML-KEM标准草案,旨在实现经典密码学与后量子密码学之间的安全过渡。

量子就绪:混合密钥交换

继之前关于签名混合的文章之后,本文涵盖了混合密钥交换的基础知识,以确保数据的最大安全性。

目录

  • 快速回顾:定义
  • 组合KEM
  • 经验教训

想要提升技能?了解我们的培训课程!了解更多。

比数字签名方案更为紧迫的是,我们在互联网上交换共享秘密的方式需要过渡到后量子时代。由于对新的"后量子"密钥交换方案的认识相对不足,大多数机构鼓励使用混合方案,将知名经典方案的稳健性与新方案提供的后量子保护相结合。这篇博文详细介绍了混合密钥交换主要概念的最新状态。

快速回顾:定义

密钥封装机制

将某种东西称为密钥封装机制只是对您已经知道的问题的一种通用解决方案的华丽说法:Alice和Bob如何通过公共渠道共享一个秘密?在经典密码学中,这个问题通过Diffie-Hellman密钥交换来解决:Alice和Bob发布各自的公钥,并将他们的私钥与对方的公钥混合,从而得到只有他们自己知道的相同秘密值。在后量子密码学中,存在各种技术来建立这个共享秘密,其中一些技术已在之前关于基于格的方案的文章中讨论过。

一个密钥封装机制由三个例程定义:

  • KeyGen():负责生成一个密钥对,包括私钥部分和公钥部分。
  • Encaps(K_pub):负责生成一个秘密值和一个"封装",这是一个只能由相关私钥持有者转换回秘密值的二进制数据块。
  • Decaps(K_priv, caps):用于通过相应的私钥从封装中恢复秘密值。

例如,经典的Diffie-Hellman密钥交换可以被视为一个KEM,通过如下方式定义三个例程:

1
2
3
4
5
DH公共参数:生成器 g

KeyGen():选择一个随机整数 s。返回 K_priv = s 和 K_pub = g ** s
Encaps(K_pub):Encaps可以看作是一次性的Diffie-Hellman密钥交换。首先选择一个随机整数 x,然后计算将作为您的封装 caps = g ** x 的相关公钥,以及共享秘密 secret = K_pub ** x
Decaps(caps, K_priv):Decaps 只需计算临时公钥与我们自己私钥之间的密钥交换。secret = caps ** K_priv

混合

混合是结合后量子和经典方案以获得一种新的、更能抵御经典和量子攻击的方案的实践。有关更多细节,更完整的定义已在我们之前关于混合签名的博客文章中介绍过。如果您还没有阅读,我们鼓励您去阅读。

组合KEM

KEM组合器由F. Giacon、F. Heuer和B. Poettering在一篇2018年的研究论文中进行了深入研究。在这篇论文中,他们介绍了一个组合器的通用构造,其中所有的密钥交换都是并行执行的。实际的组合工作是在最后进行的,作为一个函数 W,它接收所有共享秘密、封装和公钥,并输出组合后的共享秘密。

在以下部分,我们研究此类函数,并深入探讨为什么它们可能是或可能不是合适的选择。

连接共享秘密

在组合多个秘密时,可能采取的第一个方法是简单地将它们全部连接起来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
- CombiKEM.KeyGen():
    S_X, P_X <= KEM_X.KeyGen()
    S_Y, P_Y <= KEM_Y.KeyGen()
    S <= S_X, S_Y
    P <= P_X, P_Y
    return S, P

- CombiKEM.Encaps(P):
    P_X, P_Y <= Split(P)
    ss_X, caps_X <= KEM_X.Encaps(P_X)
    ss_Y, caps_Y <= KEM_Y.Encaps(P_Y)
    ss <= ss_X || ss_Y                 <--- W
    caps <= caps_X, caps_Y
    return ss, caps

- CombiKEM.Decaps(caps, S):
    caps_X, caps_Y <= Split(caps)
    S_X, S_Y <= S
    ss_X <= KEM_X.Decaps(caps_X, S_X)
    ss_Y <= KEM_Y.Decaps(caps_Y, S_Y)
    ss <= ss_X || ss_Y                 <--- W
    return ss

然而,有一些原因可能表明这不是最好的做法:

  1. 最终共享秘密的长度随组合的KEM数量及其输出大小而变化。
  2. 如果其中一个KEM被攻破,最终共享秘密将部分被攻击者知晓。因此,它将不再完全"保密"。

理想情况下,攻破我们组合器中除一个之外的所有KEM不应提供足够的信息来猜测最终共享秘密的部分内容。直观上,我们希望我们的KEM能够将每个KEM带来的熵扩散到最终共享秘密的所有位。

连接共享秘密,然后哈希

有了之前关于熵扩散的经验,可以将以下组合器视为下一次迭代:

这个组合器连接组件KEM的共享秘密,然后执行密钥派生操作(例如使用哈希函数),以形成最终的共享秘密。这确保了每个输出位在熵方面都依赖于每个输入位。然而,在我们的理想组合器必须"至少与每个成分KEM一样好"的世界里,这仍然不够。

为了进一步证明这一点,让我们审视"会话密钥不可区分性"(在两篇IACR论文4中讨论过)的概念。它描述了对手在给定相应封装的情况下,区分共享秘密与随机二进制数据块的能力。

会话密钥不可区分性可以被认为是猜测"盒子里是什么?",给定两种可能的答案。

我们将在此讨论两个级别的会话密钥不可区分性:

  • 抗选择明文攻击的不可区分性(IND-CPA):挑战者提供一个公钥、一个封装、其关联的共享秘密和一个随机二进制数据块。对手可以访问一个Encaps预言机,他们可以任意次调用,使用他们选择的任何公钥。在某一时刻,对手必须决定所提供的两个二进制数据块中哪一个是所提供的封装所包裹的共享秘密。
  • 抗选择密文攻击的不可区分性(IND-CCA):挑战者仍然提供一个公钥、一个封装、其关联的共享秘密和一个随机二进制数据块。对手现在可以访问一个Encaps(P)和一个Decaps(caps)预言机(使用与挑战相同的密钥进行解封装),他们可以任意次调用,对除提供的封装之外的任何输入。在某一时刻,对手必须决定所提供的两个二进制数据块中哪一个是所提供的封装所包裹的共享秘密。

为了遵循我们"盒子里是什么?“的比喻,您可以将IND-CPA视为可以访问一个礼品包装机,允许您在猜测原始盒子里是什么之前包装任意多的物品。IND-CCA让您访问一个包装机和一个解包装机,您可以要求它们做任何事情,但不能要求解包装原始盒子。

KEM的安全性可以根据这些安全属性来评定,我们希望我们的组合器能达到与最佳成分KEM相同的会话密钥不可区分性级别。

遗憾的是,在一般情况下,连接然后哈希的组合器不符合我们的期望。确实,让我们考虑两个KEM,KEM_X和KEM_Y。我们进一步假设KEM_X是IND-CCA安全的,但KEM_Y以这样一种方式被攻破:对于固定的(但秘密的)私钥k,可以找到封装/共享秘密碰撞,即对于某个caps,找到caps′ ≠ caps使得Decaps(caps,k) =  = Decaps(caps′,k)。我们注意到KEM_Y不是IND-CCA安全的;给定一个挑战(caps,sA,sB),对手可以生成一个与caps碰撞的封装caps′,然后使用Decaps(caps′)查询解封装预言机。然后,对手可以使用预言机返回的共享秘密赢得游戏。

确实,当提供一个挑战(caps,sA,sB)时(其中caps由来自KEM_X的X_caps和来自KEM_Y的Y_caps组成),对手能够制作Y_caps′ ≠ Y_caps,该封装用KEM_Y解封装得到相同的共享秘密。因此,对手有两个不同的封装caps = X_caps, Y_caps和caps′ = X_caps, Y_caps′,它们用我们的组合KEM解封装得到相同的共享秘密。使用与KEM_Y不是IND-CCA相同的论点,当KEM_X是IND-CCA时,复合KEM不是IND-CCA。

请注意,像之前演示中的KEM_Y这样具有碰撞的KEM,可以通过参数选择不当的Diffie-Hellman密钥交换来实例化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# === DH params
p=1277723 # p = 2*q + 1, with q=638861 also prime
g=3       # order(g) = q


# === KeyGen()
# Alice privkey
a = 130376
# Alice pubkey
A = pow(g, a, p)


# === Encaps(A)
# Bob ephemeral private key
b = 644734
# Bob ephemeral public key
B = pow(g, b, p)
# Shared secret and capsule
ss, caps = pow(A, b, p), B


# === Decaps(caps, a)
assert pow(caps, a, p) == ss

# === Collision !
# Other capsule, colliding (using the fact that `a` is even)
v = 1277722    # order(v) = 2
caps_coll = (caps*v) % p
assert caps != caps_coll and pow(caps_coll, a, p) == ss

如您所见,对于给定的封装和选择不当的私钥,可以在此方案中立即找到碰撞的封装。

尽管如此,根据M. Barbosa等人的研究,如果可以证明成分KEM的密文第二原像抵抗性,那么"连接然后哈希"的组合是安全的。在我们的例子中,这个属性显然没有被遵守,因为KEM_Y是专门为了提供碰撞而设计的,因此导致了最终KEM的IND-CCA失效。

连接共享秘密和封装,然后哈希

对于一般情况,M. Campagna和A. Petcher在2020年的论文中提出将每个KEM的封装和公钥也纳入混合中。这种添加可以防御前一段中提出的攻击,因为碰撞封装的最终共享秘密现在将有所不同。然而,后量子算法的封装和密钥比经典算法要大得多。因此,这种构造带来了不可忽视的开销,表现为密钥派生函数调用的输入更大。

为了避免这种性能缺陷,IETF的复合ML-KEM草案提出了不同的方法。通过证明ML-KEM具有密文第二原像抵抗性,他们可以安全地将庞大的ML-KEM封装和公钥从KDF输入中省略。这种方法在保持所需IND-CCA安全性的同时,避免了性能损失。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
KemCombiner<KDF>(mlkemSS, tradSS, tradCT, tradPK, Domain) -> ss
[...]
    if KDF is "SHA3-256":
        ss = SHA3-256(mlkemSS || tradSS || tradCT || tradPK || Domain)

    else if KDF is "HMAC-{Hash}":
        ss = HMAC-{Hash}(key={0}, text=mlkemSS || tradSS || tradCT || tradPK || Domain)
        ss = truncate(ss, 256)
    
    return ss

IETF复合ML-KEM中使用的组合器函数。Domain是一个常量,用于指示具体组合了哪一对算法。

经验教训

混合密钥交换提供了一条审慎的前进道路,融合了经典算法经过时间考验的安全性和后量子方案的未来保障。然而,本文证明,组合这些方案的方法与方案本身同样关键。我们已经看到,简单地连接共享秘密是不够的,甚至更直观的"连接然后哈希"方法也可能引入微妙的漏洞。

要构建一个真正有弹性的混合KEM,主要有两条路径:

  1. 通用构造:不仅将共享秘密,还将每个KEM的封装和公钥都纳入最终的密钥派生函数中。这是更安全的路径,但可能由于后量子产物的大尺寸而带来显著的性能成本。
  2. 更优化的方法:例如IETF为复合ML-KEM采取的方法,依赖于证明成分KEM更强的安全属性,例如密文第二原像抵抗性。这允许采用更精简的组合,在哈希时省略庞大的后量子封装和密钥,从而在不牺牲安全性的情况下实现高性能。

总之,设计安全的混合机制,无论是用于签名还是密钥交换,都是一项细致入微的任务。开发人员应该依赖标准化并经过严格分析的构造,例如IETF提出的那些,而不是依赖直觉发明自定义解决方案。这些标准为实现高安全性和实用性能提供了可信的蓝图,确保我们的加密通信对所有对手,无论是现在还是未来,都能保持机密性。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计