3、SSL/TLS握手过程:握手阶段详解、加密套件协商、证书验证流程

说实话,搞了这么多年HTTPS排障,我最怕的就是有人说“握手慢”但不知道具体慢在哪一步。你想想看,一个完整的TLS握手少则几百毫秒,多则几秒,每个环节都可能出幺蛾子。今天我把自己踩过的坑和经验摊开来讲,咱们把握手全流程捋一遍。

核心定位:TLS握手是HTTPS安全通信的“敲门砖”。它完成三件事——身份认证、密钥协商、参数确认。任何一步失败,后面的加密传输都无从谈起。

3.1 握手阶段详解

标准握手(非PSK/0-RTT)大致分四轮交互。我用一个实际排查案例切入:有次客户反馈某APK连后台频繁超时,抓包一看——客户端发完ClientHello后服务器等了快2秒才回复ServerHello。原因?服务器DNS解析上游CDN节点花了太久。嗯,这里要提醒:Wireshark看握手时延,重点盯每个包之前的Delta时间。

下面是握手的核心流程(简化版,忽略密钥更新与会话恢复):

  1. ClientHello — 客户端发密码套件列表、TLS版本、随机数、Session ID、扩展(SNI、ALPN等)。
  2. ServerHello — 服务端选定套件、版本、随机数;同时发证书(如果要求双向认证还会发CertificateRequest)。
  3. Certificate — 服务端证书链,从叶子证书到根(不含根,除非特意发)。
  4. ServerKeyExchange — 仅DHE/ECDHE套件存在,携带公钥参数。我见过不少人忽略这条,导致密钥交换失败。
  5. ServerHelloDone — 服务端说“我发完了”。
  6. ClientKeyExchange — 客户端发premaster secret(RSA)或公钥(DHE),之后双方用随机数+premaster算出master secret。
  7. ChangeCipherSpec + Finished — 客户端告知后续用加密发Finished验证;服务端同样回复CCS+Finished。

别小看Finished这一步。我曾经遇到一个老版本OpenSSL的bug,服务端发完CCS后Finished直接飘丢了,客户端等超时重传,硬生生多耗了1个RTT。

阶段方向典型延迟来源我的经验
ClientHello → ServerHelloC→SDNS、7层负载均衡选套件曾见CN域名解析失败导致握手阻塞
CertificateS→C证书链过大、OCSP Stapling未开启一个包含6级中间证的链延迟增加120ms
ClientKeyExchange → CCSC→S非对称计算(RSA解密比ECDHE慢)RSA 4096位在低端IoT设备上耗时近300ms
Finished验证双向MAC校验、握手日志刷盘老版本Nginx线程模型导致Finished延迟

避坑指南:我曾经在客户环境抓到过客户端发完CCS后直接发了Application Data(未等服务端Finished),服务端直接告警。这不是标准行为,很多安全检测工具会报警。务必确保两边都走完Finished再传业务数据。

3.2 加密套件协商

套件协商说白了就是“双方选一个都会用的加密配方”。客户端在ClientHello里列出一堆,服务端在自己的支持列表里挑一个最安全的。我经常跟团队说,别把客户端列表写得太多——优先把现代套件放前面,否则服务端可能选到老旧的。

举个例子,某个兼容性方案中客户端把TLS_RSA_WITH_3DES_EDE_CBC_SHA放在第1位,服务端一看自己能支持,就直接选了3DES……结果CBC模式还容易被时序攻击。正确的做法是:ECDHE+AEAD套件放前面,比如TLS_AES_128_GCM_SHA256 (TLS 1.3) 或TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (TLS 1.2)。

协商过程还受TLS版本影响。TLS 1.3的握手交互次数减到1-RTT(甚至0-RTT),但它用SignatureScheme代替了以前的CipherSuite分组确认。我亲测过,同样网络条件下TLS 1.3比1.2快约30%。

类型典型套件安全性评级我个人习惯
现代AEADTLS_AES_128_GCM_SHA256高优先使用,支持PFS
传统CBCTLS_RSA_WITH_AES_128_CBC_SHA中低除非兼容遗留系统,否则禁用
废弃TLS_RSA_WITH_3DES_EDE_CBC_SHA极低一定要从列表移除
PFS关键所有以ECDHE开头的套件高检查是EHE还是RSA: ECDHE才提供前向安全

注意:很多旧版Java(JDK 6/7)默认禁用了GCM套件,导致协商降级到CBC。如果你用Java做服务端,务必检查jdk.tls.disabledAlgorithms配置。

3.3 证书验证流程

证书验证是很多工程师的盲区。我遇到过一个生产事故:客户端突然报证书错误,但浏览器访问正常。排查后发现是客户端没有携带中间证书,而服务端下发的链又缺少一级,导致客户端本地找不到信任锚定。验证流程说白了就是三关:合法性、有效性、域名匹配。

  • 第一步:构建证书链 — 客户端把服务端发来的证书链(叶子+中间)和本地受信任根证书列表比对。如果找不到匹配根,报错。这条我最常踩的坑是“证书链不完整”——很多运营商CDN会截断中间证书,而客户端不信任根。
  • 第二步:验证签名 — 从叶子到根逐级用上一级证书的公钥验签。实际中RSA签名验证比ECDSA慢不少。我推荐优先使用ECDSA证书+ECDHE密钥交换,验证速度快,还能减小证书体积。
  • 第三步:检查有效性 — 当前时间在证书有效期、是否被撤销(CRL/OCSP)、扩展密钥用途(Key Usage、Extended Key Usage)。这里有个坑:时间不对时握手直接失败。我见过客户机器NTP同步配置错误,时间偏差超过24小时,导致所有HTTPS请求报错。
  • 第四步:域名匹配 — 检查服务器主机名是否匹配证书的subjectAltName(SAN)。通配符仅生效于一个层级,比如“*.example.com”不能匹配“sub.sub.example.com”。

为什么浏览器访问正常但客户端调用失败? 因为浏览器有内置根证书库和中间证书缓存(如Firefox自有NSS库),而很多系统库(Java cacerts、Android根信任池)可能缺少某些中间根。我建议服务端始终开启OCSP Stapling,且下发完整的证书链(叶子证书+所有中间证书,不含根)。

我个人习惯用openssl s_client -connect host:443 -showcerts来快速检查服务端发来的链是否完整。如果看到verify error:num=20:unable to get local issuer certificate,那肯定漏了中间证书。

# 快速验证命令
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | grep -E "^Certificate chain|depth|verify"

# 输出示例
Certificate chain
 0 s:CN=example.com
   i:C=US, O=Amazon, CN=Amazon RSA 2048 M02
 1 s:C=US, O=Amazon, CN=Amazon RSA 2048 M02
   i:C=US, O=Starfield Technologies, CN=Starfield Services Root
 2 s:C=US, O=Starfield Technologies, CN=Starfield Services Root
   i:C=US, O=Starfield Technologies, CN=Starfield Root Certificate Authority - G2
--- 这里如果没有第3级说明链不完整

警惕:部分CDN或WAF在透传证书时可能会替换自己的证书。这在某些内网架构中很常见。如果你的业务需要验证端到端完整证书链,记得在白名单里排除中间代理的证书。

TLS 1.2 完整握手流程图 客户端 (C) 服务端 (S) ClientHello ServerHello Certificate ServerHelloDone ClientKeyExchange CCS + Finished CCS + Finished ✅ 握手完成,开始加密通信

图上省略了ServerKeyExchange和CertificateRequest,实际中很常见。如果你碰到支持ECDHE的服务,记得检查ServerKeyExchange里携带的椭圆曲线参数(支持哪几条曲线)。我曾经遇到过服务端只允许secp256r1,而客户端列表只有X25519,导致协商失败。

检查证书链小技巧:用 openssl x509 -text -noout -in cert.pem | grep -A1 "Subject Key Identifier" 看SKI,再用 openssl x509 -text -noout -in ca.pem | grep "Authority Key Identifier" 确认上一级证书的AKI是否匹配。我在排障时经常这么干。

好了,到这里握手里面的三个核心环节——阶段、套件、证书验证——就掰扯清楚了。每一个环节的延迟和错误都可能让HTTPS变成“HTTDNS”。下回遇到握手慢,别先怀疑网络,先按这个流程定位一下到底是哪个阶段在拖后腿。