Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
222 lines (146 sloc) 12.4 KB

TLS 1.3 Cryptographic Computations

TLS 握手建立一个或多个输入的 secrets,如下文所述,将这些 secrets 组合起来以创建实际工作密钥材料。密钥派生过程包含输入 secrets 和握手记录。请注意,由于握手记录包含来自 Hello 消息的随机值,因此即使使用相同的输入 secrets,任何给定的握手都将具有不同的流量 secrets,就像将相同的 PSK 用于多个连接的情况一样。

一. Key Schedule

密钥派生过程使用 HKDF [RFC5869] 定义的 HKDF-Extract 和 HKDF-Expand 函数,以及下面定义的函数:

       HKDF-Expand-Label(Secret, Label, Context, Length) =
            HKDF-Expand(Secret, HkdfLabel, Length)

       Where HkdfLabel is specified as:

       struct {
           uint16 length = Length;
           opaque label<7..255> = "tls13 " + Label;
           opaque context<0..255> = Context;
       } HkdfLabel;

       Derive-Secret(Secret, Label, Messages) =
            HKDF-Expand-Label(Secret, Label,
                              Transcript-Hash(Messages), Hash.length)

Transcript-Hash 和 HKDF 使用的 Hash 函数是密码套件哈希算法。Hash.length 是其输出长度(以字节为单位)。消息是表示的握手消息的串联,包括握手消息类型和长度字段,但不包括记录层头。请注意,在某些情况下,零长度 context(由 "" 表示)传递给 HKDF-Expand-Label。本文档中指定的 labels 都是 ASCII 字符串,不包括尾随 NUL 字节。

注意:对于常见的哈希函数,任何超过 12 个字符的 label 都需要额外迭代哈希函数才能计算。所有标准都已选择遵守此限制。

密钥是从使用 HKDF-Extract 和 Derive-Secret 函数的两个输入 secrets 中派生出来的。添加新 secret 的一般模式是使用 HKDF-Extract,其中 Salt 是当前的 secret 状态,输入密钥材料(IKM)是要添加的新 secret 。在此版本的 TLS 1.3 中,两个输入 secrets 是:

  • PSK(外部建立的预共享密钥,或从先前连接的 resumption_master_secret 值派生的)

  • (EC)DHE 共享 secret (Section 7.4)

这将生成一个完整的密钥推导计划,如下图所示。在此图中,约定以下的格式:

  • HKDF-Extract 画在图上,它为从顶部获取 Salt 参数,从左侧获取 IKM 参数,它的输出是底部,和右侧输出的名称。

  • Derive-Secret 的 Secret 参数由传入的箭头指示。例如,Early Secret 是生成 client_early_traffic_secret 的 Secret。

  • "0" 表示将 Hash.length 字节的字符串设置为零。

             0
             |
             v
   PSK ->  HKDF-Extract = Early Secret
             |
             +-----> Derive-Secret(., "ext binder" | "res binder", "")
             |                     = binder_key
             |
             +-----> Derive-Secret(., "c e traffic", ClientHello)
             |                     = client_early_traffic_secret
             |
             +-----> Derive-Secret(., "e exp master", ClientHello)
             |                     = early_exporter_master_secret
             v
       Derive-Secret(., "derived", "")
             |
             v
   (EC)DHE -> HKDF-Extract = Handshake Secret
             |
             +-----> Derive-Secret(., "c hs traffic",
             |                     ClientHello...ServerHello)
             |                     = client_handshake_traffic_secret
             |
             +-----> Derive-Secret(., "s hs traffic",
             |                     ClientHello...ServerHello)
             |                     = server_handshake_traffic_secret
             v
       Derive-Secret(., "derived", "")
             |
             v
   0 -> HKDF-Extract = Master Secret
             |
             +-----> Derive-Secret(., "c ap traffic",
             |                     ClientHello...server Finished)
             |                     = client_application_traffic_secret_0
             |
             +-----> Derive-Secret(., "s ap traffic",
             |                     ClientHello...server Finished)
             |                     = server_application_traffic_secret_0
             |
             +-----> Derive-Secret(., "exp master",
             |                     ClientHello...server Finished)
             |                     = exporter_master_secret
             |
             +-----> Derive-Secret(., "res master",
                                   ClientHello...client Finished)
                                   = resumption_master_secret

这里的一般模式指的是,图左侧显示的 secrets 是没有上下文的原始熵,而右侧的 secrets 包括握手上下文,因此可以用来派生工作密钥而无需额外的上下文。请注意,对 Derive-Secret 的不同调用可能会使用不同的 Messages 参数,即使是具有相同的 secret。在 0-RTT 交换中,Derive-Secret 和四个不同的副本一起被调用;在 1-RTT-only 交换中,它和三个不同的副本一起被调用。

如果给定的 secret 不可用,则使用由设置为零的 Hash.length 字节串组成的 0 值。请注意,这并不意味着要跳过轮次,因此如果 PSK 未被使用,Early Secret 仍将是 HKDF-Extract(0,0)。对于 binder_key 的计算,label 是外部 PSK(在 TLS 之外提供的那些)的 "ext binder" 和用于恢复 PSK 的 "res binder"(提供为先前握手的恢复主密钥的那些)。不同的 labels 阻止了一种 PSK 替代另一种 PSK。

这存在有多个潜在的 Early Secret 值,具体取决于 Server 最终选择的 PSK。Client 需要为每个潜在的 PSK 都计算一个值;如果没有选择 PSK,则需要计算对应于零 PSK 的 Early Secret。

一旦计算出了从给定 secret 派生出的所有值,就应该删除该 secret。

二. Updating Traffic Secrets

一旦握手完成后,任何一方都可以使用第 4.6.3 节中定义的 KeyUpdate 握手消息更新其发送流量密钥。 下一代流量密钥的计算方法是,如本节所述,从 client_ / server_application_traffic_secret_N 生成出 client_ / server_application_traffic_secret_N + 1,然后按 7.3 节所述方法重新导出流量密钥。

下一代 application_traffic_secret 计算方法如下:

       application_traffic_secret_N+1 =
           HKDF-Expand-Label(application_traffic_secret_N,
                             "traffic upd", "", Hash.length)

一旦计算了 client_ / server_application_traffic_secret_N + 1 及其关联的流量密钥,实现方应该删除 client_ / server_application_traffic_secret_N 及其关联的流量密钥。

三. Traffic Key Calculation

流量密钥材料由以下输入值生成:

  • secret 的值

  • 表示正在生成的特定值的目的值

  • 生成密钥的长度

使用输入流量 secret 的值生成流量密钥材料:

  [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
  [sender]_write_iv  = HKDF-Expand-Label(Secret, "iv", "", iv_length)

[sender] 表示发送方。每种记录类型的 Secret 值显示在下表中:

       +-------------------+---------------------------------------+
       | Record Type       | Secret                                |
       +-------------------+---------------------------------------+
       | 0-RTT Application | client_early_traffic_secret           |
       |                   |                                       |
       | Handshake         | [sender]_handshake_traffic_secret     |
       |                   |                                       |
       | Application Data  | [sender]_application_traffic_secret_N |
       +-------------------+---------------------------------------+

每当底层 Secret 更改时(例如,从握手更改为应用数据密钥或密钥更新时),将重新计算所有流量密钥材料。

四. (EC)DHE Shared Secret Calculation

1. Finite Field Diffie-Hellman

对于有限的字段组合,执行传统的 Diffie-Hellman [DH76] 计算。协商密钥(Z)被转换成一个字节字符串,字符串以 big-endian 形式编码,并用零往左边填充到初始的大小。此字节字符串用作上面指定的密钥计划中的共享密钥。

请注意,此结构与先前版本的 TLS 不同,TLS 之前的版本删除了前导零。

2. Elliptic Curve Diffie-Hellman

对于 secp256r1,secp384r1 和 secp521r1,ECDH 计算(包括参数和密钥生成以及共享密钥计算)根据 [IEEE1363] 使用 ECKAS-DH1 方案执行,identity 映射作为密钥导出函数(KDF), 因此,共享 secret 是表示为八位字节串的 ECDH 共享秘密椭圆曲线点的 x 坐标。   注意,FE2OSP (字段元素到八位字符串转换原语)输出的该八位字节串(IEEE 1363 术语中的“Z”)对于任何给定字段都具有恒定长度;在此八位字符串中找到的前导零不得被截断。

(请注意,使用 KDF 标识是一种技术性。完整的做法是 ECDH 与 KDF 一起使用,因为 TLS 不直接将此 secret 用于计算其他 secret 以外的任何其他内容。)

对于 X25519 和 X448,ECDH 计算如下:

  • 放入 KeyShareEntry.key_exchange 结构的公钥是将 ECDH 标量乘法函数应用于适当长度(标量输入)和标准公共基点( u 坐标点输入)的密钥的结果。

  • ECDH 共享密钥是将 ECDH 标量乘法函数应用于密钥(标量输入)和对等方的公钥( u 坐标点输入)的结果。输出是直接使用的,没有经过处理的。

对于这些曲线,实现方应该使用 RFC7748 中指定的方法来计算 Diffie-Hellman 共享密钥。实现方必须检查计算的 Diffie-Hellman 共享密钥是否为全零值,如果是,则中止,如 [RFC7748] 第 6 节 所述。如果实现者使用这些椭圆曲线的替代实现,他们应该执行 [RFC7748] 第 7 节中指定的附加检查。

五. Exporters

[RFC5705] 根据 TLS 伪随机函数(PRF)定义 TLS 的密钥材料 exporter。本文档用 HKDF 取代 PRF,因此需要新的结构。exporter 的接口保持不变。

exporter 的值计算方法如下:

   TLS-Exporter(label, context_value, key_length) =
       HKDF-Expand-Label(Derive-Secret(Secret, label, ""),
                         "exporter", Hash(context_value), key_length)

Secret 可以是 early_exporter_master_secret 或 exporter_master_secret。除非应用程序明确指定,否则实现方必须使用exporter_master_secret。early_exporter_master_secret 被定义用来在 0-RTT 数据需要 exporter 的设置这种情况中使用。建议为 early exporter 提供单独的接口;这可以避免 exporter 用户在需要常规 exporter 时意外使用 early exporter,反之亦然。

如果未提供上下文,则 context_value 为零长度。因此,不提供上下文计算与提供空上下文得到的结果都是相同的。这是对以前版本的 TLS 的更改,以前的 TLS 版本中,空的上下文产生的输出与不提供的上下文的结果不同。截至本文档,无论是否使用上下文,都不会使用已分配的 exporter 标签。未来的规范绝不能定义允许空上下文和没有相同标签的上下文的 exporter 的使用。exporter 的新用法应该是在所有 exporter 计算中提供上下文,尽管值可能为空。

exporter 标签格式的要求在 [RFC5705] 第4节 中定义。


Reference:

RFC 8446

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: https://halfrost.com/TLS_1.3_Cryptographic_Computations/