TLS 1.3 中的密钥派生过程:从共享秘密到应用密钥的 HKDF 链
字数 3873 2025-12-13 17:15:22

TLS 1.3 中的密钥派生过程:从共享秘密到应用密钥的 HKDF 链

题目描述

在 TLS 1.3 协议中,一旦客户端和服务器通过密钥交换(如基于椭圆曲线的迪菲-赫尔曼密钥交换 ECDHE)生成了一个共享密钥(Pre-Shared Key, PSK),并不能直接用作加密或认证的密钥。为了生成多个用于不同用途(如加密客户端到服务器的流量、服务器到客户端的流量、计算握手消息的完整性校验码等)的独立且安全的密钥,TLS 1.3 使用了基于 HMAC 的密钥派生函数(HKDF)来构建一个层次化的密钥派生链。本题将详细解析这个过程:如何从初始的共享秘密出发,经过一系列确定的、不可逆的步骤,派生出最终的应用层加密密钥。

解题过程

整个密钥派生过程可以看作一条单向的、可扩展的派生链。其核心是 HKDF 的两个组成部分:HKDF-Extract 和 HKDF-Expand。我们用“=>”表示派生关系。

步骤 1: 起点——初始密钥材料的确定

在 TLS 1.3 握手中,密钥派生的起点被称为“早期密钥”或初始“输入密钥材料”(Input Keying Material, IKM)。对于最常见的(EC)DHE 握手,这个初始秘密是两个计算得到的值经过 HKDF-Extract 后的结果:

  1. (EC)DHE 共享秘密(SS): 由客户端和服务器各自计算得到的相同值。
  2. “0”值盐(Salt): 在第一次调用 HKDF-Extract 时,通常使用一个全零的字节串作为盐(Salt),或者,如果使用了预共享密钥(PSK),这个盐会是一个之前派生的值。

这个初始提取步骤可以形式化表示为:
Early Secret = HKDF-Extract(salt=0, IKM=(EC)DHE Shared Secret)
这里的 HKDF-Extract(salt, IKM) 本质上是 HMAC-Hash(salt, IKM),其输出是一个密码学意义上强度足够、均匀分布的伪随机密钥(PRK)。这个 Early Secret 是整个密钥派生树的根。

步骤 2: 派生握手阶段密钥——密钥树的第一次分支

接下来,需要为握手消息的完整性(MAC)派生一个密钥。这是通过 HKDF-Expand-Label 函数实现的,它是 HKDF-Expand 的一个封装,方便在 TLS 1.3 的特定上下文中添加标签。
Derived Secret = HKDF-Expand-Label(PRK, label, Context, Length)
其中:

  • PRK: 输入密钥,这里是上一步的 Early Secret
  • label: 一个ASCII字符串标签,明确标识派生密钥的用途,例如 "derived"
  • Context: 可选的上下文信息,在握手阶段通常为空或包含握手哈希。
  • Length: 期望的输出密钥长度。

具体过程如下:

  1. 计算 Handshake Secret 的中间派生值:Derived Secret = HKDF-Expand-Label(Early Secret, "derived", "", Hash.length)。这个 Derived Secret 用于“重置”派生链,作为下一次 HKDF-Extract 的盐。
  2. 将 (EC)DHE 共享秘密与这个新的盐再次提取,得到 Handshake SecretHandshake Secret = HKDF-Extract(salt=Derived Secret, IKM=(EC)DHE Shared Secret)
  3. Handshake Secret 派生出两个实际使用的密钥:
    • 客户端握手流量密钥(client_handshake_traffic_secret)= HKDF-Expand-Label(Handshake Secret, "c hs traffic", Handshake Context, Hash.length)
    • 服务器握手流量密钥(server_handshake_traffic_secret)= HKDF-Expand-Label(Handshake Secret, "s hs traffic", Handshake Context, Hash.length)
    • 这里的 Handshake Context 是到“Server Hello”消息为止(但不包含证书等加密后的消息)所有握手消息的哈希值,用于绑定密钥到当前特定的握手过程。

步骤 3: 派生物流阶段密钥——密钥树的第二次分支

握手消息使用上述密钥加密认证后,双方计算到目前为止(包含所有明文握手消息和加密的证书等)的所有握手消息的哈希,称为 Handshake Hash。然后用类似步骤2的过程,从 Handshake Secret 派生出应用数据阶段的根密钥。

  1. 计算 Master Secret 的中间派生值:再次使用“derived”标签派生一个新的盐:Derived Secret' = HKDF-Expand-Label(Handshake Secret, "derived", "", Hash.length)
  2. 用这个新盐和一个“0”值的 IKM 进行提取,得到 Master Secret(在TLS 1.3规范中也称为 application_traffic_secret_0 的父秘密):Master Secret = HKDF-Extract(salt=Derived Secret', IKM=0)。这里的 IKM 是0,意味着我们不再引入新的熵,而是对现有的 Handshake Secret 进行转换,以物理上隔离握手密钥和应用密钥。
  3. Master Secret 派生出最终的、用于加密应用数据的两个密钥:
    • 客户端应用流量密钥(client_application_traffic_secret_0)= HKDF-Expand-Label(Master Secret, "c ap traffic", Handshake Hash, Hash.length)
    • 服务器应用流量密钥(server_application_traffic_secret_0)= HKDF-Expand-Label(Master Secret, "s ap traffic", Handshake Hash, Hash.length)
    • 这里的 Handshake Hash 是完整的握手消息哈希,确保了应用密钥与整个握手过程唯一绑定。

步骤 4: 从流量密钥到实际加密密钥

上一步得到的 *_traffic_secret 还不是直接用于 AES 或 ChaCha20 的密钥。它们是“密钥块”(Key Block)。TLS 1.3 会从这些“密钥块”中,通过 HKDF-Expand 派生出具体的对称密钥和初始化向量(IV):

  • 写密钥(client_write_key)= HKDF-Expand-Label(client_application_traffic_secret_N, "key", "", key_length)
  • 写初始向量(client_write_iv)= HKDF-Expand-Label(client_application_traffic_secret_N, "iv", "", iv_length)
  • 这里的 client_application_traffic_secret_N 中的 N 从0开始,可以随着密钥更新(Key Update)而递增,以实现前向安全。

核心安全设计思想

  1. 密钥分离:通过 HKDF 的链式结构和独特的标签(“c hs traffic”, “s ap traffic”, “key”, “iv”),确保了不同方向、不同阶段的密钥是独立且不相关的。即使一个密钥泄露,也不会危及其他密钥。
  2. 上下文绑定:在 HKDF-Expand-Label 中引入握手消息的哈希值作为上下文,确保了密钥与当前特定的通信会话绑定。即使相同的预主密钥被重用,只要握手的消息有细微差别,派生出的密钥也会完全不同,防止重放攻击和会话重复。
  3. 前向安全:每次密钥派生都是单向的。应用层密钥(Master Secret 及其派生物)与握手阶段密钥(Handshake Secret 及其派生物)通过一次以 Derived Secret' 为盐、0为IKM的 HKDF-Extract 操作进行了密码学意义上的“切割”。一旦握手完成,Handshake Secret 和 (EC)DHE 共享秘密可以被安全地删除,即使它们后来泄露,攻击者也无法回推出应用流量密钥,因为 HKDF 具有单向性。

通过这条从 Early Secret -> Handshake Secret -> Master Secret -> *_traffic_secret -> write_key/write_iv 的清晰、结构化的 HKDF 派生链,TLS 1.3 实现了从单一的共享秘密到多个安全、独立、会话绑定的工作密钥的稳健转换。

TLS 1.3 中的密钥派生过程:从共享秘密到应用密钥的 HKDF 链 题目描述 在 TLS 1.3 协议中,一旦客户端和服务器通过密钥交换(如基于椭圆曲线的迪菲-赫尔曼密钥交换 ECDHE)生成了一个共享密钥(Pre-Shared Key, PSK),并不能直接用作加密或认证的密钥。为了生成多个用于不同用途(如加密客户端到服务器的流量、服务器到客户端的流量、计算握手消息的完整性校验码等)的独立且安全的密钥,TLS 1.3 使用了基于 HMAC 的密钥派生函数(HKDF)来构建一个层次化的密钥派生链。本题将详细解析这个过程:如何从初始的共享秘密出发,经过一系列确定的、不可逆的步骤,派生出最终的应用层加密密钥。 解题过程 整个密钥派生过程可以看作一条单向的、可扩展的派生链。其核心是 HKDF 的两个组成部分:HKDF-Extract 和 HKDF-Expand。我们用“=>”表示派生关系。 步骤 1: 起点——初始密钥材料的确定 在 TLS 1.3 握手中,密钥派生的起点被称为“早期密钥”或初始“输入密钥材料”(Input Keying Material, IKM)。对于最常见的(EC)DHE 握手,这个初始秘密是两个计算得到的值经过 HKDF-Extract 后的结果: (EC)DHE 共享秘密(SS) : 由客户端和服务器各自计算得到的相同值。 “0”值盐(Salt) : 在第一次调用 HKDF-Extract 时,通常使用一个全零的字节串作为盐(Salt),或者,如果使用了预共享密钥(PSK),这个盐会是一个之前派生的值。 这个初始提取步骤可以形式化表示为: Early Secret = HKDF-Extract(salt=0, IKM=(EC)DHE Shared Secret) 这里的 HKDF-Extract(salt, IKM) 本质上是 HMAC-Hash(salt, IKM) ,其输出是一个密码学意义上强度足够、均匀分布的伪随机密钥(PRK)。这个 Early Secret 是整个密钥派生树的根。 步骤 2: 派生握手阶段密钥——密钥树的第一次分支 接下来,需要为握手消息的完整性(MAC)派生一个密钥。这是通过 HKDF-Expand-Label 函数实现的,它是 HKDF-Expand 的一个封装,方便在 TLS 1.3 的特定上下文中添加标签。 Derived Secret = HKDF-Expand-Label(PRK, label, Context, Length) 其中: PRK : 输入密钥,这里是上一步的 Early Secret 。 label : 一个ASCII字符串标签,明确标识派生密钥的用途,例如 "derived" 。 Context : 可选的上下文信息,在握手阶段通常为空或包含握手哈希。 Length : 期望的输出密钥长度。 具体过程如下: 计算 Handshake Secret 的中间派生值: Derived Secret = HKDF-Expand-Label(Early Secret, "derived", "", Hash.length) 。这个 Derived Secret 用于“重置”派生链,作为下一次 HKDF-Extract 的盐。 将 (EC)DHE 共享秘密与这个新的盐再次提取,得到 Handshake Secret : Handshake Secret = HKDF-Extract(salt=Derived Secret, IKM=(EC)DHE Shared Secret) 。 从 Handshake Secret 派生出两个实际使用的密钥: 客户端握手流量密钥(client_ handshake_ traffic_ secret) : = HKDF-Expand-Label(Handshake Secret, "c hs traffic", Handshake Context, Hash.length) 服务器握手流量密钥(server_ handshake_ traffic_ secret) : = HKDF-Expand-Label(Handshake Secret, "s hs traffic", Handshake Context, Hash.length) 这里的 Handshake Context 是到“Server Hello”消息为止(但不包含证书等加密后的消息)所有握手消息的哈希值,用于绑定密钥到当前特定的握手过程。 步骤 3: 派生物流阶段密钥——密钥树的第二次分支 握手消息使用上述密钥加密认证后,双方计算到目前为止(包含所有明文握手消息和加密的证书等)的所有握手消息的哈希,称为 Handshake Hash 。然后用类似步骤2的过程,从 Handshake Secret 派生出应用数据阶段的根密钥。 计算 Master Secret 的中间派生值:再次使用“derived”标签派生一个新的盐: Derived Secret' = HKDF-Expand-Label(Handshake Secret, "derived", "", Hash.length) 。 用这个新盐和一个“0”值的 IKM 进行提取,得到 Master Secret (在TLS 1.3规范中也称为 application_traffic_secret_0 的父秘密): Master Secret = HKDF-Extract(salt=Derived Secret', IKM=0) 。这里的 IKM 是0,意味着我们不再引入新的熵,而是对现有的 Handshake Secret 进行转换,以物理上隔离握手密钥和应用密钥。 从 Master Secret 派生出最终的、用于加密应用数据的两个密钥: 客户端应用流量密钥(client_ application_ traffic_ secret_ 0) : = HKDF-Expand-Label(Master Secret, "c ap traffic", Handshake Hash, Hash.length) 服务器应用流量密钥(server_ application_ traffic_ secret_ 0) : = HKDF-Expand-Label(Master Secret, "s ap traffic", Handshake Hash, Hash.length) 这里的 Handshake Hash 是完整的握手消息哈希,确保了应用密钥与整个握手过程唯一绑定。 步骤 4: 从流量密钥到实际加密密钥 上一步得到的 *_traffic_secret 还不是直接用于 AES 或 ChaCha20 的密钥。它们是“密钥块”(Key Block)。TLS 1.3 会从这些“密钥块”中,通过 HKDF-Expand 派生出具体的对称密钥和初始化向量(IV): 写密钥(client_ write_ key) : = HKDF-Expand-Label(client_application_traffic_secret_N, "key", "", key_length) 写初始向量(client_ write_ iv) : = HKDF-Expand-Label(client_application_traffic_secret_N, "iv", "", iv_length) 这里的 client_application_traffic_secret_N 中的 N 从0开始,可以随着密钥更新(Key Update)而递增,以实现前向安全。 核心安全设计思想 密钥分离 :通过 HKDF 的链式结构和独特的标签( “c hs traffic” , “s ap traffic” , “key” , “iv” ),确保了不同方向、不同阶段的密钥是独立且不相关的。即使一个密钥泄露,也不会危及其他密钥。 上下文绑定 :在 HKDF-Expand-Label 中引入握手消息的哈希值作为上下文,确保了密钥与当前特定的通信会话绑定。即使相同的预主密钥被重用,只要握手的消息有细微差别,派生出的密钥也会完全不同,防止重放攻击和会话重复。 前向安全 :每次密钥派生都是单向的。应用层密钥( Master Secret 及其派生物)与握手阶段密钥( Handshake Secret 及其派生物)通过一次以 Derived Secret' 为盐、0为IKM的 HKDF-Extract 操作进行了密码学意义上的“切割”。一旦握手完成, Handshake Secret 和 (EC)DHE 共享秘密可以被安全地删除,即使它们后来泄露,攻击者也无法回推出应用流量密钥,因为 HKDF 具有单向性。 通过这条从 Early Secret -> Handshake Secret -> Master Secret -> *_traffic_secret -> write_key/write_iv 的清晰、结构化的 HKDF 派生链,TLS 1.3 实现了从单一的共享秘密到多个安全、独立、会话绑定的工作密钥的稳健转换。