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握手流程,照着这个思路画就行。
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密钥交换。面试官如果问你“握手过程”,按这个框架答,稳。