SSL/TLS 握手是 HTTPS 性能的关键瓶颈。本文聚焦会话恢复(Session Resumption)技术,帮助你减少握手延迟。

问题:SSL 握手的开销

完整的 TLS 握手需要多次网络往返:

1
2
3
4
5
# 测试握手时间
curl -w "Time: %{time_total}s\n" -o /dev/null -s https://example.com

# 使用 openssl 测试连接时间
time openssl s_client -connect example.com:443 </dev/null

对于 TLS 1.2,完整握手需要 2 个 RTT。通过会话恢复可以降至 0-1 个 RTT。

Session ID 复用

工作原理

服务器为每个会话分配唯一 ID,客户端在后续连接时发送该 ID,服务器据此恢复会话状态。

Nginx 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Session ID 复用
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
}

配置说明:

参数 含义
shared:SSL:10m 共享缓存,约存储 40000 个会话
ssl_session_timeout 会话有效期
ssl_session_tickets off 禁用 Session Ticket(使用 Session ID)

测试 Session ID 复用

1
2
3
4
5
# 第一次连接,建立新会话
openssl s_client -connect example.com:443 -sess_out session.pem

# 使用保存的会话复用连接
openssl s_client -connect example.com:443 -sess_in session.pem

查看输出中的 Reused, TLSv1.3 表示会话已复用。

Session Ticket 复用

工作原理

服务器将会话状态加密后发送给客户端,客户端保存 Ticket,下次连接时发送给服务器恢复会话。

Nginx 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Session Ticket 复用
    ssl_session_cache off;
    ssl_session_tickets on;
    ssl_session_timeout 1d;

    # 配置 Ticket 密钥(多服务器部署必须)
    ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
}

生成 Ticket 密钥

1
2
3
# 生成 80 字节的 Ticket 密钥
openssl rand 80 > /etc/nginx/ssl/ticket.key
chmod 600 /etc/nginx/ssl/ticket.key

Session Ticket 的优缺点

优点:

  • 服务器无状态,易于水平扩展
  • 客户端恢复更快

缺点:

  • 需要安全分发 Ticket 密钥
  • Ticket 密钥泄露会导致会话被解密
  • 不支持前向保密(需定期轮换密钥)

TLS 1.3 的 0-RTT

工作原理

TLS 1.3 支持 0-RTT(Zero Round Trip Time),客户端可以在第一个请求中携带应用数据。

Nginx 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # 启用 TLS 1.3
    ssl_protocols TLSv1.2 TLSv1.3;

    # 启用 0-RTT(注意安全风险)
    ssl_early_data on;

    # Session Ticket 配置
    ssl_session_tickets on;
    ssl_session_timeout 1d;
}

安全注意事项

0-RTT 存在重放攻击风险,仅适用于幂等请求:

1
2
3
4
5
6
7
# 限制 0-RTT 用于敏感操作
location /api/payment {
    if ($ssl_early_data) {
        return 425;  # Too Early
    }
    # ... 正常处理
}

OCSP Stapling

减少证书验证延迟:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_stapling_responder http://ocsp.example.com;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

测试 OCSP Stapling:

1
2
3
4
5
6
# 检查服务器是否启用 OCSP Stapling
openssl s_client -connect example.com:443 -status < /dev/null 2>&1 | \
  grep -A 17 "OCSP response"

# 或者使用在线工具
# https://www.ssllabs.com/ssltest/

性能测试对比

使用 openssl s_time 测试握手性能:

1
2
3
4
5
# 测试 100 个连接的性能
openssl s_time -connect example.com:443 -new -time 10

# 测试会话复用性能
openssl s_time -connect example.com:443 -time 10

预期结果

场景 连接数/秒
完整握手 10-50
Session ID 复用 100-500
Session Ticket 复用 100-500
TLS 1.3 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
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # 协议配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    # 会话恢复
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets on;
    ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
    ssl_early_data on;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000" always;
}

验证配置:

1
2
3
4
5
6
7
8
9
# 测试配置语法
nginx -t

# 重载配置
nginx -s reload

# 测试最终效果
openssl s_client -connect example.com:443 -tls1_3 < /dev/null 2>&1 | \
  grep -E "Protocol|Reused|Verify"

参考来源