---
title: TLS 1.2 握手
url: https://doc.liz6.com/networking/05-tls-and-pki/01-tls-1-2-handshake
locale: zh
area: networking
tags:
- networking
- TLS与PKI
date: 2026-06-30
modified: 2026-07-11
description: TLS 1.2 的握手:ClientHello→ServerHello→Certificate→KeyExchange→Finished,两次往返完成密钥协商。理解每步的密码学操作(RSA vs ECDHE、签名 vs 密钥交换),是理解 TLS 1.3 为什么能减到 1-RTT 的前提。
---

# TLS 1.2 握手

> TLS 1.2 的握手:ClientHello→ServerHello→Certificate→KeyExchange→Finished,两次往返完成密钥协商。理解每步的密码学操作(RSA vs ECDHE、签名 vs 密钥交换),是理解 TLS 1.3 为什么能减到 1-RTT 的前提。

## 概述

TLS（Transport Layer Security）是互联网安全通信的基础，2008 年由 RFC 5246 定义。它解决了三个问题：身份认证（server 是谁）、机密性（数据被加密）和完整性（数据未被篡改）。TLS 1.2 握手是最广泛部署的版本——2-RTT 建立安全通道，使用 ECDHE 保证前向安全，SNI 使多域名共享一个 IP。本文逐帧解析握手过程、密钥派生和会话恢复机制。

## Full Handshake (2-RTT)

```mermaid
sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: ═══ RTT 1 ═══
    C->>S: ① ClientHello<br/>version + random + cipher_suites<br/>+ extensions (SNI, ALPN, supported_groups)

    S->>C: ② ServerHello + Certificate*<br/>+ ServerKeyExchange*<br/>+ CertificateRequest*<br/>+ ServerHelloDone

    Note over C: 验证证书链<br/>计算 pre_master_secret

    Note over C,S: ═══ RTT 2 ═══
    C->>S: ③ Certificate*<br/>ClientKeyExchange<br/>CertificateVerify*<br/>[ChangeCipherSpec]<br/>Finished

    Note over S: 解密 pre_master_secret<br/>派生 session keys

    S->>C: ④ [ChangeCipherSpec]<br/>Finished

    Note over C,S: ✅ 安全通道建立<br/>应用数据加密传输

    C->>S: Application Data (encrypted)
    S->>C: Application Data (encrypted)
```

\* = optional (取决于 cipher suite 和 server 配置)

### ClientHello

```
struct {
    ProtocolVersion client_version;     // {3,3} = TLS 1.2
    Random random;                      // 32B: gmt_unix_time(4) + random_bytes(28)
    SessionID session_id;               // ≤32B (for resumption) or empty
    CipherSuite cipher_suites<2..2^16-2>;  // 客户端支持的列表, 优先级排序
    CompressionMethod compression_methods; // {0} = null (TLS 1.2 强制 null)
    Extension extensions<0..2^16-1>;    // SNI, ALPN, supported_groups, signature_algorithms, ...
}
```

关键 Extensions:

```
server_name (SNI): 要访问的域名 — server 选正确证书 (一个 IP 上多证书)
supported_groups: 客户端支持的 EC curves (secp256r1, x25519, ...)
ec_point_formats: uncompressed (唯一值, 实际无用)
signature_algorithms: 客户端支持的签名算法 (RSA-PKCS1-SHA256, ECDSA-SHA256, ...)
ALPN: 应用层协议列表 ("h2", "http/1.1") — server 选一个
Extended Master Secret: 防止 Triple Handshake 攻击
SessionTicket TLS: 支持 session ticket (无状态 resumption)
```

### Cipher Suite 格式

<svg viewBox="0 0 720 350" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Cipher Suite 命名拆解:密钥交换、认证、加密、PRF 四段参数,两个典型套件对比">
  <rect width="720" height="350" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Cipher Suite 拆解:套件名 = 四段密码学参数</text>

  <rect x="40" y="50" width="300" height="28" rx="6" fill="#4f46e5"/>
  <text x="190" y="68" text-anchor="middle" font-size="10.5" font-weight="700" fill="#ffffff">TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</text>

  <line x1="46" y1="78" x2="46" y2="240" stroke="#c7d2fe" stroke-width="1.5"/>
  <line x1="46" y1="108" x2="52" y2="108" stroke="#c7d2fe" stroke-width="1.5"/>
  <line x1="46" y1="152" x2="52" y2="152" stroke="#c7d2fe" stroke-width="1.5"/>
  <line x1="46" y1="196" x2="52" y2="196" stroke="#c7d2fe" stroke-width="1.5"/>
  <line x1="46" y1="240" x2="52" y2="240" stroke="#c7d2fe" stroke-width="1.5"/>

  <rect x="52" y="90" width="276" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="104" font-size="12" font-weight="700" fill="#3730a3">ECDHE <tspan font-weight="400" fill="#4f46e5">密钥交换算法</tspan></text>
  <text x="66" y="119" font-size="10.5" fill="#4f46e5">Elliptic Curve Diffie-Hellman Ephemeral</text>

  <rect x="52" y="134" width="276" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="148" font-size="12" font-weight="700" fill="#3730a3">RSA <tspan font-weight="400" fill="#4f46e5">服务器认证</tspan></text>
  <text x="66" y="163" font-size="10.5" fill="#4f46e5">证书中的公钥类型 — RSA signing</text>

  <rect x="52" y="178" width="276" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="192" font-size="12" font-weight="700" fill="#3730a3">AES_128_GCM <tspan font-weight="400" fill="#4f46e5">对称加密</tspan></text>
  <text x="66" y="207" font-size="10.5" fill="#4f46e5">AES-128-GCM AEAD</text>

  <rect x="52" y="222" width="276" height="36" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="236" font-size="12" font-weight="700" fill="#3730a3">SHA256 <tspan font-weight="400" fill="#4f46e5">PRF</tspan></text>
  <text x="66" y="251" font-size="10.5" fill="#4f46e5">Pseudo-Random Function,密钥派生</text>

  <rect x="380" y="50" width="300" height="28" rx="6" fill="#0d9488"/>
  <text x="530" y="68" text-anchor="middle" font-size="9.5" font-weight="700" fill="#ffffff">TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256</text>

  <line x1="386" y1="78" x2="386" y2="240" stroke="#99f6e4" stroke-width="1.5"/>
  <line x1="386" y1="108" x2="392" y2="108" stroke="#99f6e4" stroke-width="1.5"/>
  <line x1="386" y1="152" x2="392" y2="152" stroke="#99f6e4" stroke-width="1.5"/>
  <line x1="386" y1="196" x2="392" y2="196" stroke="#99f6e4" stroke-width="1.5"/>
  <line x1="386" y1="240" x2="392" y2="240" stroke="#99f6e4" stroke-width="1.5"/>

  <rect x="392" y="90" width="276" height="36" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="113" font-size="12" font-weight="700" fill="#115e59">ECDHE <tspan font-weight="400" fill="#0f766e">密钥交换</tspan></text>

  <rect x="392" y="134" width="276" height="36" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="148" font-size="12" font-weight="700" fill="#115e59">ECDSA <tspan font-weight="400" fill="#0f766e">服务器认证</tspan></text>
  <text x="406" y="163" font-size="10.5" fill="#0f766e">EC 证书</text>

  <rect x="392" y="178" width="276" height="36" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="192" font-size="12" font-weight="700" fill="#115e59">CHACHA20_POLY1305 <tspan font-weight="400" fill="#0f766e">对称加密</tspan></text>
  <text x="406" y="207" font-size="10.5" fill="#0f766e">ChaCha20-Poly1305 AEAD</text>

  <rect x="392" y="222" width="276" height="36" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="245" font-size="12" font-weight="700" fill="#115e59">SHA256 <tspan font-weight="400" fill="#0f766e">PRF</tspan></text>

  <rect x="60" y="276" width="600" height="56" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="298" font-size="12.5" fill="#3730a3">套件名的四段可独立组合:密钥交换算法定前向安全性,认证算法定证书类型,</text>
  <text x="76" y="318" font-size="12.5" fill="#3730a3">对称加密定数据加密方式,PRF(TLS 1.2 固定用 SHA256)负责密钥派生。</text>
</svg>

### 密钥派生

```
1. 从 DH key exchange: pre_master_secret (服务器/客户端共同计算)
2. master_secret = PRF(pre_master_secret, "master secret",
                        ClientHello.random + ServerHello.random)[0..47]

3. 从 master_secret: 6 个 session key
   key_block = PRF(master_secret, "key expansion",
                    ServerHello.random + ClientHello.random)
   → client_write_MAC_key, server_write_MAC_key,
     client_write_key, server_write_key,
     client_write_IV, server_write_IV

4. Finished message = PRF(master_secret, "client finished",
                           hash(all handshake messages))[0..11]
   → 保证 handshake 未被篡改
```

### Session Resumption

```
Session ID (有状态, RFC 5246):
  1. 首次 full handshake → ServerHello.session_id = <random ID>
  2. Server 缓存: session_id → master_secret + cipher_suite
  3. 重连: ClientHello.session_id = <cached ID>
     → Server 找到 session → 跳过 Certificate + ServerKeyExchange
     → 1-RTT

Session Ticket (无状态, RFC 5077):
  1. Server: 把 (master_secret + cipher_suite + ticket_lifetime)
     加密 (用 server 的 ticket key) → 发给 client → SessionTicket extension
  2. 重连: ClientHello.session_ticket = <encrypted blob>
     → Server 解密 ticket → 恢复 session → 1-RTT
     → Server 不需要存 session state (cipher from ticket)

  安全: ticket key 必须保密 + 定期轮换 (ticket 泄露 → 历史 session 泄露)
```

## 参考

- **RFC**: 5246, 6066, 7301, 5077, 7627 (Extended Master Secret)
- **工具**: `openssl s_client -connect host:443 -tls1_2`, Wireshark `ssl.handshake`

*Keywords: TLS 1.2, ECDHE, cipher suite, session resumption, session ticket, SNI, ALPN, PRF*
