TLS 握手原理与抓包分析
TLS 握手原理与抓包分析
一个具体的场景
周六上午,你想给朋友发一封邮件,里面写了银行卡密码。邮件内容是明文 HTTP 传输,邮件服务器之间的链路被一个中间人劫持,对方用 Wireshark 打开抓包文件,你的密码赤裸裸地出现在 TCP 流里。
这是 1995 年前后互联网的常态。1994 年网景公司搞出了 SSL 1.0(从未公开),1995 年 SSL 2.0 随网景浏览器发布,1996 年 SSL 3.0 全面铺开。1999 年 IETF 把 SSL 标准化为 TLS 1.0(实质就是 SSL 3.1),2018 年 TLS 1.3 落地,今天你打开的每一个 https:// 都在跑 TLS 1.2 或 1.3。
TLS 解决的问题有三个,按重要性排序:
- 保密性:别人看不到你们聊什么
- 完整性:别人改不了你们聊的内容
- 真实性:你确定对面是你朋友,不是冒充的
这三个问题要同时解决,靠的不是一种算法,是四种工具的组合:非对称加密、对称加密、哈希、证书。握手过程就是让浏览器和服务器协商出接下来用的对称密钥,同时验证服务器身份。
第一个问题:怎么让对方知道秘钥又不被偷听
对称加密速度是 RSA 的 100-1000 倍,适合加密大流量数据流。但对称加密有个先天缺陷:加密方和解密方必须持有同一把密钥。如果你是浏览器,对方是一个你从没见过的电商网站,这把密钥怎么送过去?发到网络上会被偷,自己送过去又得物理跑一趟。
非对称加密是反过来的工具:每个用户有一对密钥,一把公开(公钥),一把私密(私钥)。公钥加密的东西只有私钥能解开,私钥签的东西只有公钥能验签。问题是运算速度慢,加密 1MB 数据要几秒,浏览器加载一个网页几 MB 几十 MB,慢得不可接受。
现实方案是混搭:用非对称加密安全地传递一把临时对称密钥,之后所有流量用这把对称密钥加密。这把临时密钥有个专业名字叫 pre-master secret。整个 TLS 握手的核心任务,就是让双方在不安全的网络上协商出这把 pre-master secret,而且不让中间人拿到。
第二个问题:怎么证明对方是真的对方
密钥协商好之后,浏览器要把数据加密发出去。问题来了:和它握手的到底是京东还是钓鱼网站?A 冒充京东的域名和 IP,浏览器照样能完成密钥协商,照样能加解密,照样能正常显示"京东商城"四个字——但你的用户名密码全到了 A 手里。
解决这个问题的工具叫数字证书。本质上,证书就是一张身份证,证书颁发机构(CA)拿着自己的私钥给网站的公钥"盖章":"我以 CA 的名义保证,这个公钥属于 www.jd.com。" 浏览器收到证书后,用 CA 的公钥验证签名,签名合法就说明证书是真的。
但这里有个鸡生蛋的问题:CA 的公钥是从哪来的?答:内置在操作系统和浏览器里。Windows、macOS、iOS、Android、Firefox 都维护一份 CA 信任列表,VeriSign、GeoTrust、Let's Encrypt 等知名 CA 的根证书预先装好。这就是"信任链"的起点——你必须无条件信任这些预装根证书。
TLS 1.2 的真实握手过程
以 https://www.example.com 为例,完整 TLS 1.2 握手涉及 4 个网络包,按时间顺序:
每一步都解决具体问题:
ClientHello 包含:客户端随机数(用于后续密钥派生)、支持的 TLS 版本、支持的加密套件列表(如 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)、支持的压缩算法、扩展字段(如 SNI 服务器名称指示)。
ServerHello + Certificate 包含:服务器选定的 TLS 版本和加密套件、服务器随机数、服务器证书链(从服务器证书一直到根 CA 证书)。
ClientKeyExchange 是核心:浏览器生成一个 pre-master secret,用服务器证书中的公钥加密后发给服务器。双方随后用三个随机数(客户端随机数 + 服务器随机数 + pre-master secret)一起派生出 4 把会话密钥:客户端写、服务器写、客户端 MAC、服务器 MAC(或者用 AEAD 模式合并)。
Finished 消息是握手过程所有内容的哈希,用新协商的密钥加密。如果对方能解出正确的 Finished,说明他真的持有 pre-master secret,密钥协商成功。
抓包实战:Wireshark 看 TLS 握手
打开 Wireshark,过滤器写 tls.handshake.type == 1,能过滤出所有 ClientHello 消息。点击一条,看详细字段:
- Handshake Type: Client Hello (1)
- Version: TLS 1.2 (0x0303)
- Random: 5c8f 9b... (32 字节客户端随机数)
- Cipher Suites (18 suites):列出浏览器支持的 18 个加密套件
- Extension: server_name (0):里面写着 "www.example.com"
过滤 tls.handshake.type == 11 看到 Certificate 消息,里面是整个证书链。点开 Certificate 项能看到 X.509 格式的证书详情:颁发者、有效期、公钥算法、签名算法。
过滤 tls.handshake.type == 16 看到 ClientKeyExchange。点开能看到用服务器公钥加密的 pre-master secret 密文——这部分是抓包者看不到明文的,因为私钥在服务器手里。
实战中最有用的过滤器:
| 过滤器 | 用途 |
|---|---|
tls.handshake.extensions_server_name == "www.example.com" | 查特定域名的握手 |
tls.alert.message_type == 21 | 抓到解密失败告警 |
tcp.stream eq 5 && tls | 看 TCP 流 5 里的所有 TLS 数据 |
tls.handshake.ciphersuite == 0xc02f | 抓用了 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 套件的连接 |
TLS 1.3 改了什么
TLS 1.2 握手需要 2 个 RTT(往返时间),如果客户端离服务器 200ms,每个 TLS 连接要 400ms 才能开始传数据。这对短连接的 API 请求影响很大。
TLS 1.3 把握手压到 1 个 RTT:
图注:TLS 1.3 把 RSA 密钥交换彻底移除,只保留 ECDHE(带前向保密),从 2-RTT 压到 1-RTT。
更进一步,TLS 1.3 还支持 0-RTT 握手:客户端上次连接过服务器,缓存了 session 密钥,下一次连接时 ClientHello 里直接带加密的早期数据,服务器收到就处理。但这引入了一个重放攻击风险:中间人截下 0-RTT 数据重发给服务器,服务器分不清是"用户重发"还是"攻击者重发"。所以 0-RTT 只能用于幂等请求(GET、不能改状态的请求)。
TLS 1.3 还把加密套件从 37 个砍到 5 个,禁止了所有不带前向保密(PFS)的算法。前向保密指的是:即使服务器的长期私钥未来某天泄露,之前的会话密钥也推不出来。这是用 ECDHE(椭圆曲线 Diffie-Hellman 临时密钥交换)而不是 RSA 密钥交换来保证的。
真实世界的握手失败案例
案例 1:证书过期
2017 年阿里云某个证书过期 1 小时,全国大量小网站打不开。错误信息是 NET::ERR_CERT_DATE_INVALID。Chrome 看到证书 Not Valid After 字段已过当前时间,直接拒绝握手,应用层拿不到任何数据。
案例 2:证书链不完整
服务器只发了服务器证书,没发中间 CA 证书。某些客户端(特别是移动端和老旧库)的 CA 信任库里没有服务器证书的根 CA,需要从服务器拿到中间 CA 才能拼接出完整信任链。配置 Nginx 时 ssl_certificate 必须包含中间证书,常见做法是 cat server.crt intermediate.crt > fullchain.crt。
案例 3:SNI 与虚拟主机冲突
一张服务器 IP 上部署多个 HTTPS 网站,每个网站用不同证书。客户端在 ClientHello 里通过 SNI 扩展告诉服务器"我要访问 www.a.com"。如果服务器配置错误用错了证书,浏览器会报 NET::ERR_CERT_COMMON_NAME_INVALID。
案例 4:客户端不支持 ECDHE
2015 年前某些国产老浏览器只支持 RSA 密钥交换。服务器为了兼容必须保留 RSA 套件。但 RSA 密钥交换没有前向保密,被 NSA 吹哨人斯诺登曝光后整个业界开始淘汰 RSA 套件。
思考题
- 如果客户端只信任系统内置的 100 个 CA 根证书,某天这 100 个 CA 中某个被攻陷,攻击者签发了假证书冒充你访问的网站,你有什么办法发现吗?(提示:证书透明性 CT 机制、HPKP 已废弃、CRL/OCSP 现状)
- 0-RTT 握手让延迟从 1 RTT 降到 0,为什么不能所有请求都用?(提示:重放攻击 + 业务幂等性)
- 假设你维护的电商网站每天有 100 万次 TLS 握手,每次握手涉及 4 次非对称运算,ECDHE 椭圆曲线运算需要 5ms。请估算握手阶段共消耗多少 CPU 核心资源?这个数字对架构设计有什么启示?(提示:会话复用、TLS 1.3、连接池、QUIC)
延伸阅读
- RFC 5246: TLS 1.2 协议
- RFC 8446: TLS 1.3 协议
- 霍尔兹曼《网络安全:公众世界中的秘密通信》
- 实际动手:用
openssl s_client -connect www.example.com:443 -msg看完整握手过程