Network · #tls#ssl#security#https

TLS 1.3握手过程与性能优化

2025.05.03 6 min 2.6k
// 目录 · contents

前言

TLS(Transport Layer Security)是互联网安全通信的基石。TLS 1.3(RFC 8446)相比TLS 1.2做了大幅简化和改进,移除了不安全的密码套件,将握手从2-RTT减少到1-RTT,并支持0-RTT恢复。本文将深入分析TLS 1.3的握手过程、密钥交换机制和各种性能优化手段。

TLS版本演进

timeline
    title TLS协议演进
    1995 : SSL 2.0 (已废弃)
    1996 : SSL 3.0 (已废弃, POODLE攻击)
    1999 : TLS 1.0 (已废弃)
    2006 : TLS 1.1 (已废弃)
    2008 : TLS 1.2 (广泛使用)
    2018 : TLS 1.3 (RFC 8446, 推荐)

TLS 1.3握手过程(1-RTT)

完整握手流程

sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: 1-RTT Full Handshake

    C->>S: ClientHello
    Note right of C: supported_versions: TLS 1.3<br>key_share: ECDHE公钥(X25519)<br>signature_algorithms<br>supported_groups<br>psk_key_exchange_modes

    S->>C: ServerHello
    Note left of S: selected version: TLS 1.3<br>key_share: ECDHE公钥(X25519)<br>选定的密码套件

    Note over C,S: 此后所有数据加密传输

    S->>C: {EncryptedExtensions}
    S->>C: {CertificateRequest*} (可选)
    S->>C: {Certificate}
    S->>C: {CertificateVerify}
    S->>C: {Finished}

    Note over C: 验证证书链<br>验证CertificateVerify<br>验证Finished

    C->>S: {Certificate*} (如果请求)
    C->>S: {CertificateVerify*}
    C->>S: {Finished}
    C->>S: [Application Data]

    S->>C: [Application Data]

与TLS 1.2的对比

graph LR
    subgraph "TLS 1.2 (2-RTT)"
        A1[ClientHello] --> A2[ServerHello<br>Certificate<br>ServerKeyExchange<br>ServerHelloDone]
        A2 --> A3[ClientKeyExchange<br>ChangeCipherSpec<br>Finished]
        A3 --> A4[ChangeCipherSpec<br>Finished]
        A4 --> A5[Application Data]
    end

    subgraph "TLS 1.3 (1-RTT)"
        B1[ClientHello<br>+ key_share] --> B2[ServerHello<br>+ key_share<br>加密: Certificate<br>Finished]
        B2 --> B3[Finished<br>Application Data]
    end

TLS 1.3的关键改进: - 客户端在ClientHello中就发送密钥共享参数,省去一个RTT - 移除了ChangeCipherSpec消息 - ServerHello之后的所有消息都加密传输

密钥交换:ECDHE

TLS 1.3只支持前向安全(Forward Secrecy)的密钥交换方式,强制使用ECDHE(或DHE)。

ECDHE密钥交换过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ECDHE密钥交换(概念示意,使用X25519曲线)
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey

# Client端
client_private = X25519PrivateKey.generate()
client_public = client_private.public_key()

# Server端
server_private = X25519PrivateKey.generate()
server_public = server_private.public_key()

# 双方交换公钥后,各自计算共享密钥
client_shared = client_private.exchange(server_public)
server_shared = server_private.exchange(client_public)

assert client_shared == server_shared # 共享密钥一致

密钥派生(HKDF)

TLS 1.3使用HKDF(HMAC-based Key Derivation Function)从共享密钥派生出各种会话密钥:

graph TB
    PSK[Pre-Shared Key<br>或 0] --> ES[Early Secret<br>HKDF-Extract]
    ES --> BK[binder_key]
    ES --> ETS[client_early_traffic_secret]

    ES --> DS[Derived Secret]
    ECDHE[ECDHE Shared Secret] --> HS[Handshake Secret<br>HKDF-Extract]
    DS --> HS

    HS --> CHTS[client_handshake_traffic_secret]
    HS --> SHTS[server_handshake_traffic_secret]

    HS --> DS2[Derived Secret]
    DS2 --> MS[Master Secret<br>HKDF-Extract]

    MS --> CATS[client_application_traffic_secret]
    MS --> SATS[server_application_traffic_secret]
    MS --> RMS[resumption_master_secret]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 密钥派生过程(简化)
import hmac
import hashlib

def hkdf_extract(salt, ikm):
"""提取伪随机密钥"""
return hmac.new(salt, ikm, hashlib.sha256).digest()

def hkdf_expand_label(secret, label, context, length):
"""扩展密钥"""
hkdf_label = (
length.to_bytes(2, 'big') +
len(b"tls13 " + label).to_bytes(1, 'big') +
b"tls13 " + label +
len(context).to_bytes(1, 'big') +
context
)
return hkdf_expand(secret, hkdf_label, length)

# 密钥调度
early_secret = hkdf_extract(salt=b'\x00'*32, ikm=psk or b'\x00'*32)
handshake_secret = hkdf_extract(
salt=derive_secret(early_secret, b"derived", b""),
ikm=ecdhe_shared_secret
)
master_secret = hkdf_extract(
salt=derive_secret(handshake_secret, b"derived", b""),
ikm=b'\x00'*32
)

密码套件

TLS 1.3大幅精简了密码套件,只保留5个:

1
2
3
4
5
TLS_AES_128_GCM_SHA256         (0x1301) - 推荐
TLS_AES_256_GCM_SHA384 (0x1302) - 推荐
TLS_CHACHA20_POLY1305_SHA256 (0x1303) - 移动端推荐
TLS_AES_128_CCM_SHA256 (0x1304)
TLS_AES_128_CCM_8_SHA256 (0x1305)

移除了所有不安全的组件: - RC4、DES、3DES - 静态RSA密钥交换(无前向安全) - CBC模式(BEAST/POODLE攻击) - MD5、SHA-1 - 压缩(CRIME攻击)

1
2
3
4
5
6
7
8
9
10
# Nginx TLS 1.3配置
ssl_protocols TLSv1.2 TLSv1.3;

# TLS 1.3密码套件(Nginx 1.19.4+)
ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

# TLS 1.2密码套件(兼容)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

ssl_prefer_server_ciphers off;

证书链验证

graph TB
    ROOT[Root CA<br>内置于浏览器/OS] --> |签发| ICA[Intermediate CA]
    ICA --> |签发| LEAF[Leaf Certificate<br>example.com]

    LEAF --> |包含| PUB[Public Key]
    LEAF --> |包含| SAN[Subject Alternative Names<br>example.com<br>*.example.com]
    LEAF --> |包含| VALID[Validity Period<br>Not Before / Not After]

    style ROOT fill:#e53935,color:#fff
    style ICA fill:#fb8c00,color:#fff
    style LEAF fill:#43a047,color:#fff

验证流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 证书链验证(概念)
def verify_certificate_chain(cert_chain, trusted_roots):
"""
1. 验证签名链
2. 检查有效期
3. 检查域名匹配
4. 检查吊销状态
"""
for i, cert in enumerate(cert_chain):
# 检查有效期
if cert.not_valid_before > now or cert.not_valid_after < now:
raise CertificateExpired(cert)

# 验证签名
if i < len(cert_chain) - 1:
issuer = cert_chain[i + 1]
else:
issuer = find_in_trust_store(cert.issuer, trusted_roots)
if not issuer:
raise UntrustedRoot(cert)

verify_signature(cert, issuer.public_key)

# 验证域名
leaf = cert_chain[0]
if not matches_san(leaf, requested_hostname):
raise HostnameMismatch()

return True

CertificateVerify

TLS 1.3中,服务端通过CertificateVerify消息证明自己拥有证书对应的私钥:

1
2
3
4
5
6
7
8
CertificateVerify:
signature = Sign(
private_key,
" " * 2 + // 64个空格
"TLS 1.3, server CertificateVerify" +
"\x00" +
Hash(Handshake Context) // 到此为止的所有握手消息的哈希
)

OCSP Stapling

传统OCSP需要浏览器单独向CA查询证书吊销状态,增加延迟。OCSP Stapling让服务端代为查询并在TLS握手时带上结果。

sequenceDiagram
    participant B as Browser
    participant S as Web Server
    participant CA as OCSP Responder (CA)

    Note over S,CA: 服务端定期获取OCSP响应
    S->>CA: OCSP Request
    CA->>S: OCSP Response (Signed, valid 7 days)

    Note over B,S: TLS握手时附带OCSP响应
    B->>S: ClientHello (+ status_request)
    S->>B: ServerHello
    S->>B: Certificate + OCSP Response (Stapled)
    Note over B: 验证OCSP响应签名<br>无需额外网络请求
1
2
3
4
5
6
7
8
9
10
# Nginx OCSP Stapling配置
ssl_stapling on;
ssl_stapling_verify on;

# 用于验证OCSP响应的CA证书链
ssl_trusted_certificate /etc/ssl/certs/ca-chain.pem;

# DNS解析器(用于查询OCSP Responder)
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
1
2
3
# 验证OCSP Stapling是否生效
openssl s_client -connect example.com:443 -status 2>/dev/null | grep "OCSP Response Status"
# 期望输出: OCSP Response Status: successful (0x0)

会话恢复(Session Resumption)

TLS 1.3 PSK恢复

TLS 1.3使用PSK(Pre-Shared Key)机制实现会话恢复,取代了TLS 1.2的Session ID和Session Ticket:

sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: 首次完整握手
    C->>S: ClientHello
    S->>C: ServerHello ... Finished
    C->>S: Finished
    S->>C: NewSessionTicket (包含PSK)
    Note over C: 保存PSK

    Note over C,S: 后续恢复握手 (1-RTT with PSK)
    C->>S: ClientHello + pre_shared_key + key_share
    Note over S: 验证PSK
    S->>C: ServerHello (选择PSK) + Finished
    C->>S: Finished + Application Data

    Note over C,S: 0-RTT恢复 (0-RTT with PSK)
    C->>S: ClientHello + pre_shared_key + early_data + [Early Data]
    S->>C: ServerHello + Finished
    C->>S: Finished

0-RTT安全考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 0-RTT数据的安全限制
class ZeroRTTPolicy:
"""0-RTT只适用于幂等请求"""

SAFE_METHODS = {'GET', 'HEAD', 'OPTIONS'}

@staticmethod
def is_safe_for_0rtt(request):
# 1. 只允许幂等方法
if request.method not in ZeroRTTPolicy.SAFE_METHODS:
return False

# 2. 不应包含敏感操作
if request.has_side_effects:
return False

# 3. 服务端应实现重放防护
return True

@staticmethod
def server_replay_protection(ticket_nonce, max_age=10):
"""服务端重放防护"""
# 使用一次性token或时间窗口
if ticket_nonce in used_nonces:
return False # 拒绝重放
used_nonces.add(ticket_nonce)
return True

性能优化最佳实践

证书优化

1
2
3
4
5
6
7
8
9
10
11
# 1. 使用ECDSA证书(比RSA更小更快)
# RSA 2048位证书: ~1KB
# ECDSA P-256证书: ~0.5KB

# 生成ECDSA密钥和CSR
openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -key server.key -out server.csr

# 2. 使用完整但精简的证书链
# 包含中间CA,不包含根CA(浏览器已内置)
cat server.crt intermediate.crt > fullchain.pem

全面配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server {
listen 443 ssl;
http2 on;

# 证书
ssl_certificate /etc/ssl/fullchain.pem;
ssl_certificate_key /etc/ssl/server.key;

# 协议版本
ssl_protocols TLSv1.2 TLSv1.3;

# 密码套件
ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

# ECDHE曲线
ssl_ecdh_curve X25519:prime256v1;

# Session缓存
ssl_session_cache shared:TLS:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/ca-chain.pem;
resolver 8.8.8.8;

# 0-RTT (TLS 1.3 Early Data)
ssl_early_data on;
proxy_set_header Early-Data $ssl_early_data;

# 安全头
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}

检测和评估工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用testssl.sh进行全面检测
./testssl.sh https://example.com

# 使用ssllabs进行评分
# https://www.ssllabs.com/ssltest/

# 使用openssl调试
openssl s_client -connect example.com:443 -tls1_3 -msg

# 检查支持的协议和密码套件
nmap --script ssl-enum-ciphers -p 443 example.com

# 测量TLS握手时间
curl -w "TCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" \
-o /dev/null -s https://example.com

常见问题排查

问题 可能原因 解决方案
ERR_SSL_VERSION_OR_CIPHER_MISMATCH 密码套件不匹配 检查客户端支持的套件
Certificate expired 证书过期 更新证书,配置自动续期
Certificate chain incomplete 缺少中间CA 配置完整证书链
OCSP stapling failed OCSP Responder不可达 检查DNS和网络连通性
0-RTT not working 缺少PSK或配置未开启 检查ssl_early_data配置

总结

TLS 1.3在安全性和性能方面都比TLS 1.2有显著提升:

  1. 1-RTT握手:客户端在ClientHello中发送密钥共享参数,省去一个RTT
  2. 强制前向安全:只支持ECDHE密钥交换,即使长期密钥泄露也无法解密历史会话
  3. 精简密码套件:移除所有已知不安全的算法
  4. 0-RTT恢复:对重复访问的服务器可以零延迟发送数据
  5. OCSP Stapling:消除证书吊销检查的额外延迟

在2025年,TLS 1.3应该是所有新部署的默认选择,同时保留TLS 1.2兼容性直到可以安全移除。

作者 · authorzt
发布 · date2025-05-03
篇幅 · length2.6k 字 · 6 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论