前言
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 from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey client_private = X25519PrivateKey.generate() client_public = client_private.public_key() 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 hmacimport hashlibdef 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 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-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 ssl_stapling on ;ssl_stapling_verify on ;ssl_trusted_certificate /etc/ssl/certs/ca-chain.pem;resolver 8.8.8.8 8.8.4.4 valid=300s ;resolver_timeout 5s ;
1 2 3 openssl s_client -connect example.com:443 -status 2>/dev/null | grep "OCSP Response Status"
会话恢复(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 class ZeroRTTPolicy : """0-RTT只适用于幂等请求""" SAFE_METHODS = {'GET' , 'HEAD' , 'OPTIONS' } @staticmethod def is_safe_for_0rtt (request ): if request.method not in ZeroRTTPolicy.SAFE_METHODS: return False if request.has_side_effects: return False return True @staticmethod def server_replay_protection (ticket_nonce, max_age=10 ): """服务端重放防护""" 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 openssl ecparam -genkey -name prime256v1 -out server.key openssl req -new -key server.key -out server.csrcat 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; ssl_ecdh_curve X25519:prime256v1; ssl_session_cache shared:TLS:10m ; ssl_session_timeout 1d ; ssl_session_tickets on ; ssl_stapling on ; ssl_stapling_verify on ; ssl_trusted_certificate /etc/ssl/ca-chain.pem; resolver 8.8.8.8 ; 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 https://example.com openssl s_client -connect example.com:443 -tls1_3 -msg nmap --script ssl-enum-ciphers -p 443 example.com 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-RTT握手 :客户端在ClientHello中发送密钥共享参数,省去一个RTT
强制前向安全 :只支持ECDHE密钥交换,即使长期密钥泄露也无法解密历史会话
精简密码套件 :移除所有已知不安全的算法
0-RTT恢复 :对重复访问的服务器可以零延迟发送数据
OCSP Stapling :消除证书吊销检查的额外延迟
在2025年,TLS 1.3应该是所有新部署的默认选择,同时保留TLS
1.2兼容性直到可以安全移除。