抓包看 TLS 握手:SNI / ALPN / 证书 / Alert 一条线读出来

· 约 5 分钟 🦈 PCAP 抓包查看

抓包定位 HTTPS 故障是高级运维 / SRE 的基本功。Wireshark 树状视图覆盖全协议栈,但90% 的握手失败只用看 ClientHello + ServerHello + Alert 三条记录——本工具就是这条速览路径。

TLS 握手能告诉你什么

握手是双方在加密之前的”谈判”——版本、加密算法、密钥、身份证明都在这一段定下来。握手元数据全是明文(除了 ECH 的 inner SNI),所以不需要解密就能看:

  • SNI:客户端要访问的域名
  • 支持的 TLS 版本:1.0 / 1.1 / 1.2 / 1.3
  • 支持的密码套件:100+ 种组合
  • 支持的曲线:X25519 / secp256r1 / secp384r1 等
  • ALPN 协商:h2 / http/1.1 / h3
  • 证书链:服务器身份 + 中间 CA + 根 CA

任意一项谈不拢,握手失败。pcap 里能看到失败发生在哪一步因为哪个字段——这是排障的钥匙。

TLS 1.2 vs TLS 1.3:握手结构

TLS 1.2(2 RTT)
Client → Server: ClientHello                    [明文]
Server → Client: ServerHello, Certificate,
                 ServerKeyExchange, ServerHelloDone  [明文]
Client → Server: ClientKeyExchange,
                 ChangeCipherSpec, Finished     [部分加密]
Server → Client: ChangeCipherSpec, Finished     [加密]
Client → Server: HTTP GET / ...                 [加密]

TLS 1.3(1 RTT)
Client → Server: ClientHello (含 key_share)     [明文]
Server → Client: ServerHello, EncryptedExtensions,
                 Certificate, CertificateVerify,
                 Finished                       [前两段明文,后续加密]
Client → Server: Finished, HTTP GET / ...       [加密]

关键差异——TLS 1.3 把 Certificate 放进了加密部分,所以 pcap 看不到 1.3 的服务器证书内容(除非有 keylog)。1.2 的 Certificate 是明文,工具直接展示 SAN/Issuer/有效期。

五个高频 Alert 速查

抓包里看到 fatal Alert 就是握手失败,alert_description 字段告诉你原因:

Code名字常见根因
40handshake_failureTLS 版本 / cipher / 曲线没共同集
50decode_error客户端 / 服务器格式不兼容(罕见,多为协议实现 bug)
70protocol_versionTLS 版本被强制拒绝(如服务器禁用 TLS 1.0)
112unrecognized_nameSNI 不匹配任何 server_name 配置
120no_application_protocolALPN 没共同协议(h2/http/1.1 协商失败)

详细列表见 RFC 8446 §6

经典案例:SNI 拼错了

某后台调外部 API 失败,curl 报:

SSL certificate problem: certificate is not yet valid

抓包:

ClientHello SNI = "api.example.cn"
ServerHello selected cipher = TLS_AES_128_GCM_SHA256
Certificate SAN = ["api.example.com", "*.example.com"]

SAN 列表里没 .cn——客户端代码里 BaseURL 配错了 .com 写成 .cn,但 DNS 把 .cn 解析到了同一个 CDN IP。CDN 默认证书签的是 .com,于是 SNI 不匹配证书。

修这个 bug 不需要看一行代码——从 pcap 直接定位是 BaseURL 拼错。

经典案例:HTTP/2 协商失败

App 升级后部分用户报”加载慢一倍”,但服务端日志正常。抓包:

ClientHello alpn_protocols = ["h2", "http/1.1"]
ServerHello selected_protocol = "http/1.1"

服务器没选 h2。继续看证书 + 服务器配置——发现反代链路里 ELB 没开 ALPN passthrough,所以 ALPN 信息被剥掉,源站默认回 http/1.1。

修复:开 ELB 的 TLS passthrough,或者把 ALPN 协商在 ELB 层完成。

经典案例:TLS 1.3 only 卡死老客户端

升级 Nginx 配置 ssl_protocols TLSv1.3 后 30% Android 4.x 用户连不上。抓包看老客户端:

ClientHello supported_versions = ["TLS 1.0", "TLS 1.1", "TLS 1.2"]
Server → Alert (70 protocol_version) fatal

这些设备根本不发 supported_versions 扩展或不带 1.3——服务端拒绝握手。

修复:保留 TLS 1.2 + 1.3,禁用 1.0/1.1 即可,不要全禁 1.2。

用 pcap-viewer 看完整流程

打开 PCAP 抓包查看,拖入 .pcap 后:

  1. Filter 输入 tls 只看 TLS 报文
  2. 第一条 ClientHello → 看 SNI、versions、ciphers、alpn、groups
  3. 第二条 ServerHello → 看 selected_version、selected_cipher、selected_alpn
  4. 后续 Certificate → 看 SAN、Issuer、有效期(仅 TLS 1.2 / 之前明文可见)
  5. 失败案例查 Alert(红色高亮)→ 看 description code

整条流程不需要看 TCP 重组、不需要追流——握手就在最初几个包里,单包视图就够用

什么时候必须用 Wireshark

本工具够用的场景占抓包分析的 80%。剩下 20% 必须 Wireshark:

  • TCP 流重组:跨多包的大 HTTP body / 大证书链
  • 专家系统:自动标 retransmission / out-of-order / window full
  • TLS 解密:配 SSLKEYLOGFILE 后看明文 ApplicationData
  • 小众协议:BGP / OSPF / 802.11 Radiotap / Bluetooth
  • 大文件:> 200 MB 抓包先 tshark -Y 过滤再分析
  • 统计图表:I/O 图、流量分布、延迟分布

剩下”看 SNI / ALPN / 证书 / Alert / 简单 HTTP”——浏览器拖一下文件就能看的事,没必要装 Wireshark。

抓包命令速查

# macOS - 抓 443 端口流量到指定主机
sudo tcpdump -i en0 -w out.pcap 'host api.example.com and tcp port 443'

# Linux - 抓所有接口
sudo tcpdump -i any -w out.pcap 'tcp port 443'

# 只抓握手部分(节省体积)
sudo tcpdump -i en0 -w handshake.pcap 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2):1] = 0x16)'

# tshark 预过滤(脱敏 + 缩小)
tshark -r in.pcap -Y 'tls or dns' -w sanitized.pcap

# iOS(不越狱)
rvictl -s <udid>     # 创建虚拟接口
sudo tcpdump -i rvi0 -w ios.pcap 'tcp port 443'

避坑

  • 抓包前用 clear 清屏——再敲命令,便于对照时间戳
  • -s 0 抓全包——默认只截 96 字节够 IP/TCP 头但 TLS 握手会被截断
  • 复现请求前再开始抓——边抓边操作,避免无关流量混进来
  • Ctrl+C 后看一眼包数——xxx packets captured 太多说明过滤条件没生效

抓包不是黑魔法——TLS 握手协议设计就是让你能看见。装好 tcpdump,记住几个 Alert 编号,浏览器拖一下文件,HTTPS 故障定位从”翻日志四小时”变成”看 pcap 三分钟”。

❓ 常见问题

为什么 TLS 1.3 握手只有一个 RTT,1.2 要两个?

TLS 1.2 握手分两段——ClientHello / ServerHello 协商版本和 cipher,然后客户端发 ClientKeyExchange、双方各发 ChangeCipherSpec + Finished。两个 RTT 才能开始传业务数据。TLS 1.3 把这些合并——ClientHello 里直接带上 ECDHE 公钥(key_share 扩展),服务器 ServerHello 里也带,握手完毕后立即可以加密发业务请求。0-RTT 模式(PSK 续会话)甚至能在 ClientHello 里捎带应用数据,一次 RTT 都不用等——但有重放风险,业务侧需自己防。

SNI 是明文的,看 pcap 就能知道访客访问了哪些域名吗?

是的,TLS 1.3 之前 SNI 完全明文——这是 ISP 和企业防火墙能"看到访问了哪些域名"的根本原因。ECH(Encrypted Client Hello) 把 SNI 加密放进了 inner ClientHello,外层只露一个伪装域名(如 cloudflare-ech.com),但目前 ECH 仅 Cloudflare 全网部署 + Firefox/Chrome 部分支持,普通 HTTPS 站点 SNI 仍可见。所以 pcap 看到 ClientHello 的 SNI 字段就等于知道目标主机名——这是 80% 排障的入手点。

ALPN 协商失败会怎样?

ALPN(Application-Layer Protocol Negotiation)是 TLS 扩展,客户端在 ClientHello 里列出支持的协议(h2http/1.1h3),服务器在 ServerHello 选一个。如果服务器选了客户端不支持的——TLS 握手失败,发 no_application_protocol Alert(112)。常见坑——Nginx 升级 HTTP/2 配置错,客户端只支持 http/1.1 但 Nginx 强制 h2;或反代链路里有一段不支持 ALPN,HTTP/2 升级失败回退 1.1 但业务以为还在 h2。pcap 里看 ClientHello 的 alpn_protocols 列表 vs ServerHello 的 selected_protocol,一目了然。

看到 fatal Alert "handshake_failure" 是什么意思?

双方协商不出共同的密钥套件 / 版本 / 曲线。常见根因——(1) TLS 版本不兼容:客户端只支持 TLS 1.2,服务器配置 TLS 1.3 only,或反过来;(2) 没有共同 cipher suite:客户端只支持 ECDHE-RSA-AES128-GCM,服务器只配了 ChaCha20-Poly1305;(3) 没有共同曲线:TLS 1.3 客户端 key_share 给了 X25519,服务器只接受 secp256r1;(4) 客户端证书要求失败:mTLS 场景客户端没提供证书或证书不匹配。pcap 里看 ClientHello 的 cipher_suites + supported_versions + supported_groups,对比 ServerHello 选了什么——大概率能直接看出谁不让步。

unrecognized_name (112) 和 server_name_unknown 一样吗?

不完全一样。unrecognized_name Alert 在 TLS 1.2 RFC 里定义为"服务器不认识 SNI 指定的域名"——但语义上是 warning 级别,应当继续握手,只是部分实现错把它当 fatal 发。RFC 6066 明确——服务器收到不认识的 SNI 应当继续用默认证书完成握手,让客户端自己判断证书是否有效,或返回 fatal alert "unrecognized_name"。实战意义——这个 alert 出现 90% 是 Nginx / Apache 虚拟主机配置错,没找到匹配 SNI 的 server_name,要么默认证书没设置好,要么 SNI 拼错了(如多了 www. 或漏了 .cn)。

怎么把 pcap 转给前端同事看 / 给客户分析师看而又不泄露 Cookie?

用 tshark 预过滤 + 脱敏。原命令——tshark -r in.pcap -Y 'tls or dns' -w sanitized.pcap 只保留 TLS 握手和 DNS 查询,去除明文 HTTP(含 Cookie / Authorization)。再上传到本工具浏览器查看,零外网请求。敏感场景请记住——pcap 中的明文 HTTP 请求会原样保留 Cookie / Authorization 头部、POST body,等于一份完整的会话凭证;TLS SNI 暴露用户访问过哪些域名;DNS 查询同理。共享前先过滤再脱敏。

为什么 Wireshark 能解密 HTTPS 而本工具不行?

两个都不能"解密"——只有客户端导出密钥日志后才能。Wireshark 的解密功能依赖客户端启动时设置 SSLKEYLOGFILE 环境变量,把每个会话的 master_secret / handshake_secret 写到文件里,然后 Wireshark 读这个 keylog 文件用密钥推算明文。本工具 MVP 不接 keylog 路径——浏览器静态页面读取本地文件路径需要用户每次手动选,不太好做。要看 HTTPS 明文 Body 请用 mitmproxy / Charles 做 MITM,或导出 HAR(浏览器自己有明文版)。

🦈 打开 PCAP 抓包查看 拖入 .pcap/.pcapng→包列表/分层详情·TLS 握手 SNI/ALPN/cipher·HTTP 请求重组·DNS·过滤搜索·本地不上传