跳到主要内容

TLS握手与证书体系

1. 概念与原理

HTTPS 不等于"加了一层加密",它是一整套身份认证 + 密钥协商 + 加密通信的系统。TLS(Transport Layer Security)就是这套系统,前端必须懂的原因:

  • 部署 HTTPS 选证书、配 TLS 版本、调优握手性能
  • 排查 NET::ERR_CERT_* 系列报错
  • 配置 HSTS、CSP,做安全加固
  • 看懂 mTLS、双向认证(企业内网常见)

2. TLS 演进

版本年份状态
SSL 2.0/3.01995-1996全部废弃,有重大漏洞
TLS 1.0/1.11999-20062020 浏览器全面废弃
TLS 1.22008主流
TLS 1.32018推荐,性能和安全显著提升

生产配置基线:禁用 TLS 1.1 及以下,启用 1.2 + 1.3。

3. TLS 1.2 握手(经典 RSA / ECDHE)

3.1 完整握手流程

Client Server
| ClientHello |
| - TLS 版本 |
| - 随机数 ClientRandom |
| - 支持的 cipher suite 列表 |
| - SNI(要访问哪个域名) |
| - ALPN(h2/h3) |
|─────────────────────────────────────────> |
| |
| | ServerHello
| | - 选定的 cipher suite
| | - 随机数 ServerRandom
| <─────────────────────────────────────────|
| |
| Certificate |
| CertificateRequest(可选,mTLS) |
| ServerKeyExchange(ECDHE 公钥) |
| ServerHelloDone |
| <─────────────────────────────────────────|
| |
| 验证证书链 |
| ClientKeyExchange(自己的 ECDHE 公钥) |
| ChangeCipherSpec |
| Finished |
|─────────────────────────────────────────> |
| |
| | ChangeCipherSpec
| | Finished
| <─────────────────────────────────────────|
| |
| 开始加密通信 |

两次往返(2-RTT) 才能开始发应用数据。

3.2 密钥协商:从 RSA 到 ECDHE

老 RSA 密钥交换:客户端生成随机 pre-master secret,用服务端公钥加密发过去。致命缺陷:私钥泄露后,所有历史抓包都能解密。

ECDHE(椭圆曲线 Diffie-Hellman 临时密钥):每次握手生成临时密钥对,私钥泄露不影响历史会话。这叫前向安全(Forward Secrecy)

现代配置必须只用 ECDHE 系列 cipher suite,例如:

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256

3.3 SNI(Server Name Indication)

一个 IP 多个 HTTPS 站点的关键。客户端在 ClientHello 里告诉服务端要访问哪个域名,服务端据此返回对应证书。

SNI 是明文的(在 TLS 加密前),所以中间人能看到你访问哪个域名(虽然看不到内容)。Encrypted ClientHello(ECH)解决这个,2024 起逐步推开。

3.4 ALPN(应用层协议协商)

ClientHello 里带 h2http/1.1,服务端选一个。HTTP/2 over TLS 必须 ALPN。

4. TLS 1.3 握手(极简)

Client Server
| ClientHello |
| - 提前发 key share(猜服务端用什么算法) |
|─────────────────────────────────────────> |
| |
| | ServerHello + 加密扩展
| | + 证书 + 完成
| <─────────────────────────────────────────|
| |
| 验证 + Finished |
|─────────────────────────────────────────> |
| |
| 应用数据(1-RTT) |

1-RTT 完成握手。复用 session 可达 0-RTT:第一个数据包就带应用数据。

4.1 TLS 1.3 关键变化

  • 删除所有不安全 cipher(RSA 密钥交换、CBC、RC4、MD5、SHA-1)
  • 只用 ECDHE / DHE(强制前向安全)
  • AEAD 算法(AES-GCM、ChaCha20-Poly1305)
  • 加密更多握手内容(证书都加密了)
  • 0-RTT 复用

4.2 0-RTT 的代价

0-RTT 数据可被重放。GET 这种幂等请求可以,POST 必须拒绝 0-RTT。Nginx:

ssl_early_data on; # 开 0-RTT
# 但必须在应用层判断是否安全

应用要检测 $ssl_early_data 标识,拒绝非幂等请求。

5. 证书与 PKI

5.1 证书是什么

X.509 证书 = 公钥 + 主体信息 + CA 签名。CA(证书颁发机构)用自己的私钥签,浏览器内置的根 CA 公钥能验证签名。

5.2 证书链

根 CA(自签名,预装在浏览器/OS)
↓ 签发
中间 CA
↓ 签发
服务器证书(你的网站)

服务器必须返回完整证书链(除了根,根客户端有)。不返回中间证书是配置错误第一名,浏览器报 NET::ERR_CERT_AUTHORITY_INVALID

5.3 证书字段

openssl x509 -in cert.pem -noout -text

关键字段:

字段含义
Subject证书颁发给谁。CN = 主体名
Issuer颁发者(上级 CA)
Validity有效期(Let's Encrypt 90 天,付费证书 1 年)
Subject Alternative Name(SAN)多域名/通配符,现代浏览器只看这个,CN 已被忽略
Public Key公钥(RSA 2048/4096 或 ECDSA P-256)
SignatureCA 签名
Key Usage / Extended Key Usage用途(serverAuth、clientAuth)

5.4 证书类型

类型验证级别适合
DV(Domain Validation)验证域名所有权个人、中小企业(Let's Encrypt)
OV(Organization)验证组织真实性中大型企业
EV(Extended Validation)严格审核组织金融、电商(地址栏不再显示绿色公司名,意义削弱)
通配符(Wildcard)*.example.com多子域
多域名(SAN)一个证书多个域名SaaS

5.5 Let's Encrypt + certbot

免费自动签发,前端首选。

# 安装
apt install certbot python3-certbot-nginx

# 申请并自动配置 Nginx
sudo certbot --nginx -d example.com -d www.example.com

# 仅签发证书(手动配置)
sudo certbot certonly --webroot -w /var/www/html -d example.com

# 自动续期(90 天有效期,cron 已自动配置)
sudo certbot renew --dry-run

# 强制续期
sudo certbot renew --force-renewal

证书路径:/etc/letsencrypt/live/example.com/

fullchain.pem ← 服务器证书 + 中间证书(Nginx 用这个)
privkey.pem ← 私钥
cert.pem ← 只有服务器证书
chain.pem ← 只有中间证书

5.6 OCSP 与吊销

证书可能被吊销(私钥泄露)。客户端需要验证证书是否还有效:

  • CRL(Certificate Revocation List):CA 发布的吊销列表,太大不实用
  • OCSP(在线状态查询):每次访问问 CA,慢且暴露隐私
  • OCSP Stapling:服务器定期问 CA,把响应"钉"在 TLS 握手里发给客户端

启用 OCSP Stapling(Nginx):

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;

6. Nginx 生产 TLS 配置(基线)

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

# === 证书 ===
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

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

# === cipher suite(TLS 1.2 用,1.3 是固定的)===
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# === Session 复用 ===
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m; # 约 200000 个 session
ssl_session_tickets off; # 关 ticket(缺少 key rotation 会破坏 forward secrecy)

# === OCSP Stapling ===
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

# === HSTS ===
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# === 其他安全头 ===
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

# === HTTP 强制跳 HTTPS ===
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}

这套配置在 SSL Labs 能拿 A+。

7. HSTS 与 HSTS Preload

7.1 HSTS

Strict-Transport-Security 响应头告诉浏览器:以后这个域名只能 HTTPS。

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • max-age 秒数(生产建议 2 年)
  • includeSubDomains 含所有子域
  • preload 申请加入浏览器预加载列表

首次访问还是会走 HTTP,必须依赖浏览器先收到 HSTS 头。

7.2 HSTS Preload

提交到 hstspreload.org,浏览器源码内置该域名永远 HTTPS。单向门:上了 preload 列表想撤销极慢(几个月),所有子域必须永久支持 HTTPS。

申请前置条件:

  • 主域有效 HTTPS
  • HTTP 301 跳 HTTPS(不能 302)
  • 响应头包含 max-age >= 31536000; includeSubDomains; preload
  • 所有子域都支持 HTTPS

8. mTLS(双向 TLS)

普通 HTTPS 只验证服务端身份,mTLS 同时验证客户端身份。企业内部 API、服务间通信、银行级安全用。

server {
listen 443 ssl http2;
ssl_certificate ...;
ssl_certificate_key ...;

# === 客户端证书验证 ===
ssl_client_certificate /path/to/client-ca.crt; # 信任的客户端 CA
ssl_verify_client on; # 强制验证
ssl_verify_depth 2;

location / {
# 把客户端证书信息传给后端
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_pass http://backend;
}
}

K8s service mesh(Istio、Linkerd)默认 mTLS 服务间通信。

9. 性能优化

9.1 算法选择

  • ECC(ECDSA)证书 > RSA,密钥短性能高(ECDSA P-256 ≈ RSA 3072)
  • 优先 AEAD(AES-GCM、ChaCha20-Poly1305)
  • 移动端启用 ChaCha20-Poly1305(无 AES 硬件加速时更快)

9.2 Session 复用

  • Session ID:服务端缓存(多实例需共享缓存如 Redis)
  • Session Ticket:客户端持票,服务端只需 ticket key
  • TLS 1.3 PSK:统一机制

9.3 OCSP Stapling

省客户端一次 OCSP 查询,减少首次握手 100-300ms。

9.4 HTTP/2 + 0-RTT

HTTP/2 多路复用 + TLS 1.3 0-RTT,首屏可省 2-3 个 RTT。

9.5 测试工具

# 看 TLS 配置评分
# https://www.ssllabs.com/ssltest/ 最权威

# 命令行
nmap --script ssl-enum-ciphers -p 443 example.com

# 看证书
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text

# 看完整握手
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -tls1_2

# 测特定 cipher 支持
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

10. 故障排查

10.1 常见浏览器证书错误

错误原因
NET::ERR_CERT_AUTHORITY_INVALID证书链不完整 / 自签名 / CA 不被信任
NET::ERR_CERT_DATE_INVALID过期 / 客户端时间错
NET::ERR_CERT_COMMON_NAME_INVALID域名不匹配(看 SAN)
NET::ERR_CERT_REVOKED证书被吊销
NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHMSHA-1 等弱算法
ERR_SSL_VERSION_OR_CIPHER_MISMATCH客户端不支持服务端 cipher
ERR_SSL_PROTOCOL_ERROR协议错(中间设备劫持也常见)

10.2 证书链不完整排查

# 验证链
openssl s_client -connect example.com:443 -showcerts < /dev/null

# 看输出里有几个 Certificate,应该至少 2 个(叶子 + 中间)

# 在线工具
# https://www.ssllabs.com/ssltest/ 会指出 "Chain issues"

修复:Nginx 用 fullchain.pem 而非 cert.pem

10.3 SNI 不匹配

服务器多个 https,配置错了 SNI 路由不对,返回错证书。openssl s_client -servername example.com -connect 1.2.3.4:443 验证。

10.4 客户端时间偏差

证书有效期校验依赖客户端时间。手机/电脑时间偏差超出有效期就报 invalid date。生产服务器必须 NTP 同步:

timedatectl status
sudo timedatectl set-ntp true

10.5 TLS 握手慢

curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n" \
-o /dev/null -s https://example.com

TLS 时间高 = 证书链长、未开 OCSP Stapling、未开 session 复用、cipher 慢。

11. 安全考量

11.1 私钥保护

  • 私钥文件 chmod 600,所有者 root 或服务账户
  • 永远不要把私钥提交到 git
  • 私钥泄露立即吊销 + 重签
  • 用 KMS / Vault 集中管理

11.2 证书过期监控

最常见生产事故。监控方案:

  • 用 Prometheus blackbox_exporter 定期探测
  • Let's Encrypt 90 天,certbot 自动续期 + 监控续期日志
  • 关键域名加邮件告警

11.3 弱配置检查

定期跑 SSL Labs 测试,发现 B 及以下评分必须修。

11.4 CT 监控

Certificate Transparency 日志全公开,可监控有没有人冒签你的域名证书。用 crt.sh 查。

12. 常见反模式

  • 只配 cert.pem 不配中间证书:iOS / Android / 一些客户端报错(浏览器有 AIA 兜底所以经常没发现)
  • TLS 1.0/1.1 还开着:PCI DSS 不合规,安全审计扣分
  • ssl_prefer_server_ciphers on 但 cipher 顺序乱:拉低安全等级
  • HSTS preload 没准备好就申请:撤销极慢,所有子域被锁死必须 HTTPS
  • 自签证书生产用:所有用户警告,无法被 CA 信任
  • 私钥 chmod 644:同机器其他用户能读
  • 证书过期才知道:必须监控,提前 30 天告警
  • 0-RTT 开了不区分 method:重放攻击
  • OCSP Stapling 配了但 resolver 没设:Nginx 启动报警,stapling 实际不工作
  • 多域名共用同一证书没用 SAN:浏览器拒绝
  • 私钥放在容器镜像里:镜像泄露 = 私钥泄露

13. 延伸阅读