当你访问一个 HTTPS 网站时,浏览器与服务端之间会发生一系列精密的"握手"操作——这就是 SSL/TLS 握手。整个过程在毫秒级完成,却完成了密钥协商、身份验证、加密算法确认,最终建立起一条安全的通信隧道。本文用图文并茂的方式,详细拆解 TLS 握手的每一个阶段。
👉 如果你想快速获取一张受浏览器信任的 SSL 证书,请访问 **EveryoneTrust SSL 证书购买与价格页**。
什么是 SSL/TLS 握手?
SSL/TLS 握手是 TLS 协议中客户端与服务端之间协商加密参数、建立安全连接的过程。它发生在 TCP 握手完成之后、数据加密传输之前。
握手的核心目标有四个:
- 协商加密算法——双方约定使用哪套加密套件
- 验证服务端身份——确认服务器不是伪造的
- 生成会话密钥——双方独立计算出相同的对称密钥
- 建立安全通道——后续所有数据均使用对称加密传输
握手本身是明文传输的(不包含密钥),但握手中的每一步都是为了确保后续加密通信的安全。
TLS 握手的完整流程
总体时序图
客户端 服务端
| |
| 1. ClientHello (支持的TLS版本、加密套件、随机数) |
| ──────────────────────────────────────────────────────> |
| |
| 2. ServerHello (选定TLS版本、加密套件、服务端随机数) |
| <────────────────────────────────────────────────────── |
| |
| 3. Certificate (服务端证书链) |
| <────────────────────────────────────────────────────── |
| |
| 4. ServerKeyExchange (ECDH 参数 / 签名) |
| <────────────────────────────────────────────────────── |
| |
| 5. CertificateRequest (可选:请求客户端证书) |
| <────────────────────────────────────────────────────── |
| |
| 6. ServerHelloDone (服务端 Hello 完成) |
| ──────────────────────────────────────────────────────> |
| |
| 7. Certificate (客户端证书,可选) |
| ──────────────────────────────────────────────────────> |
| |
| 8. ClientKeyExchange (ECDHE 参数 / 预主密钥) |
| ──────────────────────────────────────────────────────> |
| |
| 9. CertificateVerify (客户端签名,可选) |
| ──────────────────────────────────────────────────────> |
| |
| 10. ChangeCipherSpec (客户端:切换到加密模式) |
| ──────────────────────────────────────────────────────> |
| |
| 11. Finished (加密的握手消息摘要) |
| ──────────────────────────────────────────────────────> |
| |
| 12. ChangeCipherSpec (服务端:切换到加密模式) |
| <────────────────────────────────────────────────────── |
| |
| 13. Finished (加密的握手消息摘要) |
| <────────────────────────────────────────────────────── |
| |
| ========= 加密的 Application Data 通信 ========= |
| <────────────────────────────────────────────────────> |
阶段一:建立安全连接(ClientHello & ServerHello)
ClientHello — 客户端发起请求
客户端(浏览器)首先发送 ClientHello 消息,包含以下关键信息:
| 字段 | 说明 | 示例 |
|---|---|---|
| TLS 版本 | 客户端支持的最高 TLS 版本 | TLS 1.3(最新版) |
| 客户端随机数 | 32 字节随机数据,参与密钥计算 | Client_Random |
| Session ID | 复用会话时使用(避免完整握手) | abcd1234... |
| 加密套件列表 | 客户端支持的加密套件(按优先级排序) | TLS_AES_256_GCM_SHA384 |
| 压缩方法 | 支持的压缩方法(通常为空) | null |
| 扩展 | SNI、DNS 记录、ALPN、0-RTT 等 | server_name: example.com |
SNI(Server Name Indication) 是一个关键扩展。当同一个 IP 上托管多个 HTTPS 网站时,浏览器通过 SNI 告诉服务器要访问的具体域名,服务器才能返回正确的证书:
ClientHello
extension: server_name
server_name: example.com ← 浏览器告诉服务器要访问哪个网站
ServerHello — 服务端回应
服务端收到 ClientHello 后,选定最终使用的加密参数并回应:
| 字段 | 说明 |
|---|---|
| TLS 版本 | 双方协商一致的 TLS 版本 |
| 服务端随机数 | 32 字节随机数据(Server_Random) |
| Session ID | 复用会话时返回 |
| 选定的加密套件 | 从客户端列表中选出一个 |
| 选定的压缩方法 | 通常为 null |
阶段二:服务端身份验证(Certificate)
服务端证书链
服务端紧接着发送 Certificate 消息,包含证书链:
Certificate (证书链)
└── leaf.crt (服务端证书,绑定你的域名)
└── intermediate.crt (中间 CA 证书)
└── root.crt (根证书,浏览器内置信任)
浏览器会逐级验证证书链:
验证步骤:
1. 叶子证书 → 中间证书:用中间 CA 的公钥验证签名
2. 中间证书 → 根证书:用根 CA 的公钥验证签名(部分由浏览器直接内置信任中间 CA)
3. 检查证书是否过期(notBefore ~ notAfter)
4. 检查证书是否被吊销(CRL / OCSP)
5. 检查域名是否匹配(CN 或 SAN)
6. 验证证书链完整(无中间证书缺失)
为什么需要证书链? 根证书由 CA 离线保管,通过中间证书向下签发。这样即使中间证书泄露,也可以吊销而不影响根证书的安全性。
服务端密钥交换(ServerKeyExchange)
对于 ECDHE(前向保密)加密套件,服务端需要发送 ECDHE 公钥参数:
ServerKeyExchange
named_curve: secp256r1 ← 椭圆曲线
public_exchange: 服务端 ECDHE 公钥
signature: 用服务端私钥对 (Client_Random + Server_Random + ECDHE参数) 的签名
签名的作用是证明这些 ECDHE 参数确实来自真实的服务器,防止中间人篡改。
阶段三:客户端响应(ClientKeyExchange)
客户端密钥交换
对于 ECDHE 密钥交换,客户端也生成自己的 ECDHE 公钥:
ClientKeyExchange
named_curve: secp256r1 ← 必须与服务端一致
public_exchange: 客户端 ECDHE 公钥
双方独立计算得到相同的密钥
这是 TLS 握手中最精妙的部分——双方各自独立计算,数学上保证得出相同结果,且不会在网络上传输私钥:
密钥计算过程(ECDHE):
1. 双方各自生成 ECDHE 密钥对
- 客户端:(client_priv, client_pub)
- 服务端:(server_priv, server_pub)
2. 双方交换公钥(client_pub 和 server_pub)
3. 各自计算 ECDHE 共享密钥
- 客户端:client_priv × server_pub = 共享点 G × a × b
- 服务端:server_priv × client_pub = 共享点 G × b × a
数学上:a × G × b = b × G × a
因此两边计算出完全相同的共享密钥!
4. 由共享密钥派生出真正的会话密钥
pre_master_secret → master_secret → session keys
session keys = {
client_write_MAC_key, ← 客户端消息认证
server_write_MAC_key, ← 服务端消息认证
client_write_key, ← 客户端加密密钥
server_write_key, ← 服务端加密密钥
client_write_IV, ← 客户端初始化向量
server_write_IV ← 服务端初始化向量
}
为什么 ECDHE 能实现前向保密? 因为每次会话使用临时的密钥对,即使攻击者记录了所有加密流量并后来获取了服务器的长期私钥,也无法解密历史通信——因为会话密钥由临时的 ECDHE 密钥对决定,而不是由服务器的长期私钥直接派生。
阶段四:握手完成(ChangeCipherSpec & Finished)
ChangeCipherSpec — 切换到加密模式
双方各自发送 ChangeCipherSpec 消息,通知对方:"从现在起,后续消息将使用协商出的加密算法和密钥。"
Finished — 验证握手完整性
Finished 消息是第一条加密的握手消息,包含双方对整个握手过程的摘要:
Finished
verify_data = PRF(
master_secret,
"client finished" / "server finished",
Hash(所有握手消息)
)
双方计算对方发来的 Finished 消息,如果能正确解密并验证摘要,就证明:
- 握手没有被中间人篡改
- 双方持有相同的会话密钥
- TLS 连接已成功建立
⚠️ 注意:TLS 1.3 优化了握手流程(见下节),但
ChangeCipherSpec和Finished的作用逻辑不变。
TLS 1.3 对握手的重大优化
TLS 1.3(2018 年发布,RFC 8446)将握手从两轮往返(2-RTT)优化为一轮往返(1-RTT),并在安全性和性能上有大幅提升。
TLS 1.3 vs TLS 1.2 握手对比
| 对比维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手往返数 | 2-RTT | 1-RTT(初访)/ 0-RTT(重连) |
| 完整握手耗时 | ~100–300ms | ~50–100ms |
| 服务端证书传输 | 紧跟 ServerHello | 紧跟 ServerHello(不变) |
| ServerKeyExchange | 需要(RSA 密钥交换) | 不再需要单独的 ServerKeyExchange |
| RSA 密钥交换 | 支持(不支持前向保密) | 已移除(仅保留 ECDHE) |
| 前向保密 | 可选(ECDHE) | 强制要求 |
| 0-RTT 重连 | 不支持 | 支持(0-RTT) |
| 支持的加密套件 | 数百种 | 仅 5 种推荐套件 |
| 加密算法 | 大量遗留算法 | 仅保留 AEAD(AES-GCM, ChaCha20-Poly1305) |
| 密钥导出函数 | MD5/SHA-1/SHA-256 混用 | 仅使用 HKDF |
TLS 1.3 握手时序(1-RTT)
客户端 服务端
| |
| 1. ClientHello |
| + supported_versions(TLS 1.3) |
| + key_share(ECDHE 客户端公钥) ← 提前携带密钥参数 |
| + supported_groups(secp256r1...) |
| ──────────────────────────────────────────────────────> |
| |
| 2. ServerHello |
| + version(TLS 1.3) |
| + key_share(ECDHE 服务端公钥) ← 服务端直接响应密钥 |
| ─────────────────────────────────────────────────────── |
| |
| === 双方立即计算出相同的会话密钥 === |
| |
| 3. {EncryptedExtensions} (加密) |
| 4. {Certificate} (加密) |
| 5. {CertificateVerify} (加密) ← 用证书私钥签名 |
| 6. {Finished} (加密) ← 含握手摘要验证 |
| ──────────────────────────────────────────────────────> |
| |
| 7. {Finished} (加密) |
| <────────────────────────────────────────────────────── |
| |
| ========= 加密的 Application Data 通信 ========= |
| <────────────────────────────────────────────────────> |
TLS 1.3 的关键安全改进
1. 强制前向保密(PFS)
TLS 1.3 完全移除了不支持前向保密的 RSA 密钥交换和静态 DH 密钥交换,仅允许 ECDHE 和 DHE。这意味着即使攻击者获取了服务器的长期私钥,也无法解密历史通信记录。
2. 减少攻击面
TLS 1.3 仅保留以下 5 种推荐加密套件(cipher suites):
| 套件 | 说明 |
|---|---|
| TLS_AES_256_GCM_SHA384 | AES-256-GCM + SHA-384(最高安全) |
| TLS_CHACHA20_POLY1305_SHA256 | ChaCha20-Poly1305 + SHA-256(移动端优化) |
| TLS_AES_128_GCM_SHA256 | AES-128-GCM + SHA-256 |
| TLS_AES_128_CCM_SHA256 | AES-128-CCM + SHA-256(受限环境) |
| TLS_AES_128_CCM_8_SHA256 | AES-128-CCM-8 + SHA-256(受限环境) |
3. 0-RTT 数据
TLS 1.3 支持 0-RTT 模式,在首次连接时就发送应用数据(如 HTTP 请求),大幅降低延迟。但 0-RTT 存在重放攻击风险,适用于对安全性要求相对较低且可接受重放风险的场景(如 CDN 读取静态资源)。
握手过程中的常见安全问题
问题一:证书链不完整
如果服务端只配置了叶子证书,缺少中间 CA 证书,部分客户端(尤其是 Java 客户端)无法构建完整的证书链,导致握手失败。
错误日志:
javax.net.ssl.SSLHandshakeException:
PKIX path building failed: ... unable to find valid certification path to requested target
解决方案:在服务器配置中加载完整证书链(Nginx:ssl_certificate_chain)
问题二:SNI 与证书不匹配
当多个 HTTPS 站点共享同一 IP 时,SNI 尤为重要。某些旧版 TLS 库不支持 SNI,可能导致服务器返回默认证书,而该证书与请求的域名不匹配。
解决方案:升级 TLS 库版本,确保 SNI 支持
问题三:中间人攻击(MITM)
如果攻击者拦截了握手过程,替换了服务端证书,就会发生中间人攻击。攻击者持有自己的证书(由攻击者自己的 CA 签发),无法通过浏览器内置的受信任根证书库验证。
浏览器的防护机制:
- 验证证书链至受信任根 CA
- 检查证书吊销状态(OCSP / CRL)
- 检查证书透明度(Certificate Transparency)
问题四:TLS 版本降级攻击
攻击者可能诱导双方使用过时的 TLS 版本(如 TLS 1.0),绕过现代安全保护。
防护措施:服务器端禁用 TLS 1.0/1.1,仅启用 TLS 1.2 和 TLS 1.3:
ssl_protocols TLSv1.2 TLSv1.3;
会话恢复与会话 ID
完整握手的计算成本较高(尤其是 ECDHE 的椭圆曲线运算),TLS 支持会话恢复机制,避免重复握手。
会话 ID 机制(Session Resumption)
首次握手:
服务端生成 Session ID,随 ServerHello 返回
客户端缓存 Session ID 和 master_secret
重连时:
客户端在 ClientHello 中带上 Session ID
服务端查找并恢复会话,跳过完整握手
ClientHello (Session ID: "abc123")
ServerHello (Session ID: "abc123") + ChangeCipherSpec + Finished
握手完成!仅 1-RTT
TLS 1.3 PSK(预共享密钥)机制
TLS 1.3 将会话恢复升级为 PSK(Pre-Shared Key) 机制,允许双方在首次握手时就约定一个"会话票据"(ticket),后续凭此票据直接恢复会话:
0-RTT 重连(TLS 1.3):
客户端在 ClientHello 中直接携带加密的应用数据
服务端验证 PSK 后立即解密并响应
适合 CDN 等对延迟极度敏感的场景
HTTPS 连接的安全保障总结
SSL/TLS 握手通过以下机制保障连接安全:
| 安全保障 | 实现机制 |
|---|---|
| 身份验证 | 证书链验证 + CA 根证书信任 + 域名匹配 |
| 密钥安全 | ECDHE 密钥交换 + 前向保密(PFS) |
| 数据机密性 | AES-GCM / ChaCha20-Poly1305 对称加密 |
| 数据完整性 | AEAD(认证加密)同时提供加密和完整性校验 |
| 握手完整性 | Finished 消息摘要验证 |
| 防降级攻击 | 强制 TLS 1.2+,TLS 1.3 完全移除旧版本 |
如何检查自己网站的 TLS 握手
# 使用 OpenSSL 查看 TLS 握手详情
openssl s_client -connect example.com:443 -tls1_3 -debug
# 查看完整握手过程(包括证书链)
openssl s_client -connect example.com:443 -showcerts
# 测试 TLS 1.3 握手
openssl s_client -connect example.com:443 -tls1_3
# 使用 SSL Labs 在线检测(最全面)
# 访问 https://www.ssllabs.com/ssltest/
总结
SSL/TLS 握手是互联网安全的基础机制,其核心逻辑清晰而严密:
- ClientHello / ServerHello 协商加密参数
- Certificate 验证服务端身份(通过证书链)
- ECDHE 密钥交换 双方独立计算出相同的会话密钥
- Finished 验证握手完整性,确认双方持有相同的密钥
- 之后所有数据通过 AEAD(AES-GCM / ChaCha20-Poly1305)加密传输
TLS 1.3 通过强制前向保密、简化加密套件、减少握手往返,将这一过程提升到了一个新的安全与性能水准。升级到 TLS 1.3 是目前保障 HTTPS 安全的最佳实践。
👉 立即为你的网站部署 TLS 1.3 兼容的 SSL 证书:**EveryoneTrust SSL 证书购买入口**