HTTPS 与 TLS 1.3 深度解析
当你在浏览器地址栏看到那把小锁图标时,背后是一场精密的密码学协作——TLS 协议在几十毫秒内完成了身份验证、密钥协商和加密通道建立。而 TLS 1.3 的出现,让这个过程更快、更安全、更简洁。
本文从 SSL/TLS 的历史演进出发,深入解析 TLS 1.3 的握手流程、ECDHE 密钥交换、证书体系、性能优化与 Nginx 配置最佳实践。
一、HTTPS 与 SSL/TLS 发展简史
HTTPS = HTTP + SSL/TLS。TLS 的前身是 SSL(Secure Sockets Layer),由 Netscape 在 1994 年设计。经历了多次版本迭代和重命名后,形成了今天的体系:
| 版本 | 年份 | 状态 | 关键事件 |
|---|---|---|---|
| SSL 1.0 | 1994 | ❌ 未发布 | 存在严重安全漏洞,Netscape 内部就叫停了 |
| SSL 2.0 | 1995 | ❌ 已废弃 | 2011 年 RFC 6176 正式禁止,存在中间人攻击漏洞 |
| SSL 3.0 | 1996 | ❌ 已废弃 | 2014 年 POODLE 攻击证明其不安全,RFC 7568 禁止 |
| TLS 1.0 | 1999 | ❌ 已废弃 | 实质是 SSL 3.1 的标准化,2020 年各大浏览器停止支持 |
| TLS 1.1 | 2006 | ❌ 已废弃 | RFC 4346,添加了 CBC 攻击防护,2020 年同样被弃用 |
| TLS 1.2 | 2008 | ⚠️ 仍可用 | RFC 5246,支持 AEAD 加密(GCM/CCM),当前仍广泛使用 |
| TLS 1.3 | 2018 | ✅ 推荐 | RFC 8446,1-RTT 握手、0-RTT 恢复、强制 PFS,安全性和性能大幅提升 |
二、TLS 1.3 核心改进
TLS 1.3 不是小修小补——它对 TLS 1.2 做了大刀阔斧的简化。核心改进集中在三个方面:更快、更安全、更简洁。
1. 握手延迟:1-RTT → 0-RTT
TLS 1.2 的完整握手需要 2 个 RTT(TCP 握手 1-RTT + TLS 握手 1-RTT),首次连接需要 3 个 RTT 才能发送第一个 HTTP 请求。
TLS 1.3 将首次握手压缩到 1-RTT,恢复连接时支持 0-RTT——客户端可以在第一个飞行中就携带应用数据。
TLS 1.2 首次连接(2-RTT TLS 握手):
Client → Server: ClientHello
Server → Client: ServerHello + Certificate + ServerHelloDone
Client → Server: ClientKeyExchange + ChangeCipherSpec + Finished
Server → Client: ChangeCipherSpec + Finished
→ 总计 2-RTT 后才能发 HTTP 请求
TLS 1.3 首次连接(1-RTT 握手):
Client → Server: ClientHello + KeyShare
Server → Client: ServerHello + KeyShare + Certificate + Finished
Client → Server: Finished
→ 总计 1-RTT 后即可发 HTTP 请求
TLS 1.3 恢复连接(0-RTT):
Client → Server: ClientHello + KeyShare + EarlyData
Server → Client: ServerHello + KeyShare + Finished + EarlyData
→ 第一个包就携带应用数据
2. 移除不安全算法
TLS 1.3 最激进的决定:砍掉了大量老旧和不安全的密码套件,只保留经过严格审计的算法。
| 类别 | TLS 1.2 支持的(已移除) | TLS 1.3 保留的 |
|---|---|---|
| 密钥交换 | RSA、DH、DHE、ECDHE | 仅 ECDHE |
| 身份验证 | RSA、DSA、ECDSA | RSA-PSS、ECDSA |
| 对称加密 | RC4、3DES、AES-CBC、AES-GCM、ChaCha20 | AES-128-GCM、AES-256-GCM、ChaCha20-Poly1305 |
| 哈希 | MD5、SHA-1、SHA-256、SHA-384 | SHA-256、SHA-384 |
为什么要移除这些?
- RC4:已被彻底破解,多种密钥恢复攻击可行,RFC 7465 明确禁止
- 3DES:64 位块大小导致 SWEET32 攻击,生日攻击下只需捕获约 32GB 流量即可还原明文
- AES-CBC:Lucky13、POODLE、BEAST 等一系列 padding oracle 攻击
- RSA 密钥交换:不支持前向安全性——服务器私钥泄露后,所有历史流量可被解密
- MD5/SHA-1:碰撞攻击已实际可行
3. Perfect Forward Secrecy 强制
前向安全性(PFS)的含义:即使服务器长期私钥泄露,过去的历史会话也无法被解密。
TLS 1.2 中 RSA 密钥交换不支持 PFS——密钥交换依赖服务器公钥加密 pre-master secret,私钥泄露 = 历史全泄露。DHE/ECDHE 虽然支持 PFS,但只是可选的。
TLS 1.3 强制 ECDHE——所有握手都必须使用临时 Diffie-Hellman 密钥交换,每个会话的对称密钥由双方临时生成的椭圆曲线私钥推导,用完即弃。即使服务器的长期私钥泄露,攻击者也无法还原过去的 ECDHE 私钥。
非 PFS(RSA 密钥交换):
Client 生成 pre-master secret → 用服务器公钥加密 → 发给 Server
→ 服务器私钥泄露后,攻击者可以解密所有历史流量 ❌
PFS(ECDHE 密钥交换):
Client 生成临时私钥 a → 发送 G^a(公钥部分)
Server 生成临时私钥 b → 发送 G^b(公钥部分)
双方计算 shared secret = G^(ab)
→ 临时私钥 a, b 用完即弃,长期私钥泄露也无法还原 ❌→✅
三、TLS 1.3 握手流程详解
TLS 1.3 的核心优化在于:客户端不再等服务器发完证书才计算密钥,而是在 ClientHello 中就发送自己的密钥交换参数(KeyShare),让服务器能立即计算出共享密钥。
完整握手(1-RTT)
步骤 1:Client → Server(ClientHello)
├── client_version: TLS 1.3 (0x0304)
├── client_random: 32 字节随机数
├── cipher_suites: [TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, ...]
├── key_share:
│ └── X25519: 客户端的椭圆曲线公钥 G^a(32 字节)
├── supported_versions: [0x0304]
└── signature_algorithms: [rsa_pss_rsae_sha256, ecdsa_secp256r1_sha256, ...]
步骤 2:Server → Client(ServerHello + 扩展)
├── server_version: TLS 1.3
├── server_random: 32 字节随机数
├── cipher_suite: TLS_AES_128_GCM_SHA256(选定一个)
├── key_share:
│ └── X25519: 服务器的椭圆曲线公钥 G^b(32 字节)
└── 🔑 双方此时已能计算 shared_secret = G^(ab)
步骤 3:Server → Client(加密传输,用刚协商的密钥)
├── EncryptedExtensions: 附加协商参数
├── Certificate: 服务器证书链
├── CertificateVerify: 用服务器私钥对握手消息签名(证明身份)
└── Finished: HMAC 验证握手完整性
步骤 4:Client → Server(加密传输)
└── Finished: HMAC 验证握手完整性
→ 握手完成,开始加密通信 ✅
关键创新:步骤 2 之后,所有消息都是加密的。而 TLS 1.2 的 Certificate、ServerKeyExchange 都是明文传输,暴露了证书信息和密钥交换参数。
恢复握手(0-RTT)
当客户端和服务器之前建立过会话,服务器会下发一个 PSK(Pre-Shared Key) 和 session ticket。下次连接时:
步骤 1:Client → Server
├── ClientHello + KeyShare + pre_shared_key
└── EarlyData: 加密的应用数据(用 PSK 派生的 early key)
→ 0-RTT!第一个包就携带业务数据
步骤 2:Server → Client
├── ServerHello + KeyShare
├── EncryptedExtensions + NewSessionTicket
└── 应用数据响应
→ 1-RTT 省掉了,直接开始通信 ✅
1. 无前向安全性——0-RTT 数据的密钥从 PSK 派生,PSK 泄露 = 0-RTT 数据可解密
2. 重放攻击——攻击者可以截获 0-RTT 数据并重新发送,服务器无法区分是原始请求还是重放
3. 缓解措施——服务器应限制 0-RTT 只接受幂等操作,使用 anti-replay 机制(如时间窗口 + 票据去重)
ECDHE 密钥交换原理
ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)是 TLS 1.3 唯一的密钥交换方式。核心是椭圆曲线上的离散对数问题(ECDLP)——已知 G^a 和 G^b,很难推算出 a 或 b,但双方可以轻松计算 G^(ab)。
椭圆曲线:y² = x³ + ax + b (mod p)
X25519 曲线(TLS 1.3 推荐):
- 基于 Curve25519,由 Daniel J. Bernstein 设计
- 256 位密钥,提供约 128 位安全强度
- 无需担心侧信道攻击(常数时间实现)
- 比 NIST P-256 更快、更安全
密钥交换过程:
Client: 生成随机私钥 a,计算公钥 A = a * G,发送 A
Server: 生成随机私钥 b,计算公钥 B = b * G,发送 B
Client: 计算 shared_secret = a * B = a * (b * G) = ab * G
Server: 计算 shared_secret = b * A = b * (a * G) = ab * G
→ 双方得到相同的 shared_secret ✅
窃听者:知道 A 和 B,但无法计算 ab * G(ECDLP 困难)
四、证书体系:CA、CSR、X.509、证书链验证
TLS 握手中,服务器需要通过证书向客户端证明自己的身份。整个信任体系建立在 PKI(Public Key Infrastructure) 之上。
1. 证书的构成(X.509 v3)
X.509 证书主要字段:
├── 版本(Version): v3
├── 序列号(Serial Number): CA 分配的唯一编号
├── 签名算法(Signature Algorithm): sha256WithRSAEncryption / ecdsa-with-SHA256
├── 颁发者(Issuer): C=US, O=Let's Encrypt, CN=R3
├── 有效期(Validity): Not Before → Not After
├── 主体(Subject): CN=example.com
├── 主体公钥(Subject Public Key Info): RSA 2048 / ECDSA P-256
├── 扩展(Extensions):
│ ├── Subject Alternative Name (SAN): example.com, *.example.com
│ ├── Key Usage: Digital Signature, Key Encipherment
│ ├── Extended Key Usage: TLS Web Server Authentication
│ ├── Basic Constraints: CA:False
│ └── Authority Information Access: CA Issuers / OCSP URI
└── 签名(Signature): CA 用私钥对以上内容的签名
example.com 和 *.example.com。
2. 证书签发流程
1. 生成密钥对
openssl ecparam -genkey -name prime256v1 -out server.key
2. 生成 CSR(Certificate Signing Request)
openssl req -new -key server.key -out server.csr \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:*.example.com"
CSR 包含:公钥 + 主体信息 + 域名(SAN)+ 签名(用私钥签名证明拥有权)
3. 提交 CSR 给 CA
CA 验证域名所有权(DNS TXT 记录 / HTTP 文件验证 / 邮箱验证)
4. CA 签发证书
CA 用自己的私钥对 CSR 内容签名 → 生成 server.crt
5. 部署
server.key(私钥,保密) + server.crt(证书,公开)→ 配置到 Web 服务器
3. 证书链验证
客户端验证证书时,不是直接信任服务器证书,而是沿证书链逐级验证直到信任锚(Root CA):
证书链(从叶子到根):
┌─────────────────────────┐
│ End-Entity Certificate │ ← 服务器证书(example.com)
│ 签名者: R3 │
└────────────┬────────────┘
│ 验证签名
┌────────────▼────────────┐
│ Intermediate CA (R3) │ ← 中间 CA(Let's Encrypt R3)
│ 签名者: ISRG Root X1 │
└────────────┬────────────┘
│ 验证签名
┌────────────▼────────────┐
│ Root CA (ISRG Root X1) │ ← 根 CA(浏览器/OS 内置信任)
│ 自签名 │
└─────────────────────────┘
验证过程:
1. 用 R3 的公钥验证服务器证书的签名 → ✅ 确实由 R3 签发
2. 用 ISRG Root X1 的公钥验证 R3 的签名 → ✅ 确实由 ISRG 签发
3. ISRG Root X1 在浏览器信任库中 → ✅ 信任链完整
→ 证书有效 ✅
4. 证书吊销
| 机制 | 原理 | 优缺点 |
|---|---|---|
| CRL(Certificate Revocation List) | CA 发布已吊销证书的列表,客户端下载后检查 | ✅ 简单 ❌ 列表越来越大、更新延迟、实时性差 |
| OCSP(Online Certificate Status Protocol) | 客户端实时向 CA 查询证书状态 | ✅ 实时 ❌ 暴露用户访问的站点(隐私问题)、CA 可用性瓶颈 |
| OCSP Stapling | 服务器定期从 CA 获取 OCSP 响应,握手时附带给客户端 | ✅ 实时 + 隐私保护 + 减轻 CA 压力 ❌ 需要服务器配置 |
五、HTTPS 性能优化
1. OCSP Stapling
OCSP Stapling 让服务器代替客户端去查询证书吊销状态,并把 CA 签名的 OCSP 响应「钉」在 TLS 握手中:
无 OCSP Stapling:
Client → Server: TLS 握手
Client → CA OCSP: 这个证书还有效吗?(暴露用户隐私 + 额外延迟)
CA OCSP → Client: 有效
有 OCSP Stapling:
Server → CA OCSP: (定期查询,如每小时一次)
CA OCSP → Server: 有效(签名)
Client → Server: TLS 握手
Server → Client: 证书 + OCSP 响应( stapled )
→ 客户端直接验证 OCSP 响应签名,无需联系 CA ✅
2. HSTS(HTTP Strict Transport Security)
HSTS 告诉浏览器:这个域名只能用 HTTPS 访问,自动把 HTTP 请求升级为 HTTPS:
// HTTP 响应头
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
参数说明:
max-age=31536000 → 1 年内强制 HTTPS(秒)
includeSubDomains → 所有子域名也强制 HTTPS
preload → 申请加入浏览器的 HSTS 预加载列表
工作流程:
1. 首次访问 example.com → 浏览器收到 HSTS 头 → 记录
2. 后续访问 → 浏览器自动将 http:// 转为 https:// → 跳过 302 重定向
解决的问题:
❌ 无 HSTS: http://example.com → 302 → https://example.com(302 被劫持 = 中间人)
✅ 有 HSTS: http://example.com → 浏览器内部转为 https://(没有网络请求,无法劫持)
3. HTTP/2 + TLS
HTTP/2 的多路复用、头部压缩、服务器推送等特性与 TLS 协同工作,带来显著性能提升:
| 特性 | 效果 | 与 TLS 的关系 |
|---|---|---|
| 多路复用 | 一个 TCP 连接并行多个请求 | 减少 TLS 握手次数(1 个连接 = 1 次握手) |
| 头部压缩(HPACK) | 请求头体积缩小 80%+ | 加密传输下仍可压缩(TLS 1.3 早期数据更高效) |
| 服务器推送 | 主动推送资源,减少 RTT | 与 0-RTT 结合效果更佳 |
| ALPN 协商 | TLS 握手阶段协商应用协议 | 在 ClientHello 中声明支持 h2,避免额外协商 RTT |
HTTP/1.1 over TLS 1.2:
6 个并行 TCP 连接 → 6 次 TLS 握手 → 串行请求
总延迟 ≈ DNS + TCP × 6 + TLS × 6 + 请求
HTTP/2 over TLS 1.3:
1 个 TCP 连接 → 1 次 TLS 握手(1-RTT)→ 并行请求
总延迟 ≈ DNS + TCP × 1 + TLS × 1 + 请求
性能提升:首次页面加载减少 30-50% 延迟
六、常见攻击与防御
1. 中间人攻击(MITM)
攻击场景:
Client ←→ Attacker ←→ Server
攻击者拦截通信,分别与客户端和服务器建立独立连接
防御:TLS 证书验证
1. 服务器证书由可信 CA 签发 → 攻击者无法伪造有效证书
2. 客户端验证证书域名匹配 → 攻击者的证书域名不匹配
3. ECDHE 密钥交换 → 即使拿到公钥也无法推导共享密钥
攻击者能做什么?
├── 自签名证书 → 浏览器警告 ⚠️ → 用户可能忽略(社会工程学)
├── 伪造 CA 签名 → 计算上不可行(SHA-256 碰撞 + RSA/ECDSA 伪造)
└── 零日漏洞 → 理论可能,但 TLS 1.3 大幅减少了攻击面
2. 降级攻击(Downgrade Attack)
攻击者让客户端和服务器协商使用更低版本的 TLS 或更弱的密码套件:
TLS 1.2 的降级风险:
Client 支持 TLS 1.2, 1.1, 1.0
攻击者篡改 ClientHello:删除 TLS 1.2,只保留 TLS 1.0
Server 被迫使用 TLS 1.0 + 弱密码套件 → 攻击成功
TLS 1.3 的防御:降级保护机制
1. supported_versions 扩展:TLS 1.3 的版本号是 0x0304
攻击者无法将其修改为 0x0303(TLS 1.2),因为会改变握手哈希
2. 降级 sentinel:ServerHello.random 的最后 8 字节
如果服务器实际支持 TLS 1.3 但协商为 TLS 1.2:
random 后 8 字节 = 44 4F 57 4E 47 52 44 01("DOWNGRD\x01")
→ 客户端检测到 sentinel → 拒绝连接 ✅
3. TLS 1.3 不再支持版本协商回退
客户端指示支持 TLS 1.3 但收到 TLS 1.2 的 ServerHello → 必须中止
3. 证书伪造
| 攻击方式 | 原理 | 防御 |
|---|---|---|
| CA 被入侵 | 攻击者获取 CA 私钥,签发任意域名证书 | 证书透明度(CT)、CA 严格审计、HSM |
| 域名验证绕过 | 利用 DNS 劫持或 BGP 劫持骗过 CA 的域名验证 | CAA 记录、多因素验证 |
| SHA-1 碰撞 | 构造两个哈希相同的证书,一个合法一个伪造 | TLS 1.3 仅支持 SHA-256+,彻底移除 SHA-1 |
| WildCard 滥用 | 利用通配符证书覆盖子域名 | SAN 精确匹配、限制通配符使用范围 |
七、实战:Nginx HTTPS 配置最佳实践
完整配置
# /etc/nginx/nginx.conf 或站点配置
server {
listen 80;
server_name example.com www.example.com;
# HTTP → HTTPS 重定向
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2; # HTTP/2 + TLS
server_name example.com www.example.com;
# ─── 证书配置 ───
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ─── TLS 协议版本 ───
ssl_protocols TLSv1.2 TLSv1.3; # 兼容性考虑保留 1.2,优先 1.3
# ─── 密码套件 ───
# TLS 1.3 的套件由 OpenSSL 自动选择,无需手动指定
# 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
# 优先使用服务器的密码套件顺序
ssl_prefer_server_ciphers on;
# ─── 会话复用 ───
ssl_session_timeout 1d; # 会话票证有效期 1 天
ssl_session_cache shared:SSL:10m; # 会话缓存 10MB(约 4 万个会话)
ssl_session_tickets off; # 禁用 session ticket(PFS 考虑)
# ─── OCSP Stapling ───
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# ─── HSTS ───
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# ─── 其他安全头 ───
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# ─── DH 参数(仅 TLS 1.2 需要)───
# ssl_dhparam /etc/nginx/dhparam.pem; # 2048-bit 或更高
# ─── 0-RTT 配置(TLS 1.3)───
ssl_early_data on; # 启用 0-RTT(⚠️ 确保应用层处理重放风险)
# 0-RTT 请求标记(后端可据此判断是否为 early data)
proxy_set_header Early-Data $ssl_early_data;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
关键配置项解释
| 配置项 | 推荐值 | 说明 |
|---|---|---|
ssl_protocols |
TLSv1.2 TLSv1.3 |
保留 1.2 兼容老客户端,1.3 优先 |
ssl_ciphers |
仅 AEAD 套件 | TLS 1.3 自动选择;1.2 手动排除不安全套件 |
ssl_session_tickets |
off |
禁用 session ticket 确保前向安全性 |
ssl_stapling |
on |
启用 OCSP Stapling,提升性能+隐私 |
ssl_early_data |
on |
启用 0-RTT,后端需处理重放风险 |
HSTS max-age |
31536000(1年) |
足够长的强制 HTTPS 期限 |
配置验证
# 验证 Nginx 配置语法
nginx -t
# 检查 TLS 配置质量(在线工具)
# https://www.ssllabs.com/ssltest/
# 目标:A+ 评级
# 本地检查支持的协议和密码套件
openssl s_client -connect example.com:443 -tls1_3
# 检查 OCSP Stapling 是否生效
openssl s_client -connect example.com:443 -status -tls1_3 2>&1 | grep "OCSP response"
# 期望输出:OCSP response status: successful (0x0)
# 检查证书链
openssl s_client -connect example.com:443 -showcerts
# 检查 HSTS 头
curl -sI https://example.com | grep Strict-Transport-Security
八、速查表
| 场景 | 方案 | 关键点 |
|---|---|---|
| 选择 TLS 版本 | TLS 1.3 优先,保留 1.2 兼容 | 禁用 TLS 1.0/1.1/SSL 3.0 |
| 密码套件 | 仅 AEAD(GCM/ChaCha20) | 排除 CBC、RC4、3DES |
| 密钥交换 | ECDHE + X25519 | 强制前向安全性 |
| 证书类型 | ECDSA P-256 或 RSA 2048+ | ECDSA 更小更快,RSA 兼容性更好 |
| 证书签发 | Let's Encrypt(免费) | 自动续期 + ACME 协议 |
| 减少握手延迟 | 0-RTT + Session 缓存 | 0-RTT 仅限幂等操作 |
| 证书吊销检查 | OCSP Stapling | 隐私保护 + 减轻 CA 负载 |
| 防降级攻击 | TLS 1.3 降级 sentinel | 自动检测,无需额外配置 |
| 防中间人 | HSTS + Preload | 消除 HTTP→HTTPS 的劫持窗口 |
| 性能优化 | HTTP/2 + TLS 1.3 | 多路复用 + 1-RTT 握手 |
| Nginx 安全评级 | SSL Labs A+ | 配置参考第七节 |