2018 年 8 月,RFC 8446 正式发布了 TLS 1.3。这不是一次小修小补——它重新设计了握手流程,移除了大量历史包袱,将完整握手从 2 RTT 压缩到 1 RTT,并引入了 0-RTT 恢复模式。Cloudflare 在部署后报告,TLS 1.3 使 HTTPS 连接建立时间减少了约 30%。
但 TLS 1.3 不是”更快的 TLS 1.2”。它的设计哲学发生了根本变化——不再提供”选择不安全配置的自由”,而是强制安全。理解这些变化,对于工程师部署和升级 TLS 至关重要。
一、TLS 1.3 移除了什么
TLS 1.3 做的第一件事不是添加新功能,而是删除旧功能。RFC 8446 的作者 Eric Rescorla 明确表示:“TLS 1.3 的设计原则之一是减少攻击面。”
1.1 被移除的特性清单
| 被移除的特性 | 移除原因 | 对应的已知攻击 |
|---|---|---|
| RSA 密钥交换 | 无前向保密 | 被动解密历史流量 |
| CBC 模式加密 | Padding Oracle | BEAST、Lucky 13、POODLE |
| RC4 流加密 | 已被攻破 | RC4 偏差攻击 |
| 3DES | 太慢 + Sweet32 | Sweet32 生日攻击 |
| 压缩 | 侧信道泄露 | CRIME、BREACH |
| 重新协商(Renegotiation) | 协议复杂性 | 三重握手攻击 |
| 静态 RSA/DH | 无前向保密 | — |
| MD5/SHA-1 签名 | 哈希碰撞 | SHAttered |
| DSA 签名 | 实现质量差 | — |
| 自定义 DHE 参数 | 弱参数风险 | Logjam |
1.2 仅保留的密码套件
TLS 1.3 只保留了 5 个密码套件,全部使用 AEAD(Authenticated Encryption with Associated Data)模式:
TLS 1.3 密码套件:
TLS_AES_128_GCM_SHA256 (0x1301) ← 推荐默认
TLS_AES_256_GCM_SHA384 (0x1302) ← 高安全场景
TLS_CHACHA20_POLY1305_SHA256 (0x1303) ← 移动设备友好
TLS_AES_128_CCM_SHA256 (0x1304) ← IoT 场景
TLS_AES_128_CCM_8_SHA256 (0x1305) ← IoT 极低开销
注意命名变化:
TLS 1.2: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS 1.3: TLS_AES_128_GCM_SHA256
密钥交换和签名算法不再出现在密码套件名称中
→ 它们通过 supported_groups 和 signature_algorithms 扩展独立协商
# 查看 OpenSSL 支持的 TLS 1.3 密码套件
openssl ciphers -v -s -tls1_3
# TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
# TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
# TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
# 注意 Kx=any Au=any
# → 密钥交换和认证独立于密码套件为什么 ChaCha20-Poly1305 对移动设备更友好?因为它不依赖 AES-NI 硬件指令。在没有 AES 硬件加速的 ARM 处理器上,ChaCha20 比 AES-GCM 快 3 倍以上。Google 在 Android Chrome 中优先选择 ChaCha20-Poly1305 就是这个原因。
1.3 密码套件性能对比
# 在不同硬件上测试加密算法性能
openssl speed -evp aes-128-gcm
openssl speed -evp aes-256-gcm
openssl speed -evp chacha20-poly1305
# 典型结果 (有 AES-NI 的 x86_64 服务器):
# 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
# aes-128-gcm 5.8 GB/s 7.2 GB/s 7.8 GB/s 8.0 GB/s 8.1 GB/s
# aes-256-gcm 4.9 GB/s 6.1 GB/s 6.6 GB/s 6.8 GB/s 6.9 GB/s
# chacha20-poly1305 1.8 GB/s 2.3 GB/s 2.6 GB/s 2.7 GB/s 2.8 GB/s
# → 有 AES-NI 时,AES-GCM 远快于 ChaCha20
# 典型结果 (无 AES-NI 的 ARM Cortex-A53):
# 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
# aes-128-gcm 85 MB/s 110 MB/s 130 MB/s 140 MB/s 145 MB/s
# chacha20-poly1305 280 MB/s 350 MB/s 390 MB/s 410 MB/s 420 MB/s
# → 无 AES-NI 时,ChaCha20 约 3 倍快于 AES-GCM
# 检查当前 CPU 是否支持 AES-NI
grep -o aes /proc/cpuinfo | head -1
# 有输出 → 支持 AES-NI → 优先 AES-GCM
# 无输出 → 不支持 → 优先 ChaCha20-Poly1305选择建议:服务器端有 AES-NI 时,优先
AES-128-GCM(性能最优);客户端是移动设备或嵌入式设备时,服务器应允许客户端优先选择
ChaCha20-Poly1305(设置
ssl_prefer_server_ciphers off)。
二、TLS 1.3 完整握手:1-RTT
2.1 握手消息序列
客户端 服务器
│ │
│ ClientHello │
│ + key_share (客户端 ECDHE 公钥) │
│ + supported_versions (0x0304) │
│ + signature_algorithms │
│ + psk_key_exchange_modes (可选) │
├────────────────────────────────────────────→│
│ │
│ ServerHello │
│ + key_share (服务器 ECDHE 公钥) │
│ + supported_versions (0x0304) │
│←────────────────────────────────────────────┤
│ {EncryptedExtensions} │
│ {CertificateRequest} (可选, mTLS 时) │
│ {Certificate} │
│ {CertificateVerify} │
│ {Finished} │
│←════════════════════════════════════════════┤
│ │
│ {Certificate} (可选, mTLS 时) │
│ {CertificateVerify} (可选, mTLS 时) │
│ {Finished} │
├════════════════════════════════════════════→│
│ │
│ [Application Data] │
╞════════════════════════════════════════════╡
│ │
{} = 加密的握手消息
[] = 加密的应用数据
总计: 1 RTT 即可开始发送应用数据
2.2 关键变化分析
密钥交换前移:在 TLS 1.2 中,客户端先发
ClientHello,等收到 ServerHello
后才知道服务器选择了哪条曲线,然后才能发送自己的公钥。TLS
1.3 的做法是客户端在 ClientHello
中就”猜测”服务器会选择的曲线(通过 key_share
扩展直接发送公钥)。
TLS 1.2 的问题:
ClientHello → 等待 → ServerHello → 等待 → ClientKeyExchange
(客户端不知道服务器选什么曲线,无法提前计算)
TLS 1.3 的解法:
ClientHello + key_share(X25519) → ServerHello + key_share(X25519)
(客户端猜 X25519,猜对了 → 1 RTT)
(客户端猜错了 → HelloRetryRequest → 2 RTT)
证书加密:TLS 1.2 中服务器的 Certificate 消息是明文传输的——任何网络观察者都能看到服务器发送了什么证书。TLS 1.3 在 ServerHello 之后就开始加密,Certificate 消息被加密传输。
CertificateVerify 消息:这是 TLS 1.3 新增的消息。服务器用自己的私钥对之前所有握手消息的哈希进行签名,证明它确实持有证书对应的私钥。TLS 1.2 中 RSA 密钥交换模式下没有这一步——服务器通过解密 Pre-Master Secret 隐式证明身份。
2.3 HelloRetryRequest
如果客户端的 key_share
中没有服务器支持的曲线,服务器会发送
HelloRetryRequest(HRR),要求客户端重新发送
ClientHello:
客户端 服务器
│ │
│ ClientHello │
│ + key_share (P-384) │
├────────────────────────────────────────────→│
│ │
│ 服务器只支持 X25519 │
│ │
│ HelloRetryRequest │
│ + key_share (X25519) │
│←────────────────────────────────────────────┤
│ │
│ ClientHello (重试) │
│ + key_share (X25519) │
├────────────────────────────────────────────→│
│ │
│ 正常 ServerHello + 后续消息 │
│←════════════════════════════════════════════┤
此时退化为 2 RTT
实际上 HRR 很少发生,因为客户端通常会在 key_share 中同时提供 X25519 和 P-256 的公钥。
# 用 tshark 观察 TLS 1.3 握手
tshark -i eth0 -f 'tcp port 443' \
-Y 'tls.handshake.type' \
-T fields \
-e frame.time_relative \
-e ip.src \
-e tls.handshake.type \
-e tls.handshake.extensions.supported_version
# 同时发起 TLS 1.3 连接
curl --tlsv1.3 -v https://example.com 2>/dev/null
# 输出示例 (type 字段):
# 0.000 192.168.1.100 1 0x0304 (ClientHello, TLS 1.3)
# 0.031 93.184.216.34 2 0x0304 (ServerHello, TLS 1.3)
# 0.031 93.184.216.34 8,11,15,20 (加密的 Extensions+Cert+CertVerify+Finished)
# 0.033 192.168.1.100 20 (客户端 Finished)三、TLS 1.3 密钥推导:HKDF
TLS 1.2 使用自定义的 PRF(伪随机函数)推导密钥。TLS 1.3 替换为标准化的 HKDF(HMAC-based Key Derivation Function,RFC 5869),分为 Extract 和 Expand 两步。
3.1 密钥推导调度
0
│
v
PSK ──→ HKDF-Extract = Early Secret
│
v
Derive-Secret(., "derived", "")
│
v
(EC)DHE ──→ HKDF-Extract = Handshake Secret ──→ 握手流量密钥
│ (加密 Certificate/Finished)
v
Derive-Secret(., "derived", "")
│
v
0 ───→ HKDF-Extract = Master Secret ──→ 应用流量密钥
(加密 Application Data)
每一级 Secret 通过 Derive-Secret 派生出:
- client_handshake_traffic_secret
- server_handshake_traffic_secret
- client_application_traffic_secret
- server_application_traffic_secret
3.2 与 TLS 1.2 密钥推导的对比
| 特性 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 推导函数 | 自定义 PRF | HKDF(标准化) |
| 输入材料 | Pre-Master Secret + Randoms | PSK + (EC)DHE |
| 密钥层级 | 单层(Master Secret → Key Block) | 三层(Early/Handshake/Master) |
| 握手加密 | 无(握手消息明文) | 有(ServerHello 后加密) |
| 密钥更新 | 需要重新协商 | KeyUpdate 消息 |
# 使用 OpenSSL 导出 TLS 1.3 的密钥日志
# 设置 SSLKEYLOGFILE 环境变量
export SSLKEYLOGFILE=/tmp/tls13-keys.log
curl https://example.com >/dev/null 2>&1
# 查看密钥日志
cat /tmp/tls13-keys.log
# CLIENT_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret>
# SERVER_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret>
# CLIENT_TRAFFIC_SECRET_0 <client_random> <secret>
# SERVER_TRAFFIC_SECRET_0 <client_random> <secret>
# EXPORTER_SECRET <client_random> <secret>
# 在 Wireshark 中使用密钥日志解密 TLS 1.3 流量:
# Edit → Preferences → Protocols → TLS
# → (Pre)-Master-Secret log filename: /tmp/tls13-keys.log3.3 KeyUpdate:无中断密钥轮换
TLS 1.3 引入了 KeyUpdate
消息,允许在不重新握手的情况下更新应用数据的加密密钥。这在长连接场景中特别有用——例如
HTTP/2 的长时间复用连接。
KeyUpdate 流程:
发送方 接收方
│ │
├── KeyUpdate(update_requested) ─→│
│ (更新自己的发送密钥) │ (更新对方的接收密钥)
│ │
│ 如果 update_requested: │
│←── KeyUpdate(update_not_requested) ──┤
│ (更新对方的接收密钥) │ (更新自己的发送密钥)
│ │
新密钥 = HKDF-Expand-Label(旧密钥, "traffic upd", "", Hash.length)
四、0-RTT 恢复模式
4.1 0-RTT 的工作原理
0-RTT(Zero Round Trip Time)允许客户端在发送 ClientHello 的同时就附带加密的应用数据,不需要等待服务器响应。这是 TLS 1.3 最引人注目的性能优化。
客户端 服务器
│ │
│ ClientHello │
│ + early_data │
│ + key_share │
│ + psk_identity │
│ (Application Data) ← 0-RTT 数据,加密 │
├════════════════════════════════════════════→│
│ │
│ ServerHello │
│ + pre_shared_key │
│ {EncryptedExtensions} │
│ + early_data (接受 0-RTT) │
│ {Finished} │
│←════════════════════════════════════════════┤
│ │
│ {Finished} │
│ [Application Data] ← 1-RTT 数据 │
├════════════════════════════════════════════→│
0-RTT 数据在第一个网络包就发出去了
→ 对于 HTTPS GET 请求,响应可以更快开始
4.2 0-RTT 的前提条件
0-RTT 不是免费的——它需要满足严格的前提条件:
前提条件:
1. 客户端和服务器之前有过一次完整握手
→ 客户端持有有效的 PSK(Pre-Shared Key)
→ PSK 来自上次握手后服务器发送的 NewSessionTicket
2. PSK 未过期
→ 服务器在 NewSessionTicket 中设定了 ticket_lifetime
→ RFC 8446 限制最大为 604800 秒(7 天)
3. 使用相同的密码套件
→ 0-RTT 数据使用 Early Secret 加密
→ Early Secret 来自 PSK + 上次握手的参数
4. 服务器选择接受 0-RTT 数据
→ 服务器可以拒绝 0-RTT(返回不含 early_data 扩展)
→ 客户端必须准备好重新发送被拒绝的 0-RTT 数据
5. 应用层协议支持
→ HTTP/2 和 HTTP/3 支持 0-RTT
→ 应用必须能处理 Early-Data 头标记
4.3 0-RTT 的重放攻击风险
0-RTT 模式有一个根本性的安全问题:没有前向保密,且无法防止重放攻击。
重放攻击场景:
1. 客户端发送 0-RTT 请求: POST /transfer?amount=1000
2. 攻击者捕获了这个网络包
3. 攻击者重放这个包 → 服务器再次处理转账
为什么 0-RTT 无法防重放:
- 在 1-RTT 握手中,服务器的 ServerHello.random 确保每次握手唯一
- 在 0-RTT 中,客户端在收到服务器响应前就发送了数据
- 服务器无法提供"一次性"的随机数来防止重放
对比:
1-RTT: ClientHello → ServerHello(fresh random) → 数据
每次 ServerHello.random 不同 → 自然防重放
0-RTT: ClientHello + 数据 →
没有 ServerHello → 没有 fresh random → 可重放
4.4 0-RTT 的安全使用指南
安全原则: 只对幂等(Idempotent)操作使用 0-RTT
✓ 安全(幂等操作):
GET /index.html ← 读取操作
GET /api/user/profile ← 查询操作
HEAD /health ← 健康检查
✗ 不安全(非幂等操作):
POST /api/transfer ← 转账
POST /api/order ← 下单
PUT /api/counter/incr ← 计数器递增
DELETE /api/resource/123 ← 删除操作
服务器端的防重放策略:
策略一: 时间窗口限制
- 服务器只接受最近 N 秒内的 0-RTT 数据
- 超过窗口的 0-RTT 数据被拒绝
- 但窗口内仍可能被重放
策略二: Strike Register(一次性令牌)
- 服务器记录已处理的 0-RTT ClientHello 的标识
- 如果相同的标识再次出现 → 拒绝
- 代价: 需要跨服务器共享状态
策略三: 单次使用 Session Ticket
- 每个 NewSessionTicket 只允许用于一次 0-RTT
- 使用后立即作废
- 代价: 无法跨多台服务器去重
最佳实践:
- 应用层做幂等性保护(最可靠)
- 服务器配合时间窗口限制
- 关键业务接口禁用 0-RTT
# Nginx 配置 0-RTT
# nginx.conf
# ssl_early_data on;
# proxy_set_header Early-Data $ssl_early_data;
#
# 后端服务根据 Early-Data 头判断:
# Early-Data: 1 → 这是 0-RTT 数据,需要额外的幂等性检查
# 测试 0-RTT 是否生效
# 需要两次连接: 第一次建立 PSK,第二次使用 0-RTT
openssl s_client -connect example.com:443 \
-tls1_3 -sess_out /tmp/tls13-sess.pem \
</dev/null 2>/dev/null
openssl s_client -connect example.com:443 \
-tls1_3 -sess_in /tmp/tls13-sess.pem \
-early_data /tmp/early-data.txt 2>&1 | \
grep -E "Early data|Reused"
# Early data was accepted
# Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384五、PSK 模式
5.1 基于 Resumption 的 PSK
TLS 1.3 用 PSK(Pre-Shared Key)统一了 TLS 1.2 中的 Session ID 和 Session Ticket 两种恢复机制。
NewSessionTicket 流程:
握手完成后 (任意时刻):
服务器 → 客户端: NewSessionTicket
{
ticket_lifetime: 3600, // 秒
ticket_age_add: 0x12345678, // 混淆客户端的 ticket_age
ticket_nonce: 0x01, // 用于派生 PSK
ticket: <加密的 ticket 数据>,
extensions: {
max_early_data_size: 16384 // 0-RTT 最大数据量
}
}
客户端存储:
PSK = HKDF-Expand-Label(resumption_master_secret,
"resumption",
ticket_nonce,
Hash.length)
+ ticket_lifetime
+ ticket_age_add
+ ticket 数据本身
5.2 外部 PSK
除了基于恢复的 PSK,TLS 1.3 还支持外部预共享密钥(External PSK),即双方通过带外方式预先配置的密钥。
外部 PSK 的场景:
- IoT 设备: 出厂预配置密钥
- 企业内网: 通过安全渠道分发密钥
- 特殊安全要求: 不信任 PKI 体系
外部 PSK 的模式:
psk_ke: 仅 PSK(无前向保密)
psk_dhe_ke: PSK + ECDHE(有前向保密) ← 推荐
配置示例 (OpenSSL 3.x):
openssl s_server -psk 0123456789abcdef \
-psk_identity client1 -tls1_3 -nocert
openssl s_client -psk 0123456789abcdef \
-psk_identity client1 -tls1_3
5.3 PSK 与密钥交换模式
| 模式 | 前向保密 | 0-RTT | 适用场景 |
|---|---|---|---|
| 仅 ECDHE | ✓ | ✗ | 首次连接 |
| PSK + ECDHE | ✓ | ✓ | 恢复连接(推荐) |
| 仅 PSK | ✗ | ✓ | IoT 资源受限(不推荐) |
需要注意的是,即使使用 PSK + ECDHE 模式(推荐),0-RTT 数据仍然只用 PSK 派生的 Early Secret 加密,没有 ECDHE 的前向保密保护。ECDHE 的前向保密只在 1-RTT 及后续的数据传输中生效。这也是 0-RTT 安全性低于 1-RTT 的另一个原因。
六、版本协商与向下兼容
6.1 supported_versions 扩展
TLS 1.3 改变了版本协商的方式。在 TLS 1.2 及之前,版本在
ClientHello 和 ServerHello 的 ProtocolVersion
字段中协商。TLS 1.3 引入了 supported_versions
扩展。
版本协商的变化:
TLS 1.2:
ClientHello.version = 0x0303 (TLS 1.2)
ServerHello.version = 0x0303 (TLS 1.2)
TLS 1.3:
ClientHello.version = 0x0303 (伪装成 TLS 1.2!)
+ supported_versions 扩展: [0x0304, 0x0303]
ServerHello.version = 0x0303 (伪装成 TLS 1.2!)
+ supported_versions 扩展: 0x0304
为什么伪装?
大量中间设备(防火墙、IDS、代理)会检查 TLS 版本字段
如果看到不认识的版本号(如 0x0304)就会丢包
TLS 1.3 通过保持 0x0303 骗过这些中间设备
真正的版本在扩展中协商
这个问题被称为 "中间设备僵化"(Middlebox Ossification)
是 TLS 1.3 部署中最大的工程挑战之一
6.2 中间设备兼容性
TLS 1.3 为了兼容中间设备做的妥协:
1. 版本号伪装(如上所述)
2. ChangeCipherSpec 兼容
TLS 1.3 协议本身不使用 ChangeCipherSpec
但在 "compatibility mode" 下仍然发送一个假的 CCS
以避免中间设备因"缺少 CCS"而断开连接
3. ServerHello.random 的特殊值
如果服务器选择降级到 TLS 1.2,ServerHello.random 的最后 8 字节
必须设为 0x44 0x4F 0x57 0x4E 0x47 0x52 0x44 0x01
("DOWNGRD" + 0x01)
客户端检测到这个值 → 知道发生了降级,可以中止握手
# 检查服务器是否支持 TLS 1.3
openssl s_client -connect example.com:443 \
-tls1_3 2>&1 | grep -E "Protocol|Cipher"
# Protocol : TLSv1.3
# Cipher : TLS_AES_256_GCM_SHA384
# 如果不支持 TLS 1.3:
# Protocol : TLSv1.2
# → 自动降级到 TLS 1.2
# 查看 supported_versions 扩展
tshark -i eth0 -f 'tcp port 443' \
-Y 'tls.handshake.type == 1' \
-T fields \
-e tls.handshake.extensions.supported_version
# 输出: 0x0304,0x0303
# → 客户端支持 TLS 1.3 和 TLS 1.2七、从 TLS 1.2 升级到 TLS 1.3 的工程路径
7.1 升级前的检查清单
#!/bin/bash
# tls13_readiness.sh — TLS 1.3 升级就绪检查
HOST="${1:?Usage: $0 hostname}"
PORT="${2:-443}"
echo "=== TLS 1.3 升级就绪检查: $HOST:$PORT ==="
# 1. 检查 OpenSSL 版本
echo ""
echo "--- OpenSSL 版本 ---"
openssl version
# 需要 OpenSSL 1.1.1+ 才支持 TLS 1.3
# 2. 检查当前 TLS 配置
echo ""
echo "--- 当前 TLS 版本 ---"
echo | openssl s_client -connect "$HOST:$PORT" \
-servername "$HOST" 2>/dev/null | \
grep "Protocol"
# 3. 测试 TLS 1.3 连接
echo ""
echo "--- TLS 1.3 支持 ---"
result=$(echo | openssl s_client -connect "$HOST:$PORT" \
-servername "$HOST" -tls1_3 2>&1)
if echo "$result" | grep -q "Protocol.*TLSv1.3"; then
echo " TLS 1.3: ✓ 已支持"
echo " 密码套件: $(echo "$result" | grep "Cipher" | head -1)"
else
echo " TLS 1.3: ✗ 不支持"
fi
# 4. 检查密码套件兼容性
echo ""
echo "--- 密码套件检查 ---"
echo | openssl s_client -connect "$HOST:$PORT" \
-servername "$HOST" 2>/dev/null | \
grep "Cipher"
# 确保不依赖 TLS 1.3 中已移除的套件
# 5. 检查证书签名算法
echo ""
echo "--- 证书签名算法 ---"
echo | openssl s_client -connect "$HOST:$PORT" \
-servername "$HOST" 2>/dev/null | \
openssl x509 -noout -text 2>/dev/null | \
grep "Signature Algorithm" | head -1
# SHA-1 签名的证书在 TLS 1.3 中不被接受7.2 Nginx 升级配置
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 同时支持 TLS 1.2 和 1.3(过渡期)
ssl_protocols TLSv1.2 TLSv1.3;
# TLS 1.2 的密码套件(仅 ECDHE + GCM)
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
# TLS 1.3 密码套件由 OpenSSL 自动选择,无需手动配置
# 服务器优先选择
ssl_prefer_server_ciphers off;
# 注意: TLS 1.3 中此选项无效
# TLS 1.3 的密码套件顺序始终由客户端决定
# ECDH 曲线
ssl_ecdh_curve X25519:P-256:P-384;
# 0-RTT(评估风险后启用)
ssl_early_data on;
# 传递 0-RTT 标记给后端
proxy_set_header Early-Data $ssl_early_data;
# Session Ticket
ssl_session_tickets on;
ssl_session_timeout 1d;
# 确保 Ticket 密钥定期轮换
# ssl_session_ticket_key /etc/nginx/ticket-keys/current.key;
# ssl_session_ticket_key /etc/nginx/ticket-keys/previous.key;
}
7.3 常见升级问题
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 中间设备阻断 | TLS 1.3 握手超时 | 检查防火墙/IDS 是否支持 TLS 1.3 |
| OpenSSL 版本太旧 | 不支持 -tls1_3 参数 | 升级到 OpenSSL 1.1.1+ |
| 客户端不兼容 | 老版本浏览器/SDK 无法连接 | 保持 TLS 1.2 兼容 |
| Java 客户端 | Java 8 不支持 TLS 1.3 | 升级到 Java 11+ |
| 企业代理 | SSL Inspection 设备不认识 TLS 1.3 | 升级代理固件或旁路 |
| 0-RTT 重放 | POST 请求被重复执行 | 禁用 0-RTT 或做幂等保护 |
# 检查客户端 TLS 1.3 支持情况
# Python
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
# 需要 OpenSSL 1.1.1+
# curl
curl --version | grep TLS
# curl 7.61.0+ 支持 TLS 1.3
# Go
# Go 1.12+ 默认启用 TLS 1.3
# tls.Config{MinVersion: tls.VersionTLS13}
# Java
java -version
# Java 11+ 原生支持 TLS 1.3
# Java 8u261+ 部分支持(需要手动启用)八、TLS 1.2 vs 1.3 完整对比
| 维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| RFC | RFC 5246(2008) | RFC 8446(2018) |
| 握手 RTT | 2 RTT(完整) / 1 RTT(恢复) | 1 RTT(完整) / 0-RTT(恢复) |
| 密码套件数量 | 37+ 种(含不安全的) | 5 种(全部安全) |
| 密钥交换 | RSA / DHE / ECDHE | 仅 (EC)DHE + PSK |
| 前向保密 | 可选(取决于密钥交换) | 强制 |
| 握手加密 | 无(明文传输证书) | 有(ServerHello 后加密) |
| 密钥推导 | 自定义 PRF | HKDF(标准化) |
| 密钥更新 | 重新协商 | KeyUpdate 消息 |
| 会话恢复 | Session ID / Ticket | PSK(统一机制) |
| 0-RTT | 不支持 | 支持(有重放风险) |
| 版本协商 | ProtocolVersion 字段 | supported_versions 扩展 |
| 压缩 | 支持但应禁用 | 已移除 |
| 重新协商 | 支持 | 已移除 |
九、性能测试
TLS 版本的性能差异主要体现在握手阶段。一旦握手完成,数据传输阶段的性能几乎相同(使用相同的 AEAD 算法)。
9.1 握手延迟对比
# 对比 TLS 1.2 和 TLS 1.3 的握手延迟
# TLS 1.2 握手延迟
for i in $(seq 1 10); do
start=$(date +%s%N)
echo | openssl s_client -connect example.com:443 \
-tls1_2 -no_ticket 2>/dev/null >/dev/null
end=$(date +%s%N)
echo "TLS 1.2: $(( (end - start) / 1000000 ))ms"
done
# TLS 1.3 握手延迟
for i in $(seq 1 10); do
start=$(date +%s%N)
echo | openssl s_client -connect example.com:443 \
-tls1_3 2>/dev/null >/dev/null
end=$(date +%s%N)
echo "TLS 1.3: $(( (end - start) / 1000000 ))ms"
done
# 使用 curl 测量完整的 HTTPS 请求时间
# TLS 1.2
curl -o /dev/null -s -w "\
DNS: %{time_namelookup}s\n\
TCP: %{time_connect}s\n\
TLS: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
--tlsv1.2 --tls-max 1.2 https://example.com
# TLS 1.3
curl -o /dev/null -s -w "\
DNS: %{time_namelookup}s\n\
TCP: %{time_connect}s\n\
TLS: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
--tlsv1.3 https://example.com
# 典型结果 (RTT=30ms 的场景):
# TLS 1.2: TLS 握手 ≈ 90ms (3 × RTT, 含 TCP 握手)
# TLS 1.3: TLS 握手 ≈ 60ms (2 × RTT, 含 TCP 握手)
# 节省约 30ms (一个 RTT)9.2 吞吐量对比
数据传输阶段,TLS 1.2 和 1.3 使用相同的 AEAD 算法时,吞吐量几乎没有差异:
# 使用 openssl s_time 测试吞吐量
# TLS 1.2
openssl s_time -connect example.com:443 \
-tls1_2 -time 10 2>/dev/null | tail -3
# 1234 connections in 10.00s; 123.4 connections/s
# TLS 1.3
openssl s_time -connect example.com:443 \
-tls1_3 -time 10 2>/dev/null | tail -3
# 1567 connections in 10.00s; 156.7 connections/s
# → TLS 1.3 的连接建立速率更高(因为握手更快)
# → 但单连接的数据传输速率相同十、总结
TLS 1.3 不是 TLS 1.2 的简单优化,而是一次安全理念的升级:
“移除”比”添加”更重要。 TLS 1.3 的安全提升主要来自移除不安全的选项——RSA 密钥交换、CBC 模式、RC4、压缩、重新协商。当你无法选择不安全的配置时,系统整体的安全水位就提高了。这也是 TLS 1.3 只保留 5 个密码套件的设计哲学。
1 RTT 的节省来自”乐观猜测”。 客户端在 ClientHello 中就发送 ECDHE 公钥(猜测服务器会接受 X25519),如果猜对了就省下一个 RTT。这个优化在高延迟网络(RTT > 100ms)中效果显著——30% 以上的连接建立时间缩短。
0-RTT 是性能与安全的权衡。 它能在第一个包就发送加密数据,但代价是无法防止重放攻击。工程上的建议很简单:只对幂等操作使用 0-RTT,非幂等操作必须在应用层做幂等保护。
中间设备僵化是最大的部署障碍。 TLS 1.3 不得不通过”伪装成 TLS 1.2”来绕过不支持新协议的防火墙和代理。这个教训告诉我们:协议的可扩展性不只取决于协议本身的设计,还取决于中间设备的实现质量。
升级路径是渐进的。 不需要立即禁用 TLS 1.2——同时启用 TLS 1.2 和 1.3,支持 TLS 1.3 的客户端自动使用新协议,不支持的客户端降级到 TLS 1.2。当 TLS 1.2 流量占比降到足够低时,再考虑禁用。
下一篇我们进入证书工程——PKI 体系、ACME 协议与 Let’s Encrypt 的自动化证书管理,以及证书过期导致宕机的防范策略。
上一篇:TLS 1.2 握手完整解剖:从 ClientHello 到 Application Data
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】TLS 1.2 握手完整解剖:从 ClientHello 到 Application Data
TLS 1.2 握手是 HTTPS 的基础机制。本文逐包分析完整握手的每一步——ClientHello 的密码套件协商、ServerHello 的参数选择、证书验证、密钥交换(RSA vs ECDHE)的安全差异,以及 ChangeCipherSpec 到 Application Data 的完整流程。结合 Wireshark 抓包和 OpenSSL 命令实操。
【网络工程】TLS 攻防:降级攻击、中间人与协议漏洞
TLS 协议在过去二十年里暴露了一系列严重漏洞。本文从攻击原理到防御工程,系统性剖析 BEAST、POODLE、Heartbleed、DROWN 等经典攻击的技术本质,详解版本降级攻击与 TLS_FALLBACK_SCSV 防御、CBC padding oracle 攻击的数学原理、以及生产环境的 TLS 安全配置检查清单。
【QUIC 协议拆解】QUIC 协议拆解(上):为什么 TCP 改不动了
打开一个网页要握手几次?TCP 三次 + TLS 一次 = 至少 2 RTT。QUIC 说:我一次搞定,重连甚至 0 次。不是 TCP 不够好,是它的基因决定了它改不动。
【网络工程】CDN 与 HTTPS:边缘 TLS、证书管理与安全
CDN 的 HTTPS 部署涉及边缘 TLS 终止、证书托管、回源加密等多个工程环节。本文系统拆解 CDN HTTPS 的架构模式、证书管理方案、安全最佳实践与常见故障排查方法。