第二章:TLS 协议核心机制
如果说 HTTPS 是房子的门锁,那 TLS 协议就是锁芯里的那套精密结构。你得弄清楚它怎么转的,才能判断锁有没有坏。这一章我把 TLS 最关键的五个核心机制拆开揉碎讲清楚——加密套件、证书认证、密钥交换、会话复用、前向安全性。
2.1 加密套件:协议能力的“配置文件”
加密套件其实就是一串字符串,指定了 TLS 会话要用哪些算法。我见过很多配置问题,根源就在于套件选错了或者顺序不对。
一个典型的套件长这样:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384。它包含四部分:
- 密钥交换算法:比如 ECDHE、DHE、RSA(前两个能提供前向安全性,RSA 不行)
- 证书认证算法:比如 RSA、ECDSA
- 对称加密算法+模式+密钥长度:比如 AES_256_GCM
- 消息认证码(MAC)或 AEAD 的哈希算法:比如 SHA384
重点:配置时优先使用 AEAD 模式(GCM、CCM)的套件,避免 CBC 模式。CBC 在旧版 TLS 里容易出 padding oracle 攻击,我早年修复过一个就是因为套件顺序里 CBC 排在了 GCM 前面。
| 套件名称 | 前向安全 | AEAD | 性能 | 推荐度 |
|---|---|---|---|---|
| TLS_AES_128_GCM_SHA256 (TLS 1.3) | 是 | 是 | 高 | ★★★★★ |
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | 是 | 是 | 高 | ★★★★★ |
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA | 是 | 否 | 中 | ★★☆☆☆ |
| TLS_RSA_WITH_AES_128_GCM_SHA256 | 否 | 是 | 高 | ★☆☆☆☆ |
套件顺序也很关键。服务器通常会选客户端列表里的第一个它支持的。所以我建议把最安全的放前面,比如 TLS_AES_256_GCM_SHA384,然后是 TLS_CHACHA20_POLY1305_SHA256。像 TLS_RSA_WITH_3DES_EDE_CBC_SHA 这种就该直接关掉,别留。
2.2 证书认证:身份的“身份证”
证书的作用是告诉客户端:我是我,不是假冒的。整个过程依赖 PKI(公钥基础设施)。
- 服务器证书:包含域名、公钥、颁发者、有效期、签名。客户端会用内置的 CA 根证书去验证签名链。
- 中间 CA 证书:很多时候服务器证书不是直接由根 CA 签的,中间有一到两层。很多人部署时漏掉了中间证书,导致手机浏览器报错——我遇到过不止一次。
- 证书链验证:客户端从服务器证书开始,逐级向上验证签名,直到信任的根 CA。任何一个环节的签名不匹配或证书过期,都会握手失败。
注意:证书的 Subject Alternative Name(SAN)字段要包含正确的域名。Common Name(CN)在 RFC 2818 之后已经不建议作为域名匹配依据了。我曾经排查过一个诡异的问题:某客户配置的 CN 是对的,但 Chrome 就是不认,最后发现是浏览器强制要求 SAN。
自签名证书可以用于测试和内部网络,但生产环境必须用受信任的 CA 签发的证书。还有个细节:证书私钥要保护好,权限设为 600,别让 web 用户读到。
2.3 密钥交换:商量一个只有你我知道的秘密
密钥交换的目的是让客户端和服务端在不安全的网络里共同算出一个对称密钥。经典的 Diffie-Hellman(DH)系列是主流。
- RSA 密钥交换:客户端生成一个随机数,用服务器公钥加密后发过去。服务器用私钥解密。缺点是没有前向安全性——私钥泄露,之前的所有会话都可被解密。
- DHE(基于 DH 的临时密钥交换):双方各自生成临时 DH 参数,交换后计算出共享密钥。每次会话的临时密钥都不同,所以前向安全。
- ECDHE(基于椭圆曲线的 DHE):使用椭圆曲线上的点乘运算,性能和安全性都更好。现在基本是标配了。
我个人的习惯是只使用 ECDHE,禁用纯 RSA 密钥交换。在 OpenSSL 里可以通过 SSL_CTX_set_cipher_list 或配置文件的 CipherString 来强制。例如:
# 强制只使用 ECDHE 套件
CipherString = ECDHE+AESGCM:ECDHE+CHACHA20
你想想看,如果服务器配置里开着 TLS_RSA_WITH_...,那攻击者只要拿到私钥,历史流量全都能解密。所以前向安全性是个硬要求。
2.4 会话复用:别每次都重新握手
每次完整握手开销很大——证书传输、签名验证、DH 计算都要时间。会话复用就是为了减少这个成本。
- Session ID 复用(TLS 1.2 及之前):服务器在第一次握手后生成一个 Session ID,缓存状态。客户端下次连接时带上这个 ID,如果服务器还缓存着,就能跳过密钥交换步骤。但服务器要维护大量状态,扩展性差。
- Session Ticket 复用(RFC 5077):服务器把状态加密后作为一个 ticket 发给客户端。客户端在后续连接中带上这个 ticket,服务器解密后恢复会话。状态在客户端,服务器无需存储。更灵活,但也要求 ticket 密钥定期更换。
- TLS 1.3 PSK(预共享密钥):支持 0-RTT 模式。客户端可以带着预共享密钥直接发应用数据,但要注意重放攻击。我建议对非幂等请求禁用 0-RTT。
实践建议:开启 Session Ticket 并设置 ticket 密钥的自动轮换周期(比如 24 小时)。在 nginx 里用 ssl_session_tickets on 和 ssl_session_ticket_key 配置。密钥定期换,别用默认硬编码。
2.5 前向安全性:私钥泄露也不怕
前向安全性(PFS)指的是:即使服务器长期私钥泄露,攻击者也无法解密之前记录的流量。因为每次会话的对称密钥是通过临时 DH 参数算出来的,和长期私钥没有关系。
说白了,没有 PFS 的 TLS 就像用一个万能钥匙开所有门。一旦万能钥匙被偷,所有房间的历史记录都能翻出来。有 PFS 就是每扇门单独配电子密码,每次开完密码就变。
实现 PFS 的关键就是使用 DHE 或 ECDHE 作为密钥交换算法。在 TLS 1.3 里所有密钥交换都是 ephemeral 的,所以天然具备 PFS。但在 TLS 1.2 及之前版本,你必须在配置里显式指定 ECDHE 或 DHE 套件,并禁用 RSA 密钥交换。
检查方法:用 openssl s_client -connect example.com:443 -cipher 'ECDHE' 测试是否能成功握手。如果失败,说明服务器没有启用 ECDHE 套件。
我曾经帮一个金融客户做渗透测试,发现他们的 TLS 配置里优先使用 RSA 密钥交换。我说“这不行,前向安全性是零”。他们调整了套件顺序,把 ECDHE 提到最前面。后来再次扫描就全绿了。
嗯,到这儿你应该对 TLS 的核心机制有个整体印象了。每个环节都有自己的配置陷阱,后面的章节会针对每一点深挖合规性验证的具体方法。我们先消化好这个底层框架。