Skip to main content

6. 密钥更新 (Key Update)

一旦握手被确认(参见第 4.1.2 节),端点可以 (MAY) 发起密钥更新.

密钥阶段 (Key Phase) 位指示使用哪些数据包保护密钥来保护数据包. 密钥阶段位最初为第一组 1-RTT 数据包设置为 0,并切换以表示每次后续的密钥更新.

密钥阶段位允许接收方检测密钥材料的变化,而无需接收触发变化的第一个数据包. 注意到密钥阶段位改变的端点更新密钥并解密包含更改值的数据包.

发起密钥更新会导致两个端点都更新密钥. 这与 TLS 不同,TLS 中端点可以独立更新密钥.

此机制取代了 TLS 的密钥更新机制,TLS 依赖于使用 1-RTT 加密密钥发送的 KeyUpdate 消息. 端点禁止 (MUST NOT) 发送 TLS KeyUpdate 消息. 端点必须 (MUST) 将收到 TLS KeyUpdate 消息视为类型为 0x010a 的连接错误,等同于致命 TLS 警报 unexpected_message; 参见第 4.8 节.

图 9 显示了密钥更新过程,其中使用的初始密钥集(用 @M 标识)被更新的密钥(用 @N 标识)替换. 密钥阶段位的值在方括号 [] 中指示.

   发起对等方                    响应对等方

@M [0] QUIC Packets

... Update to @N
@N [1] QUIC Packets
-------->
Update to @N ...
QUIC Packets [1] @N
<--------
QUIC Packets [1] @N
containing ACK
<--------
... Key Update Permitted

@N [1] QUIC Packets
containing ACK for @N packets
-------->
Key Update Permitted ...

图 9: 密钥更新

6.1. 发起密钥更新 (Initiating a Key Update)

端点为数据包保护维护单独的读取和写入密钥. 端点通过更新其数据包保护写入密钥并使用该密钥保护新数据包来发起密钥更新. 端点按照 [TLS13] 第 7.2 节的执行方式从现有写入密钥创建新的写入密钥. 这使用 TLS 提供的 KDF 函数,标签为 "quic ku". 相应的密钥和 IV 如第 5.1 节中定义的从该密钥创建. 包头保护密钥不更新.

例如,要使用 TLS 1.3 更新写入密钥,使用 HKDF-Expand-Label 如下:

secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku",
"", Hash.length)

端点切换密钥阶段位的值,并使用更新的密钥和 IV 保护所有后续数据包.

端点在确认握手之前禁止 (MUST NOT) 发起密钥更新(第 4.1.2 节). 除非端点已收到对使用当前密钥阶段的密钥保护发送的数据包的确认,否则禁止 (MUST NOT) 发起后续密钥更新. 这确保在可以发起另一个密钥更新之前,两个对等方都有密钥可用. 这可以通过跟踪使用每个密钥阶段发送的最低包编号和 1-RTT 空间中的最高已确认包编号来实现: 一旦后者大于或等于前者,就可以发起另一个密钥更新.

注意: 除 1-RTT 数据包之外的数据包的密钥永远不会更新; 它们的密钥仅从 TLS 握手状态派生.

发起密钥更新的端点还会更新用于接收数据包的密钥. 在对等方更新后发送数据包时将需要这些密钥.

端点必须 (MUST) 保留旧密钥,直到它成功解除使用新密钥发送的数据包的保护. 在解除使用新密钥发送的数据包的保护后,端点应该 (SHOULD) 将旧密钥保留一段时间. 过早丢弃旧密钥可能导致延迟的数据包被丢弃. 丢弃数据包将被对等方解释为包丢失,可能会对性能产生不利影响.

6.2. 响应密钥更新 (Responding to a Key Update)

对等方在收到当前密钥阶段中数据包的确认后被允许发起密钥更新. 当处理密钥阶段与用于保护其发送的最后一个数据包的值不同的数据包时,端点检测到密钥更新. 要处理此数据包,端点使用下一个数据包保护密钥和 IV. 有关生成这些密钥的考虑,请参见第 6.3 节.

如果使用下一个密钥和 IV 成功处理了数据包,则对等方已发起密钥更新. 端点必须 (MUST) 作为响应将其发送密钥更新到相应的密钥阶段,如第 6.1 节所述. 必须 (MUST) 在发送对使用更新密钥接收的数据包的确认之前更新发送密钥. 通过在使用更新密钥保护的数据包中确认触发密钥更新的数据包,端点表示密钥更新已完成.

端点可以根据其正常的数据包发送行为推迟发送数据包或确认; 不必立即生成数据包以响应密钥更新. 端点发送的下一个数据包将使用更新的密钥. 包含确认的下一个数据包将导致密钥更新完成. 如果端点在发送包含对发起密钥更新的数据包的确认的更新密钥的任何数据包之前检测到第二次更新,则表明其对等方在等待确认的情况下两次更新了密钥. 端点可以 (MAY) 将此类连续密钥更新视为类型为 KEY_UPDATE_ERROR 的连接错误.

如果端点收到的确认承载在使用旧密钥保护的数据包中,其中任何已确认的数据包使用较新的密钥保护,则可以 (MAY) 将其视为类型为 KEY_UPDATE_ERROR 的连接错误. 这表明对等方已接收并确认了发起密钥更新的数据包,但未响应更新密钥.

6.3. 接收密钥生成时机 (Timing of Receive Key Generation)

响应明显密钥更新的端点禁止 (MUST NOT) 生成可能指示密钥阶段位无效的时序侧信道信号(参见第 9.5 节). 当尚不允许密钥更新时,端点可以使用随机数据包保护密钥代替丢弃的密钥. 使用随机密钥确保尝试移除数据包保护不会导致时序变化,并导致具有无效密钥阶段位的数据包被拒绝.

为接收数据包创建新数据包保护密钥的过程可能会揭示已发生密钥更新. 端点可以 (MAY) 在数据包处理过程中生成新密钥,但这会创建一个时序信号,攻击者可以使用该信号来了解何时发生密钥更新,从而泄漏密钥阶段位的值.

端点通常应该具有当前和下一个接收数据包保护密钥可用. 在密钥更新完成后的短时间内,最多到 PTO,端点可以 (MAY) 推迟生成下一组接收数据包保护密钥. 这允许端点仅保留两组接收密钥; 参见第 6.5 节.

一旦生成,即使随后丢弃了接收到的数据包,也应该 (SHOULD) 保留下一组数据包保护密钥. 包含明显密钥更新的数据包很容易伪造,虽然密钥更新过程不需要大量工作,但触发此过程可能被攻击者用于 DoS.

因此,端点必须 (MUST) 能够保留两组用于接收数据包的数据包保护密钥: 当前的和下一个的. 除这些之外保留先前的密钥可能会提高性能,但这不是必需的.

6.4. 使用更新密钥发送 (Sending with Updated Keys)

端点永远不会发送使用旧密钥保护的数据包. 只使用当前密钥. 用于保护数据包的密钥可以在切换到较新密钥后立即丢弃.

具有更高包编号的数据包必须 (MUST) 使用与具有较低包编号的数据包相同或更新的数据包保护密钥进行保护. 当使用较新密钥的数据包具有较低包编号时,如果端点使用旧密钥成功移除保护,则必须 (MUST) 将其视为类型为 KEY_UPDATE_ERROR 的连接错误.

6.5. 使用不同密钥接收 (Receiving with Different Keys)

对于在密钥更新期间接收数据包,如果使用较旧密钥保护的数据包被网络延迟,它们可能会到达. 保留旧数据包保护密钥允许成功处理这些数据包.

由于使用下一个密钥阶段的密钥保护的数据包使用与使用先前密钥阶段的密钥保护的数据包相同的密钥阶段值,因此如果要处理使用旧密钥保护的数据包,则有必要区分两者. 这可以使用包编号来完成. 低于当前密钥阶段的任何包编号的恢复包编号使用先前的数据包保护密钥; 高于当前密钥阶段的任何包编号的恢复包编号需要使用下一个数据包保护密钥.

需要注意确保在先前、当前和下一个数据包保护密钥之间进行选择的任何过程不会暴露可能揭示使用哪些密钥移除数据包保护的时序侧信道. 有关更多信息,请参见第 9.5 节.

或者,端点可以仅保留两组数据包保护密钥,在经过足够的时间以允许网络中的重新排序后,将先前的密钥交换为下一个密钥. 在这种情况下,仅密钥阶段位就可以用于选择密钥.

端点可以 (MAY) 允许在将下一组接收密钥提升为当前密钥后大约探测超时 (Probe Timeout, PTO; 参见 [QUIC-RECOVERY]) 的时间段,然后再创建后续的数据包保护密钥集. 这些更新的密钥可以 (MAY) 在那时替换先前的密钥. 需要注意的是,PTO 是一个主观度量 -- 也就是说,对等方可能对 RTT 有不同的看法 -- 预计此时间足够长,即使被确认,任何重新排序的数据包也会被对等方声明为丢失,并且足够短以允许对等方发起进一步的密钥更新.

端点需要考虑对等方在保留旧密钥期间可能无法解密发起密钥更新的数据包的可能性. 端点应该 (SHOULD) 在收到确认先前密钥更新已收到的确认后,在发起密钥更新之前等待三倍 PTO. 未能留出足够的时间可能导致数据包被丢弃.

端点应该 (SHOULD) 在收到使用新密钥保护的数据包后,将旧读取密钥保留不超过三倍 PTO. 在此期间之后,应该 (SHOULD) 丢弃旧读取密钥及其相应的密钥.

6.6. AEAD 使用限制 (Limits on AEAD Usage)

本文档为 AEAD 算法设置使用限制,以确保过度使用不会给攻击者在使用 QUIC 时攻击通信的机密性和完整性提供不成比例的优势.

TLS 1.3 中定义的使用限制用于防止对机密性的攻击,并适用于 AEAD 保护的成功应用. 认证加密中的完整性保护还取决于限制伪造数据包的尝试次数. TLS 通过在任何记录未能通过身份验证检查后关闭连接来实现这一点. 相比之下,QUIC 忽略任何无法验证的数据包,允许多次伪造尝试.

QUIC 分别考虑 AEAD 机密性和完整性限制. 机密性限制适用于使用给定密钥加密的数据包数量. 完整性限制适用于给定连接内解密的数据包数量. 下面是针对每个 AEAD 算法执行这些限制的详细信息.

端点必须 (MUST) 计算每组密钥的加密数据包数量. 如果使用相同密钥加密的数据包总数超过所选 AEAD 的机密性限制,则端点必须 (MUST) 停止使用这些密钥. 端点必须 (MUST) 在发送超过所选 AEAD 允许的机密性限制的受保护数据包之前发起密钥更新. 如果无法进行密钥更新或达到完整性限制,则端点必须 (MUST) 停止使用连接,并且仅发送无状态重置 (Stateless Resets) 以响应接收数据包. 建议 (RECOMMENDED) 端点在达到无法进行密钥更新的状态之前,立即以类型为 AEAD_LIMIT_REACHED 的连接错误关闭连接.

对于 AEAD_AES_128_GCM 和 AEAD_AES_256_GCM,机密性限制为 2^23 个加密数据包; 参见附录 B.1. 对于 AEAD_CHACHA20_POLY1305,机密性限制大于可能的数据包数量 (2^62),因此可以忽略. 对于 AEAD_AES_128_CCM,机密性限制为 2^21.5 个加密数据包; 参见附录 B.2. 应用限制可以降低攻击者可以将正在使用的 AEAD 与随机排列区分开来的概率; 参见 [AEBounds]、[ROBUST] 和 [GCM-MU].

除了计数发送的数据包外,端点必须 (MUST) 计算在连接生命周期内身份验证失败的接收数据包数量. 如果在连接内、跨所有密钥、身份验证失败的接收数据包总数超过所选 AEAD 的完整性限制,则端点必须 (MUST) 立即以类型为 AEAD_LIMIT_REACHED 的连接错误关闭连接,并且不再处理任何数据包.

对于 AEAD_AES_128_GCM 和 AEAD_AES_256_GCM,完整性限制为 2^52 个无效数据包; 参见附录 B.1. 对于 AEAD_CHACHA20_POLY1305,完整性限制为 2^36 个无效数据包; 参见 [AEBounds]. 对于 AEAD_AES_128_CCM,完整性限制为 2^21.5 个无效数据包; 参见附录 B.2. 应用此限制可以降低攻击者成功伪造数据包的概率; 参见 [AEBounds]、[ROBUST] 和 [GCM-MU].

限制数据包大小的端点可以 (MAY) 使用更高的机密性和完整性限制; 有关详细信息,请参见附录 B.

未来的分析和规范可以 (MAY) 放宽 AEAD 的机密性或完整性限制.

为与 QUIC 一起使用而指定的任何 TLS 密码套件必须 (MUST) 定义对相关 AEAD 函数使用的限制,以保持机密性和完整性的余地. 也就是说,必须 (MUST) 指定可以验证的数据包数量和可以身份验证失败的数据包数量的限制. 提供对任何基于值的分析的参考 -- 以及该分析中使用的任何假设 -- 允许将限制调整为不同的使用条件.

6.7. 密钥更新错误代码 (Key Update Error Code)

KEY_UPDATE_ERROR 错误代码 (0x0e) 用于表示与密钥更新相关的错误.