7. Cryptographic Computations (密码学计算)
TLS握手建立一个或多个输入秘密,这些秘密组合起来创建实际的工作密钥材料,如下所述。密钥派生过程结合了输入秘密和握手转录。请注意,因为握手转录包括来自Hello消息的随机值,所以任何给定的握手都将具有不同的流量秘密,即使使用相同的输入秘密,如在多个连接使用相同PSK时的情况。
7.1. Key Schedule (密钥计划)
密钥派生过程使用为HKDF [RFC5869]定义的HKDF-Extract和HKDF-Expand函数,以及下面定义的函数:
HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret, HkdfLabel, Length)
其中HkdfLabel指定为:
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是其输出长度(以字节为单位)。Messages是指示的握手消息的连接,包括握手消息类型和长度字段,但不包括记录层头。请注意,在某些情况下,零长度Context(由""表示)会传递给HKDF-Expand-Label。本文档中指定的标签都是ASCII字符串,不包括尾随的NUL字节。
注意:对于常见的哈希函数,任何长度超过12个字符的标签都需要哈希函数的额外迭代来计算。本规范中的标签都已选择以适合此限制。
密钥使用HKDF-Extract和Derive-Secret函数从两个输入秘密派生。添加新秘密的一般模式是使用HKDF-Extract,其中Salt是当前秘密状态,输入密钥材料(IKM)是要添加的新秘密。在此版本的TLS 1.3中,两个输入秘密是:
- PSK(外部建立或从先前连接的resumption_master_secret值派生的预共享密钥)
- (EC)DHE共享秘密(第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
这里的一般模式是,图左侧显示的秘密只是没有上下文的原始熵,而右侧的秘密包括握手上下文,因此可以用于派生工作密钥而无需额外上下文。请注意,对Derive-Secret的不同调用可能采用不同的Messages参数,即使使用相同的秘密。在0-RTT交换中,使用四个不同的转录调用Derive-Secret;在仅1-RTT交换中,使用三个不同的转录调用它。
如果给定的秘密不可用,则使用由设置为零的Hash.length字节字符串组成的0值。请注意,这并不意味着跳过轮次,因此如果不使用PSK,Early Secret仍将是HKDF-Extract(0, 0)。对于binder_key的计算,外部PSK(在TLS之外提供的PSK)的标签是"ext binder",恢复PSK(作为先前握手的恢复主秘密提供的PSK)的标签是"res binder"。不同的标签防止一种类型的PSK替换另一种类型。
存在多个潜在的Early Secret值,具体取决于服务器最终选择哪个PSK。客户端需要为每个潜在的PSK计算一个;如果未选择PSK,则需要计算与零PSK对应的Early Secret。
一旦计算出要从给定秘密派生的所有值,就应该 (SHOULD) 擦除该秘密。
7.2. 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及其关联的流量密钥,实现应该 (SHOULD) 删除client_/server_application_traffic_secret_N及其关联的流量密钥。
7.3. Traffic Key Calculation (流量密钥计算)
流量密钥材料从以下输入值生成:
- 秘密值
- 指示正在生成的特定值的目的值
- 正在生成的密钥的长度
流量密钥材料使用以下方法从输入流量秘密值生成:
[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
[sender]表示发送侧。下表显示了每种记录类型的Secret值。
| 记录类型 | Secret |
|---|---|
| 0-RTT应用 | client_early_traffic_secret |
| 握手 | [sender]_handshake_traffic_secret |
| 应用数据 | [sender]_application_traffic_secret_N |
每当底层Secret更改时(例如,当从握手密钥更改到应用数据密钥时或在密钥更新时),所有流量密钥材料都会重新计算。
7.4. (EC)DHE Shared Secret Calculation ((EC)DHE共享秘密计算)
7.4.1. Finite Field Diffie-Hellman (有限域Diffie-Hellman)
对于有限域组,执行传统的Diffie-Hellman [DH76]计算。协商的密钥(Z)通过以大端形式编码并在左侧用零填充到素数大小来转换为字节字符串。此字节字符串用作上述密钥计划中的共享秘密。
请注意,此构造与删除前导零的TLS先前版本不同。
7.4.2. Elliptic Curve Diffie-Hellman (椭圆曲线Diffie-Hellman)
对于secp256r1、secp384r1和secp521r1,ECDH计算(包括参数和密钥生成以及共享秘密计算)根据[IEEE1363]使用ECKAS-DH1方案执行,将身份映射用作密钥派生函数(KDF),以便共享秘密是ECDH共享秘密椭圆曲线点的x坐标,表示为八位字节字符串。请注意,此八位字节字符串(IEEE 1363术语中的"Z")由FE2OSP(字段元素到八位字节字符串转换原语)输出,对于任何给定字段具有恒定长度;此八位字节字符串中找到的前导零不得 (MUST NOT) 被截断。
(请注意,此身份KDF的使用是技术性的。完整的情况是,ECDH与非平凡的KDF一起使用,因为TLS不直接将此秘密用于除计算其他秘密之外的任何用途。)
对于X25519和X448,ECDH计算如下:
-
要放入KeyShareEntry.key_exchange结构的公钥是将ECDH标量乘法函数应用于适当长度的秘密密钥(到标量输入)和标准公共基点(到u坐标点输入)的结果。
-
ECDH共享秘密是将ECDH标量乘法函数应用于秘密密钥(到标量输入)和对等体的公钥(到u坐标点输入)的结果。输出直接使用,无需处理。
对于这些曲线,实现应该 (SHOULD) 使用[RFC7748]中指定的方法来计算Diffie-Hellman共享秘密。实现必须 (MUST) 检查计算的Diffie-Hellman共享秘密是否为全零值,如果是,则按照[RFC7748]第6节的描述中止。如果实现者使用这些椭圆曲线的替代实现,他们应该 (SHOULD) 执行[RFC7748]第7节中指定的额外检查。
7.5. Exporters (导出器)
[RFC5705]根据TLS伪随机函数(PRF)为TLS定义密钥材料导出器。本文档将PRF替换为HKDF,因此需要新的构造。导出器接口保持不变。
导出器值的计算如下:
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。实现必须 (MUST) 使用exporter_master_secret,除非应用程序明确指定。early_exporter_master_secret定义用于需要0-RTT数据导出器的设置。建议 (RECOMMENDED) 为早期导出器提供单独的接口;这避免了导出器用户在需要常规导出器时意外使用早期导出器,反之亦然。
如果未提供上下文,则context_value为零长度。因此,不提供上下文与提供空上下文计算相同的值。这与TLS的先前版本不同,在先前版本中,空上下文产生的输出与缺少上下文不同。截至本文档发布,没有分配的导出器标签同时使用和不使用上下文。未来的规范不得 (MUST NOT) 定义允许使用相同标签的空上下文和无上下文的导出器使用。新的导出器使用应该 (SHOULD) 在所有导出器计算中提供上下文,尽管该值可能为空。
导出器标签格式的要求在[RFC5705]的第4节中定义。
🎯 第7章完成度: 100%
已完成:
- ✅ 7.1 Key Schedule
- ✅ 7.2 Updating Traffic Secrets
- ✅ 7.3 Traffic Key Calculation
- ✅ 7.4 (EC)DHE Shared Secret Calculation
- ✅ 7.5 Exporters