0-RTT

0-RTT: Zero round-trip time

这是 TLS1.3 新加的一个特性.

用途: 可以用于快速的发送一部分数据

快速” 的含义:通常的 TLS 连接, 只有 TLS 握手成功之后, 才能开始发送应用层数据。但是当使用 0-RTT 特性发送消息时, 不需要等待握手结束,便可以提交发送一小部分数据。 因为不需要等待 TLS 握手流程进行完毕,因此比较快。

代价是牺牲部分 TLS 的安全性。


如何工作的?


并不是任何情况下都能直接发送 0-RTT 数据包。 客户端需要提前有一个和服务器端共享的 PSK.

这个 PSK 的获取方式可以是: 从之前的链接上获取,或者通过其他外部方式。

这里的 PSK 主要用于 TLS 1.3 会话恢复(session resumption),

也就是意味着客户端和服务器已经有一组共享的密钥,可以用来生成加解密 0-RTT 数据的密钥(0-RTT traffic keys)

如下流程, 0-RTT 数据直接跟在客户端的 ClientHello 之后或者 Change Cipher Spec 之后.()

(Change Cipher Spec 在 TLS1.3 中不是必须的,但是为了兼容旧的版本,允许发送 Change Cipher Spec)

Client Server
ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*)(0-RTT Data) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
+ early_data*
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data]
Figure 4: Message Flow for a 0-RTT Handshake

代价


0-RTT 数据的安全性是明显弱于正常握手后发送的 TLS 数据的。

尤其是以下几点:

  1. 这些数据的不是前向保密的(forward secret)。 因为加密这些数据的密钥是从 PSK 直接生成的。
  2. 不能保证这些数据在不同链接上不会被重播(replay)。 1-RTT 数据的防止重播 的机制是依赖于 ServerHello.Random 的值,而 0-RTT数据的加密不依赖于这个值,因此没有这样的保护。

实例


image-20241114174708164

1-5 是前一个连接, 主要是为了获取 PSK, 这里的 PSK 由服务器端发送的 New Session Ticket 得到。

6-9 是当前连接(一条新的连接)。 我们注意到红框圈起来的部分便是 0-RTT 数据。 它是直接跟在 Change Cipher Spec 之后的, 也就是我们当前连接的 TCP 连接建立起来之后, 直接便发送 Client Hello, Change Cipher Spec0-RTT 数据, 而不需要等待 TLS 握手完成。

这里我们在第 6 步便把数据发送出去了, 第 8 步才是 TLS 握手成功的节点。

这里依然有一些细节需要注意,我们下面逐一解析:


[5] NewSessionTicket.early_data


Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: New Session Ticket
Opaque Type: Application Data (23)
Version: TLS 1.2 (0x0303)
Length: 82
Handshake Protocol: New Session Ticket
Handshake Type: New Session Ticket (4)
Length: 61
TLS Session Ticket
Session Ticket Lifetime Hint: 7200 seconds (2 hours)
Session Ticket Age Add: 953680565
Session Ticket Nonce Length: 8
Session Ticket Nonce: 0000000000000001
Session Ticket Length: 32
Session Ticket: ....af85e4152440
Extensions Length: 8
Extension: early_data (len=4)
Type: early_data (42)
Length: 4
Maximum Early Data Size: 16384

第 5 步中的 New Session Ticket 携带了一个扩展 early_data, 用于表明当前 Session ticket 可以用于发送 0-RTT 数据。

并且这个 early_data 中携带一个 Maximum Early Data Size 表明服务器端愿意接收 0-RTT 数据的最大值,超过这个值会被拒绝。 (这也就是我们开始说可以发送“一部分数据”的原因了, 不能像 1-RTT 那样发送任意数量的数据)


[6] ClientHello.early_data


Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: Client Hello
Content Type: Handshake (22)
Version: TLS 1.0 (0x0301)
Length: 309
Handshake Protocol: Client Hello
Handshake Type: Client Hello (1)
Length: 305
Version: TLS 1.2 (0x0303)
Random: ...1081762b162
Session ID Length: 32
Session ID: ,,,8218132ba7
Cipher Suites Length: 8
Cipher Suites (4 suites)
Compression Methods Length: 1
Compression Methods (1 method)
Extensions Length: 224
....
Extension: early_data (len=0)
Type: early_data (42)
Length: 0
Extension: pre_shared_key (len=91)
Type: pre_shared_key (41)
Length: 91
Pre-Shared Key extension
Identities Length: 38
PSK Identity (length: 32)
PSK Binders length: 49
PSK Binders

early_data 扩展:这个扩展没有实际数据,就是一个标识符,表明当前握手过程中,客户端会发送 0-RTT 数据。

这里同时还要携带 pre_shared_key(这是前提), 并且这是使用的 PSK 必须是从带有 early_data 扩展的 New Session Ticket 中计算得来的。


[7] EncryptedExtensions.early_data


Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: Encrypted Extensions
Opaque Type: Application Data (23)
Version: TLS 1.2 (0x0303)
Length: 27
[Content Type: Handshake (22)]
Handshake Protocol: Encrypted Extensions
Handshake Type: Encrypted Extensions (8)
Length: 6
Extensions Length: 4
Extension: early_data (len=0)
Type: early_data (42)
Length: 0

early_data 扩展: 用于向客户端表明服务器端接受了客户端的 0-RTT 数据。


[8] EndOfEarlyData


Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: End of Early Data
Opaque Type: Application Data (23)
Version: TLS 1.2 (0x0303)
Length: 21
[Content Type: Handshake (22)]
Handshake Protocol: End of Early Data
Handshake Type: End of Early Data (5)
Length: 0

EndOfEarlyData 是客户端发送给服务器的一个消息, 用于表明自己的 0-RTT 数据已经发送完毕,解析来的数据将是平常的 TLS 加密的数据了。

这个消息也是空的,只是用于表明自己 0-RTT发送完毕了。


实例: 0-RTT数据被拒绝


也存在一些场景,客户端发送的 0-RTT 数据不被服务器端接受.这里我们来看看响应的流程.

(这里我是用一个过期的 PSK 来模拟这种场景的)

image-20241115092807300

在步骤 1 中, 我们依然是发送 ClientHello 以及相应的 0-RTT 数据(Application Data 中)

ClientHello 中依然携带 pre_shared_keyearly_data 扩展. 但是这里的 pre_shared_key 是已经过期了. 因此,此次 TLS 会话恢复(Session resumption) 会失败,因此服务器端无法正常解密我们的 0-RTT 数据,因此拒绝接受它.

在步骤 2中, 服务器回复的 Encrypted Extensions 中便没有携带 early_data 扩展, 以表示不接受我们的 0-RTT 数据.

Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: Encrypted Extensions
Opaque Type: Application Data (23)
Version: TLS 1.2 (0x0303)
Length: 23
[Content Type: Handshake (22)]
Handshake Protocol: Encrypted Extensions
Handshake Type: Encrypted Extensions (8)
Length: 2
Extensions Length: 0

因为,客户端的 0-RTT 已经被明确的拒绝接受了,因此,客户端也不需要再发送 EndOfEarlyData 消息了.


Published at:
August 31, 2025
Keywords:
TLS
Session Ticket
0-RTT
Zero round-trip time
TLS13