第4章 HTTPS协议与TLS/SSL握手:证书链验证、密码套件协商、抓包分析
说实话,很多测试同学做了好几年接口测试,对HTTPS的理解还停留在「比HTTP多了个S,更安全」这种层面。但一到抓包、证书验证、协议层面的问题,就卡住了。我刚开始做加密接口测试时也一样,直到有一次线上环境因为证书链不全导致握手失败,排查了整整一下午……从那以后,我下定决心把TLS/SSL握手这件事彻底啃透。
这一章,我们就从底层协议讲起。你不需要成为密码学专家,但必须搞懂握手过程中每一步在干什么、怎么验证、怎么排查。我会结合我自己踩过的坑和常用工具,带你把HTTPS这层窗户纸捅破。
4.1 为什么需要HTTPS?核心差异
HTTP是明文传输,相当于你在大街上用明信片写信,谁都能看。HTTPS在HTTP外面套了一层TLS(传输层安全协议),数据加密后传输。
| 对比项 | HTTP | HTTPS |
|---|---|---|
| 传输层 | TCP | TCP + TLS |
| 默认端口 | 80 | 443 |
| 数据加密 | 否 | 是(对称+非对称) |
| 身份验证 | 无 | 证书链验证 |
| 完整性校验 | 无 | MAC校验 |
我在测试一个金融项目时,对方要求所有接口必须走HTTPS,且禁止降级到HTTP。当时我们用的抓包工具是Charles,但默认只支持HTTP,需要额外安装根证书才能解密HTTPS流量。这一点,很多刚接触HTTPS测试的同学会忘——抓包HTTPS的前提是你必须信任抓包工具的CA证书。
4.2 TLS/SSL握手完整流程
TLS握手说白了就是客户端和服务端先互相打个招呼,确认身份,然后商量好一套加密算法和密钥,最后开始加密通信。我习惯把握手分成四个阶段:Hello阶段、证书验证阶段、密钥交换阶段、加密确认阶段。
核心要点:握手过程中所有消息都是明文传输的,只有协商出主密钥(Pre-Master Secret)之后,后面的数据才加密。所以前期的消息不能被篡改,否则握手会失败。
下面这张图把四个阶段的交互画清楚了。我建议你保存下来,遇到握手问题时对着图排查。
嗯,这里要注意:上图展示的是TLS 1.2的完整握手。TLS 1.3做了很多优化,将握手从2-RTT压缩到1-RTT,但基本原理是一样的。我个人建议测试时优先压测TLS 1.2,因为大部分旧系统还在用这个版本。
4.3 密码套件协商
密码套件(Cipher Suite)就是一组算法的组合,告诉双方用什么方式加密、用什么算法做密钥交换、用什么消息认证码。客户端在ClientHello中会发送自己支持的套件列表,服务端选择其中一套。
举个例子,我抓过一个生产环境的包,看到的套件是:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
拆解一下:
- ECDHE — 密钥交换算法(椭圆曲线Diffie-Hellman临时密钥,前向安全)
- RSA — 证书身份验证使用RSA签名
- AES_128_GCM — 对称加密算法(128位AES,GCM模式)
- SHA256 — HMAC哈希算法(用于消息完整性)
避坑指南:我曾经在一次安全评审中被问到“你们接口用的密码套件支不支持前向安全?”。这个前向安全指的是:即使服务端私钥泄露,之前加密的会话也无法被解密。ECDHE就支持,而RSA密钥交换不支持。所以现在主流都推荐用ECDHE系列,千万别再用RSA做密钥交换了。
常见的密码套件对比:
| 套件ID | 密钥交换 | 认证 | 加密 | MAC | 前向安全 |
|---|---|---|---|---|---|
| 0x009E | ECDHE | RSA | AES_128_GCM | SHA256 | ✓ |
| 0x003C | RSA | RSA | AES_128_CBC | SHA256 | ✗ |
| 0x002F | RSA | RSA | AES_128_CBC | SHA1 | ✗ |
| 0xC01A | DHE | RSA | AES_256_GCM | SHA384 | ✓ |
测试时一定要确认服务端不支持弱套件(比如IDEA、DES、RC4等),否则容易被攻击利用。我习惯用sslscan或nmap --script ssl-enum-ciphers来扫描服务端支持的套件列表。
4.4 证书链验证
这个环节是测试中最容易出问题的。证书链说白了就是:浏览器/客户端怎么相信你给我的那个证书是合法的?它不会直接相信服务器颁发的证书,而是从根证书一路向下验证。
结构大概是:
根证书(自签名,预装操作系统中)
↓ 签名
中间证书(由根CA签发)
↓ 签名
服务器证书(由中间CA签发)
我经历过一次线上事故:合作方只提供了服务器证书,没有上传中间证书。结果安卓客户端直接报“证书链验证失败”,而iOS因为缓存了中间证书却能正常工作……你想想看,这种平台差异多么坑人。
重要:服务端必须把中间证书和服务器证书一起发送(除了根证书,因为客户端本地已经有)。很多测试人员只检查了服务器证书是否过期,忽略了中间证书。我曾经在压测环境中发现中间证书过期了,但没人注意到,直到证书链验证全部失败,生产级别的事故。
验证证书链的命令(OpenSSL):
# 查看服务器证书链
openssl s_client -connect example.com:443 -showcerts
# 输出中会显示:
# Certificate chain
# 0 s:CN = example.com ← 服务器证书
# 1 s:CN = R3 ← 中间证书
# 2 s:CN = ISRG Root X1 ← 根证书(通常不显示)
4.5 抓包分析实战
纸上谈兵终觉浅。我们来看看真实抓包时怎么看握手细节。我用Wireshark举例(TShark也行)。
抓包前记得设置环境变量SSLKEYLOGFILE(适用于Chrome/Firefox/curl等),这样Wireshark就能解密HTTPS流量,直接看到HTTP请求体。
我的习惯:先过滤tls.handshake.type == 1找到ClientHello,然后追踪TCP流看整个握手包。重点关注三个字段:
- Server Hello → Cipher Suite:实际协商出来的套件
- Certificate → 证书携带的 Subject 和 Issuer:确认证书链是否完整
- Change Cipher Spec → Finished:握手是否成功完成
下面是一个TLS 1.2握手的抓包示例(假设你已经开启SSLKEYLOGFILE):
# 使用 tshark 过滤握手信息
tshark -r handshake.pcap -Y "tls.handshake" -T fields \
-e tls.handshake.type \
-e tls.handshake.ciphersuite \
-e tls.handshake.extensions_supported_version
输出大约是这样:
1 0xc02f 0x0303 ← ClientHello(套件列表,TLS 1.2)
2 0xc02f 0x0303 ← ServerHello(选择了ECDHE_RSA_WITH_AES_128_GCM_SHA256)
11 ← Certificate(服务端发证书)
12 ← ServerKeyExchange(ECDHE参数)
14 ← ServerHelloDone
16 ← ClientKeyExchange
20 ← ChangeCipherSpec
21 ← EncryptedHandshakeMessage(Finished)
如果看到Alert (Level: Fatal, Description: Certificate Unknown),那就是证书链验证失败了。这时候去查服务端有没有把中间证书带上,或者本地根证书是否过期。
提示:很多测试人员以为抓包工具(如Charles/Fiddler)能自动处理证书问题,但当你把抓包工具代理关掉,直接让被测应用直连服务器时,证书问题就暴露了。我建议测试环境至少留一台设备不装抓包工具的CA证书,以便验证真实证书链。