1. TLS握手协议的基本流程

大家好,我是老张。今天咱们聊聊TLS握手。

说到TLS握手,很多人第一反应是“加密通信前那个握手”。没错,但关键是要说清楚它到底是怎么握的。

我记得有一次线上事故,用户请求突然超时飙升。排查到最后,发现是新上的证书链配置错了,握手阶段卡在半路。从那以后,我对握手流程的理解就更深了。

1.1 核心目的:为什么需要握手?

说白了,客户端和服务端在开始加密通信之前,必须互相确认几件事:

  • 你是谁?——身份认证,防止中间人冒充
  • 用什么算法?——协商双方都支持的密码套件
  • 用什么密钥?——生成一个只有双方知道的对称密钥
  • 之前的信息被篡改过吗?——握手完整性校验

你想想看,如果这些没确认就加密,那加密就跟虚设一样。

1.2 经典握手流程(TLS 1.2 为主)

我习惯把TLS 1.2的完整握手分成四个阶段。这样面试官问起来,你也能讲得清楚。

阶段 消息 方向 关键内容
1 ClientHello C → S TLS版本、密码套件列表、随机数、Session ID
2 ServerHello + Certificate + ServerKeyExchange + CertificateRequest C ← S 选定的套件、服务器证书、DH参数、要求客户端证书(可选)
3 ClientKeyExchange + CertificateVerify + ChangeCipherSpec + Finished C → S 预主密钥、客户端证书验证、协商密钥、握手完整性hash
4 ChangeCipherSpec + Finished C ← S 服务端确认密钥生效,校验握手hash

嗯,这里要注意一个细节:ChangeCipherSpec 并不是TLS握手协议的一部分,它是独立的记录层消息。面试官经常用这个点来考你。

关键记忆点:完整的TLS 1.2握手需要2-RTT。从ClientHello到服务端Finished收到,客户端需要等一个来回(RTT)才能发送ClientKeyExchange,再等一个来回才能开始发应用数据。

1.3 每一步具体干了什么?

Step 1: ClientHello

客户端发个招呼过去,告诉服务端自己支持什么。我个人习惯在看抓包时快速扫三个字段:

  • Version:比如0x0303就是TLS 1.2,0x0304就是TLS 1.3
  • Cipher Suites:一堆套件名,服务端会从中挑一个
  • Random:32字节随机数,后面算密钥要用

Step 2: ServerHello + 证书 + 密钥交换参数

服务端回复,挑一个套件,发自己的证书。这里有个常见坑:证书链必须完整。

我曾经遇到一个客户,他们把中间证书漏了,只发了服务器证书。结果安卓设备全连不上,iOS倒是没问题(因为iOS会自动补全)。从那以后,我每次上线前都会用openssl s_client检查证书链。

如果是DHE或ECDHE密钥交换,服务端还会发ServerKeyExchange,里面携带了DH参数和签名。

Step 3: 客户端计算密钥 + 验证 + 发送Finished

客户端收到证书后,先验证证书链是否可信。然后根据密钥交换算法算出预主密钥(pre-master secret),再结合两个随机数算出主密钥(master secret)。

这里有个我经常用的调试技巧:

# 查看握手过程中的主密钥计算过程(TLS 1.2)
# 使用Wireshark的SSL/TLS解析器,在"Pre-Shared Key"部分能看到关键参数

# 用openssl模拟握手并打印密钥
openssl s_client -connect example.com:443 -tls1_2 -msg -debug 2>&1 | grep -E "(master|pre-master)"

算完密钥后,客户端发ChangeCipherSpec(告诉服务端要开始加密了),紧接着发Finished消息。Finished消息是对整个握手过程的hash签名,用协商好的对称密钥加密。

Step 4: 服务端确认 + 握手完成

服务端收到Finished后,自己算一遍hash,如果一致,就说明握手没有被人篡改。它也发ChangeCipherSpec和Finished回去。这时候,双方就可以开始加密通信了。

1.4 TLS 1.3 有什么不一样?

嗯,TLS 1.3把握手简化了不少。我做个对比:

对比项 TLS 1.2 TLS 1.3
RTT次数 2-RTT(完整握手) 1-RTT(完整握手)
密钥交换 多种(RSA、DH、ECDH) 只有(EC)DHE
密码套件数量 30+ 5个
ServerHello之后 发证书 + 发ServerKeyExchange 证书和参数合并发送
0-RTT 不支持 支持(但容易重放攻击)

实际项目中,我个人更推荐优先启用TLS 1.3。它少了1个RTT,对移动端体验提升很明显。

面试高频题:为什么TLS 1.3比1.2快?答案就两点:1. 减少了握手轮次(1-RTT vs 2-RTT);2. 简化了密码套件选择,服务端不需要等客户端发完所有信息才能决定。

1.5 完整握手流程图

下面我用一张图把整个过程串起来。面试官如果让你手画TLS握手流程,照着这个思路画就行。

TLS 1.2 完整握手流程图 客户端 (Client) 服务端 (Server) 1. ClientHello 2. ServerHello + Certificate + ServerKeyExchange 3. ClientKeyExchange + ChangeCipherSpec + Finished 4. ChangeCipherSpec + Finished 握手完成后,双方开始加密应用数据通信 总共 2 个完整 RTT,密钥由预主密钥 + ClientRandom + ServerRandom 推导 Certificate 阶段是身份认证的关键,务必保证证书链完整

1.6 容易踩的坑

说了这么多,我总结几个实战中经常遇到坑:

  • 证书链不完整:刚才讲过,中间证书不能漏。用 openssl s_client -connect 你的域名:443 -showcerts 检查。
  • 密码套件不匹配:服务端配置的套件客户端不支持,握手会失败。我建议服务端至少保留 ECDHE + AES-GCM + SHA384。
  • SNI 缺失:一台服务器托管多个域名时,客户端没发SNI,服务端不知道怎么选证书。现在主流的客户端都默认支持。
  • OCSP装订没打开:证书状态检查如果走在线查询,会额外增加握手延迟。开启OCSP Stapling能省掉这一步。

一句话总结面试重点:TLS 1.2握手是2-RTT,顺序是ClientHello→ServerHello+证书→密钥交换→Finished。TLS 1.3改成1-RTT并移除RSA密钥交换。面试官如果问你“握手过程”,按这个框架答,稳。