前言

TLS 会话票据(Session Ticket)是 TLS 会话恢复的核心机制之一,允许服务器在完成一次完整握手后,向客户端发送一个加密的会话状态票据。下次连接时,客户端出示该票据即可跳过耗时的密钥交换过程,实现快速恢复会话

但如果会话票据密钥长期不轮换,攻击者可能通过窃取的历史密钥解密旧票据,威胁之前会话的安全性。本文详细介绍会话票据密钥轮换的原理和最佳实践。

会话票据工作原理

完整握手 vs 会话恢复

完整 TLS 握手:

1
2
3
4
5
客户端 → ServerHello → 客户端密钥交换 → ServerHelloDone →
ServerCertificate ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←
ServerKeyExchange + CertificateRequest + ServerHelloDone
客户端 → Certificate + ClientKeyExchange + CertificateVerify → Finished
Server → NewSessionTicket ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←

使用会话票据恢复:

1
2
客户端 → ClientHello (包含 session ticket) → 
Server → ServerHello + NewSessionTicket → Finished

会话票据加密

会话票据使用专门的会话票据密钥(Session Ticket Key)加密。这个密钥由服务器持有,通常是 32 字节(256 位)的随机数。

1
2
# 生成一个随机会话票据密钥
openssl rand -hex 32

为什么需要轮换密钥

安全原因

  1. 密钥泄露风险:长期使用同一密钥会增加泄露风险
  2. 前向保密:即使当前密钥泄露,轮换后的旧票据也无法解密
  3. 合规要求:部分安全标准(如 PCI DSS)要求定期轮换加密密钥

密钥生命周期

密钥使用时间 风险等级 建议
< 24 小时 最佳实践
1-7 天 推荐
> 30 天 应尽快轮换

Nginx 配置

启用会话票据

Nginx 默认启用会话票据,但需要配置密钥轮换:

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

    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/private.key;

    # 会话票据配置
    ssl_session_tickets on;
    
    # 轮换会话票据密钥(推荐 24 小时轮换一次)
    # 使用 3 个密钥实现无缝切换
    ssl_session_ticket_key ticket_key_current.key;
    ssl_session_ticket_key ticket_key_prev1.key;
    ssl_session_ticket_key ticket_key_prev2.key;
}

生成密钥文件

1
2
3
4
5
6
7
# 生成 3 个密钥文件(当前密钥 + 2 个历史密钥)
openssl rand -hex 32 > ticket_key_current.key
openssl rand -hex 32 > ticket_key_prev1.key
openssl rand -hex 32 > ticket_key_prev2.key

# 设置适当权限
chmod 600 ticket_key_*.key

自动轮换脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash
# rotate_ticket_keys.sh - 会话票据密钥轮换脚本

KEY_DIR="/etc/nginx/ssl"
TIMESTAMP=$(date +%Y%m%d%H%M%S)

# 轮换密钥:prev2 -> prev1 -> prev1 -> current -> new
cp "${KEY_DIR}/ticket_key_prev1.key" "${KEY_DIR}/ticket_key_prev2.key"
cp "${KEY_DIR}/ticket_key_current.key" "${KEY_DIR}/ticket_key_prev1.key"
openssl rand -hex 32 > "${KEY_DIR}/ticket_key_current.key"

# 重新加载 Nginx
nginx -s reload

echo "[${TIMESTAMP}] Ticket keys rotated"

添加到 crontab(每天凌晨 3 点执行):

1
0 3 * * * /path/to/rotate_ticket_keys.sh

Apache 配置

启用会话票据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<VirtualHost *:443>
    ServerName yourdomain.com
    
    SSLEngine on
    SSLCertificateFile /path/to/certificate.crt
    SSLCertificateKeyFile /path/to/private.key
    
    # 启用会话票据
    SSLSessionTickets On
    
    # 配置票据密钥文件(Apache 2.4.37+)
    # 使用 3 个密钥实现无缝切换
    SSLSessionTicketKeyFile /path/to/ticket_key_current.key
    SSLSessionTicketKeyFile /path/to/ticket_key_prev1.key
</VirtualHost>

密钥轮换

Apache 会话票据密钥轮换需要重启服务,建议使用脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
# rotate_apache_ticket_keys.sh

KEY_DIR="/etc/apache2/ssl"

# 轮换密钥
cp "${KEY_DIR}/ticket_key_prev1.key" "${KEY_DIR}/ticket_key_prev2.key"
cp "${KEY_DIR}/ticket_key_current.key" "${KEY_DIR}/ticket_key_prev1.key"
openssl rand -hex 32 > "${KEY_DIR}/ticket_key_current.key"

# 重启 Apache
apachectl graceful

OpenSSL s_server 会话票据

使用 OpenSSL s_server 测试时,可以通过 -num_tickets 参数控制票据数量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 启动服务器,发行 2 个票据
openssl s_server -cert server.crt -key server.key \
    -num_tickets 2 \
    -www

# 客户端连接时请求会话恢复
openssl s_client -connect localhost:4433 -sess_out session.pem

# 再次连接恢复会话
openssl s_client -connect localhost:4433 -sess_in session.pem

验证会话票据

使用 OpenSSL s_client 查看

1
2
3
4
5
# 检查服务器是否发送会话票据
openssl s_client -connect yourdomain.com:443 -brief 2>&1 | grep -i ticket

# 查看会话恢复信息
openssl s_client -connect yourdomain.com:443 -session_reuse 2>&1 | grep -i "reused\|ticket"

使用 Wireshark 抓包分析

  1. 捕获 TLS 握手包
  2. 过滤 tls.handshake.type == 4(NewSessionTicket)
  3. 查看 Ticket 字段和生命周期

检查密钥文件修改时间

1
2
3
4
5
# 查看密钥文件最后修改时间
ls -la /etc/nginx/ssl/ticket_key_*.key

# 监控密钥轮换
watch -n 1 'ls -la /etc/nginx/ssl/ticket_key_current.key'

常见问题

会话票据导致握手失败?

如果客户端持有的票据无法解密(密钥已轮换),会回退到完整握手。配置多个历史密钥可实现无缝切换。

密钥轮换会影响性能吗?

轮换本身不影响性能。首次使用新密钥时,旧票据会失效,客户端需要进行完整握手。但配置 2-3 个历史密钥可缓解此问题。

容器环境如何处理?

在容器环境中,建议:

  • 将密钥挂载为卷
  • 使用 Kubernetes Secret 或 Docker Secret
  • 定期轮换密钥并更新 Secret

安全建议

  1. 至少配置 3 个密钥:当前密钥 + 2 个历史密钥
  2. 24 小时轮换一次:平衡安全性和性能
  3. 密钥长度至少 256 位:使用 openssl rand -hex 32
  4. 限制密钥访问权限chmod 600 仅 root 可读
  5. 监控密钥轮换日志:记录轮换事件,便于审计
  6. 测试故障恢复:验证密钥轮换不会中断服务

小结

TLS 会话票据密钥轮换是保障会话安全的重要措施。通过定期轮换密钥(建议每 24 小时),可以:

  • 降低密钥泄露风险
  • 实现前向保密
  • 满足合规要求

配置时使用 3 个密钥实现无缝切换,配合监控和告警机制,确保轮换过程平稳可靠。

参考来源