TLS 1.3 引入了 0-RTT(Zero Round Trip Time,零往返时间)模式,旨在减少连接延迟,提升用户体验。本文深入解析 0-RTT 的工作原理、安全风险以及如何正确配置。

什么是 0-RTT?

0-RTT 是 TLS 1.3 引入的新特性,允许客户端在首次握手时就开始发送加密数据,无需等待服务器完成握手。相比传统的 1-RTT 或 2-RTT 模式,这可以显著减少建立连接所需的时间。

各版本 TLS 的往返次数

TLS 版本 模式 往返次数(RTT)
TLS 1.2 RSA 完整握手 2-RTT
TLS 1.2 ECDHE 完整握手 1-RTT
TLS 1.3 完整握手 1-RTT
TLS 1.3 0-RTT 0-RTT

工作原理

首次连接(建立会话)

1
2
3
4
客户端 ───── ClientHello ────> 服务器
客户端 <─── ServerHello ─────  服务器
客户端 <─── 加密完成 ─────────  服务器
            (+Session Ticket)
  1. 客户端发送 ClientHello,请求建立 TLS 1.3 连接
  2. 服务器响应 ServerHello,完成握手
  3. 服务器发送 New Session Ticket(会话票据),包含用于后续 0-RTT 连接的密钥材料

后续连接(使用 0-RTT)

1
2
3
4
5
客户端 ───── ClientHello ────> 服务器
            (+Encrypted      │
             Early Data)     │
客户端 <─── ServerHello ─────  服务器
客户端 <─── 加密完成 ─────────  服务器

客户端使用之前保存的会话票据,直接在 ClientHello 消息中附加加密的早期数据(Early Data)。服务器收到后立即解密并处理,无需等待完整的握手完成。

实际效果

根据 Google 的测试数据,0-RTT 可以将 TLS 握手时间从约 100ms 减少到 0ms,实际效果取决于网络延迟。

性能对比示例

以下命令演示如何使用 OpenSSL 查看服务器的 0-RTT 支持情况:

1
2
3
4
5
# 查看服务器 TLS 1.3 支持和密码套件
openssl s_client -connect cloudflare.com:443 -tls1_3 </dev/null 2>&1 | grep -E "Protocol|Cipher"

# 输出示例
# New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

安全风险

0-RTT 虽然性能优秀,但存在一个重要的安全弱点:重放攻击(Replay Attack)

重放攻击原理

由于 0-RTT 数据是在握手完成前发送的,服务器无法确定这些数据是否被恶意重放。攻击者可能:

  1. 窃听并记录某次正常的 0-RTT 连接
  2. 重放截获的加密早期数据
  3. 服务器可能会执行相同的操作(如重复下单、转账等)

攻击场景示例

假设一个 HTTPS 网站的 0-RTT 数据包含一个 HTTP POST 请求:

1
2
3
4
POST /api/order HTTP/1.1
Content-Type: application/json

{"item": "VIP会员", "price": 100}

攻击者截获这个加密请求后,可以无限次重放,导致用户被重复扣款。

缓解措施

1. 服务器端限制

  • 仅接受幂等请求:0-RTT 仅用于 GET 等幂等请求
  • 时间窗口限制:只接受短时间内的 0-RTT 请求(如 10 秒)
  • 单次使用票据:每个会话票据只能使用一次
  • 记录已使用票据:防止重放

2. 应用程序层面

  • 在应用程序中对 0-RTT 数据添加额外保护:
    • 使用唯一的请求 ID
    • 实现业务层面的防重放机制
    • 对关键操作要求完整握手

3. TLS 配置示例

Nginx 配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
    listen 443 ssl http2;
    ssl_protocols TLSv1.3;
    
    # 控制 0-RTT 模式
    # on: 启用但有限制
    # always: 始终启用(谨慎使用)
    # off: 禁用 0-RTT
    ssl_early_data on;
}

OpenSSL s_client 测试:

1
2
3
4
5
# 测试服务器是否支持 early_data
echo "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | \
    openssl s_client -connect example.com:443 -tls1_3 -early_data /dev/stdin

# 检查输出中的 "Early data was accepted" 或 "Early data was not sent"

使用建议

适合使用 0-RTT 的场景

  • 静态资源加载(如图片、CSS、JS)
  • 只读 API 调用
  • 用户交互较少的页面
  • 对延迟敏感且数据可重放的场景

不适合使用 0-RTT 的场景

  • 涉及金钱交易的请求
  • 敏感数据修改操作
  • 用户认证相关请求
  • 任何具有副作用的 POST/PUT 请求

最佳实践

  1. 默认启用,敏感操作禁用:对大多数请求启用 0-RTT,对关键 API 强制完整握手
  2. 监控重放攻击:日志记录 0-RTT 请求,及时发现异常
  3. 保持更新:使用最新版本的 TLS 库,获取最新安全修复

总结

TLS 1.3 的 0-RTT 特性是一个权衡取舍的设计:它以一定的安全风险为代价,换取更低的连接延迟。在实际部署时,应该:

  • 了解 0-RTT 的安全风险
  • 根据业务场景决定是否启用
  • 对敏感操作实施额外保护
  • 持续监控异常情况

对于大多数 Web 应用,启用 0-RTT 是安全的,因为它主要用于静态资源和幂等请求。但对于金融、医疗等敏感应用,建议仔细评估后再决定是否启用。


参考来源: