TLS协议中的密码套件(Cipher Suite)详解

夜深人静时,泪痕轻抚旧时光,一纸素笺难寄相思长

Posted by yishuifengxiao on 2026-08-15

密码套件的定义与作用

密码套件是 TLS 连接的”算法配方”——它是一个由 IANA 官方编号(如 0xC02B)注册的标识符,完整定义了 TLS 连接中使用的四类密码学算法的组合。

在 TLS 握手阶段,客户端在 ClientHello 中发出一组候选密码套件,服务器在 ServerHello选中一个。一旦选定,整个 TLS 会话的:

  • 密钥交换方式
  • 服务器身份认证方式
  • 应用数据加密方式
  • 密钥派生函数

全部由这个密码套件决定,会话中不可更改。


密码套件名称的四段式结构

以使用的 ECDHE-ECDSA-AES128-GCM-SHA256 (0xC02B) 为例,名称从左到右分为四段:

ECDHE — ECDSA — AES128 — GCM — SHA256
│ │ │ │ │
│ │ │ │ └─ ④ 哈希函数
│ │ │ └──────── ③ 加密模式
│ │ └───────────────── ③ 加密算法+密钥长度
│ └──────────────────────────── ② 签名算法
└───────────────────────────────────── ① 密钥交换算法
  1. ECDHE 是一次性的:密钥交换只发生在握手阶段一次,后续数据传输不再使用
  2. ECDSA 是验证性的:仅用于验证服务器身份,不参与后续数据加密
  3. SHA256 是持续性的:从握手到会话结束,贯穿密钥派生和完整性校验(Finish时计算sha256得到verify_data)
  4. AES128-GCM 是全程性的:ChangeCipherSpec 之后,所有应用数据的加解密全程使用(用于对TLS 记录层的载体部分加密/解密)
时间轴 ──────────────────────────────────────────────────────────────────►

握手阶段 (明文传输) | 应用数据阶段 (加密传输)
───────────────────────────────────────┼─────────────────────────────────
|
ClientHello [ECDHE] |
├── 0xC02B 密码套件声明 |
├── secp256r1 曲线声明 |
└── ECDSA+SHA256 签名声明 |
│ |
▼ |
ServerHello [ECDHE] |
├── 0xC02B 被选中 |
└── secp256r1 被选中 |
│ |
▼ |
Certificate [ECDSA] |
└── ECDSA 公钥嵌入证书 |
│ |
▼ |
ServerKeyExchange [ECDSA] |
├── secp256r1 服务器公钥 [ECDHE] |
└── ECDSA 签名验证 [SHA256] |
│ |
▼ |
ServerHelloDone |
│ |
▼ |
ClientKeyExchange [ECDHE] |
└── 客户端 ECDHE 公钥 |
│ |
▼ |
┌─ 密钥派生 ─────────────────────┐ |
│ shared_secret = ECDHE 计算 │ |
│ master_secret = PRF[SHA256] │ |
│ key_block = PRF[SHA256] │ |
│ → 4 个密钥/IV 派生完成 │ |
└────────────────────────────────┘ |
│ |
▼ |
ChangeCipherSpec |
(激活新密钥) |
│ |
▼ |
Finished [AES128-GCM] |
· hs_hash = SHA256[...] [SHA256] |
· verifyData = PRF[...] [SHA256] |
· 密文 = AES128-GCM(...) |
│ |
▼ │
══════════════════════════════════════╬══════════════════════════════════
后续所有消息 [AES128-GCM] ← 全程使用 AES-128-GCM 加解密
(HTTP 请求/响应) [SHA256] ← PRF/序列号等仍依赖 SHA-256

密钥交换算法:ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)

项目 说明
作用 协商会话密钥(预主密钥 pre-master secret),不直接传输密钥
安全属性 提供前向保密(Forward Secrecy):即使服务器长期私钥泄露,历史会话仍安全
分类 属于 DHE 家族(临时 DH),对比 DH-RSA(无前向保密)
对比 RSA(无前向保密,服务器直接用 RSA 加密 pre-master secret 发回)、DHE(基于有限域,非椭圆曲线)

代码体现

// generateECDHEKeyPair 根据指定曲线生成 ECDHE 密钥对
// 客户端生成临时密钥对用于 ECDHE 密钥交换
// 公钥发送给服务器,私钥用于计算共享密钥
// 这保证了前向保密(Forward Secrecy):即使服务器私钥泄露,过去的会话仍安全
func generateECDHEKeyPair(curve uint16) (*ecdh.PrivateKey, *ecdh.PublicKey, error) {
switch curve {
case CURVE_X25519:
// 使用 X25519 曲线生成密钥对
// X25519 提供约 128 位安全强度,性能优秀
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, priv.PublicKey(), nil
case CURVE_SECP256R1:
// 使用 secp256r1 (P-256) 曲线生成密钥对
// P-256 提供约 128 位安全强度,广泛支持
priv, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, priv.PublicKey(), nil
default:
return nil, nil, fmt.Errorf("unsupported curve: 0x%04x", curve)
}
}
// computeSharedSecretECDH 根据指定曲线计算 ECDHE 共享密钥
// 客户端使用自己的私钥和服务器的公钥计算共享密钥
// 返回的共享密钥即为 TLS 中的预主密钥(pre_master_secret)
// 支持 X25519 和 secp256r1 (P-256) 曲线
func computeSharedSecretECDH(curve uint16, priv *ecdh.PrivateKey, serverPubBytes []byte) ([]byte, error) {
var serverPub *ecdh.PublicKey
var err error

switch curve {
case CURVE_X25519:
// 将服务器公钥字节解析为 X25519 公钥对象
serverPub, err = ecdh.X25519().NewPublicKey(serverPubBytes)
if err != nil {
return nil, fmt.Errorf("parse server X25519 pub: %v", err)
}
case CURVE_SECP256R1:
// 将服务器公钥字节解析为 P-256 公钥对象
// P-256 公钥格式:0x04 || X(32字节) || Y(32字节) = 65 字节(未压缩)
serverPub, err = ecdh.P256().NewPublicKey(serverPubBytes)
if err != nil {
return nil, fmt.Errorf("parse server P-256 pub: %v", err)
}
default:
return nil, fmt.Errorf("unsupported curve for shared secret: 0x%04x", curve)
}

// 执行 ECDH 运算:shared_secret = client_private * server_public
secret, err := priv.ECDH(serverPub)
if err != nil {
return nil, fmt.Errorf("ECDH compute: %v", err)
}
return secret, nil
}

签名算法:ECDSA(Elliptic Curve Digital Signature Algorithm)

项目 说明
作用 服务器在 ServerKeyExchange 中对密钥交换参数签名,证明”我持有对应证书的私钥”
对比 RSA(用 RSA 私钥签名)、DSA(已淘汰)
与证书的关系 服务器证书必须包含匹配类型的公钥(ECDSA 证书 ↔ ECDSA 签名)
注意 ECDSAECDH:ECDSA 用于签名验证身份,ECDHE 用于密钥协商。两者用同一条椭圆曲线但功能不同

加密算法+密钥长度+加密模式:AES128-GCM

这一部分实际包含两个子字段:

加密算法+密钥长度AES128

  • AES(Advanced Encryption Standard)
  • 密钥长度:128 位 = 16 字节
  • 对比 AES256(256 位 = 32 字节):安全强度更高,但性能略差

加密模式GCM(Galois/Counter Mode)

  • AEAD 模式(Authenticated Encryption with Associated Data)
  • 同时提供机密性(加密)和完整性(认证标签)
  • 对比:CBC(仅机密性,需额外 HMAC 做完整性校验,已被淘汰)
  • 对比:CCM(另一种 AEAD,需要计数器模式 + CBC-MAC,性能略差)
项目 说明
作用 加密和解密 TLS 应用数据(HTTP 请求/响应)
密钥长度影响 直接决定派生密钥的长度 → keyLen = 16(AES-128)或 keyLen = 32(AES-256)

// encryptGCM 使用 AES-GCM 对数据进行加密
// TLS 1.2 GCM 模式加密(RFC 5288):
//
// nonce = implicit_IV(4字节,来自密钥派生) || explicit_nonce(8字节,序列号)
// AAD = 序列号(8字节) || 内容类型(1字节) || 版本(2字节) || 明文长度(2字节)
// 输出 = explicit_nonce || 密文 || 认证标签(16字节)
//
// GCM 提供认证加密(AEAD),同时保证机密性和完整性
func encryptGCM(data, writeKey, iv []byte, seqNum uint64, contentType byte) []byte {
// 保存明文长度
plainLen := len(data)

// 构造 12 字节 GCM nonce
// 前 4 字节为隐式 IV(来自密钥派生),后 8 字节为显式 nonce(使用序列号)
nonce := make([]byte, 12)
copy(nonce[0:4], iv)
binary.BigEndian.PutUint64(nonce[4:12], seqNum)

// 构造附加认证数据(AAD),用于 GCM 的完整性校验
// AAD 格式:sequence_number(8) || content_type(1) || protocol_version(2) || record_length(2)
aad := make([]byte, 0, 15)
seqBytes := make([]byte, 8)
binary.BigEndian.PutUint64(seqBytes, seqNum)
aad = append(aad, seqBytes...)
aad = append(aad, contentType)
aad = append(aad, 0x03, 0x03) // TLS 1.2
aad = append(aad, byte(plainLen>>8), byte(plainLen))

// 创建 AES 密码块
block, err := aes.NewCipher(writeKey)
if err != nil {
panic(err)
}
// 创建 GCM 加密器
aesgcm, err := cipher.NewGCM(block)
if err != nil {
panic(err)
}

// 执行 GCM 加密:输出 = 密文 || 16字节认证标签
ciphertext := aesgcm.Seal(nil, nonce, data, aad)

// 构造最终 TLS GCM 记录:explicit_nonce(8字节) || 密文+标签
nonceExplicit := make([]byte, 8)
binary.BigEndian.PutUint64(nonceExplicit, seqNum)
result := append(nonceExplicit, ciphertext...)
return result
}

// decryptGCM 使用 AES-GCM 对数据进行解密
// 输入格式:explicit_nonce(8字节) || 密文 || 认证标签(16字节)
// 从记录中提取 explicit nonce,与隐式 IV 合并成完整 nonce
// 并使用相同的 AAD 进行完整性验证
// 如果认证失败,返回错误表示数据已被篡改
func decryptGCM(ciphertext, writeKey, iv []byte, seqNum uint64, contentType byte) ([]byte, error) {
// 检查最小长度:8字节 nonce + 16字节 GCM 标签
if len(ciphertext) < 8+16 {
return nil, fmt.Errorf("ciphertext too short for nonce+tag")
}

// 从记录中提取 explicit nonce(前 8 字节)
explicitNonce := ciphertext[:8]
// 实际密文部分
actualCiphertext := ciphertext[8:]
// 计算明文长度 = 密文长度 - 16字节认证标签
plainLen := len(actualCiphertext) - 16

// 构造完整的 12 字节 nonce = implicit_IV(4) || explicit_nonce(8)
nonce := make([]byte, 12)
copy(nonce[0:4], iv)
copy(nonce[4:12], explicitNonce)

// 构造与加密端相同的 AAD
aad := make([]byte, 0, 15)
seqBytes := make([]byte, 8)
binary.BigEndian.PutUint64(seqBytes, seqNum)
aad = append(aad, seqBytes...)
aad = append(aad, contentType)
aad = append(aad, 0x03, 0x03) // TLS 1.2
aad = append(aad, byte(plainLen>>8), byte(plainLen))

// 创建 AES 密码块
block, err := aes.NewCipher(writeKey)
if err != nil {
return nil, err
}
// 创建 GCM 解密器
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

// 执行 GCM 解密和完整性验证
// 如果 AAD 或密文被篡改,Open 会返回错误
plaintext, err := aesgcm.Open(nil, nonce, actualCiphertext, aad)
if err != nil {
return nil, fmt.Errorf("GCM decrypt/verify failed: %v", err)
}
return plaintext, nil
}

哈希函数:SHA256

项目 说明
作用 1 TLS PRF(伪随机函数)的底层哈希 → 派生主密钥和会话密钥
作用 2 Finished 消息中对所有握手消息的完整性哈希
作用 3 扩展主密钥(Extended Master Secret)的哈希运算
对比 SHA384(输出更长,安全强度更高,与 AES-256 配对)

常见密码套件对比

密码套件 编号 密钥交换 签名 加密 哈希 备注
ECDHE-ECDSA-AES128-GCM-SHA256 0xC02B ECDHE ECDSA AES-128-GCM SHA-256 推荐:现代主流
ECDHE-ECDSA-AES256-GCM-SHA384 0xC02C ECDHE ECDSA AES-256-GCM SHA-384 高强度
ECDHE-RSA-AES128-GCM-SHA256 0xC02F ECDHE RSA AES-128-GCM SHA-256 兼容 RSA 证书
ECDHE-RSA-AES256-GCM-SHA384 0xC030 ECDHE RSA AES-256-GCM SHA-384 兼容 RSA 证书 + 高强度
AES128-GCM-SHA256(无 ECDHE) 0x9C RSA RSA AES-128-GCM SHA-256 无前向保密
ECDHE-ECDSA-AES128-SHA 0xC009 ECDHE ECDSA AES-128-CBC SHA-256 已过时:CBC + HMAC

全强度对齐原则

密码套件的选择遵循安全强度对齐原则——各组件的安全强度应处于同一等级:

安全等级 椭圆曲线 AES 哈希 典型套件
~128 位 secp256r1 / X25519 AES-128 SHA-256 0xC02B
~192 位 secp384r1 AES-192 SHA-384 0xC02D
~256 位 secp521r1 AES-256 SHA-512 0xC02E

0xC02B + secp256r1 正是 128 位安全等级的完美对齐组合,是当前 TLS 1.2 中使用最广泛、安全与性能平衡最优的现代密码套件。

为什么选择 ECDHE-ECDSA-AES128-GCM-SHA256 + secp256r1

安全强度匹配

这是一组安全强度对齐的经典搭配:

组件 安全强度 说明
secp256r1 (P-256) ~128 位 基于椭圆曲线的对数难题,256 位参数 ≈ 128 位安全
AES-128-GCM 128 位 对称加密密钥长度 128 位
SHA-256 256 位 哈希输出 256 位
ECDSA ~128 位 基于 secp256r1 曲线的签名

如果选择了 secp256r1(128 位安全)+ AES-256(256 位安全),就是安全强度不对称——椭圆曲线才是瓶颈,AES-256 的额外强度是浪费。反之,如果选择 secp384r1(192 位安全)+ AES-128(128 位),就成了加密算法的瓶颈。

兼容性与现代性

  • secp256r1 (NIST P-256):FIPS 标准、广泛支持、crypto/ecdh 原生支持
  • AES-128-GCM:当前主流的认证加密算法,性能优秀
  • ECDHE:提供前向保密(Forward Secrecy)
  • ECDSA 证书:现代网站越来越普遍采用

对比其他可能的组合

组合 问题
X25519 + 0xC02B 0xC02B 与 X25519 通常不配对,传统上 secp256r1 配 ECDSA 证书
secp384r1 + 0xC02B 安全强度不匹配,且 P-384 性能较差
secp256r1 + ECDHE-RSA 套件 服务器用 RSA 证书,与 ECDSA 曲线不匹配

密码套件 (Cipher Suite) 与椭圆曲线的关系

密码套件是一个”算法组合包”

ECDHE-ECDSA-AES128-GCM-SHA256 (0xC02B) 是一个 TLS 密码套件,它规定了 TLS 连接使用的四个核心算法:

套件组件 含义 代码中的体现
ECDHE 密钥交换算法:使用椭圆曲线 Diffie-Hellman 临时密钥交换 skxCurve 决定具体曲线
ECDSA 服务器证书签名算法:服务器用 ECDSA 验证身份 从证书公钥类型自动判断
AES128-GCM 会话数据加密算法 encryptGCM / decryptGCM
SHA256 密钥派生和消息认证的哈希算法 tls_PRF_SHA256

椭圆曲线是密钥交换的”具体实现参数”

密码套件只说了”用 ECDHE 做密钥交换”,但没有说”在 哪条曲线 上做 ECDHE”。椭圆曲线(如 secp256r1、X25519)就是 ECDHE 的底层数学基础——它决定了密钥交换在哪个椭圆曲线群上进行。

两者的关系图

TLS 连接安全
├── 密钥交换(ECDHE)── 这是"用椭圆曲线做 DH"
│ └── 椭圆曲线:secp256r1 (0x0017) ← 具体在哪条曲线上做
├── 身份认证(ECDSA)── 服务器用 ECDSA 签名
│ └── 椭圆曲线:由服务器证书决定
├── 数据加密(AES-128-GCM)
└── 哈希函数(SHA-256)

简单说:密码套件决定”用什么种类的算法”,椭圆曲线决定”用哪条具体的曲线”

密码套件 规定了 TLS 的”整体算法配方”(密钥交换种类 + 加密算法 + 哈希算法),
椭圆曲线 规定了 ECDHE 密钥交换”在哪条曲线上进行”。

二者是正交维度的选择:套件是”用 ECDHE 做交换”,曲线是”在 secp256r1 上做”。
选择 0xC02B + secp256r1 是因为它们在安全强度上完美对齐(都提供 ~128 位安全),且是 TLS 1.2 中最主流、兼容性最好的现代组合。


ECDHE-ECDSA-AES128-GCM-SHA256 各组件的作用时机详解


总体流程图

┌─────────────────────────────────────────────────────────────────────────────────┐
│ TLS 1.2 握手完整流程 │
│ ECDHE-ECDSA-AES128-GCM-SHA256 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 客户端 (Client) 服务器 (Server) │
│ │ │ │
│ │── ① ClientHello ──────────────────────────────────────────────────→│ │
│ │ · cipher_suites: [0xC02B] ┌────────────────────────┐ │ │
│ │ · supported_groups: [secp256r1] │ ECDHE: 仅声明"用它" │ │ │
│ │ · sig_algs: [ECDSA+SHA256] │ 具体曲线由后续协商 │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │←──────────────── ② ServerHello ───────────────────────────────────│ │
│ │ · cipher_suite: 0xC02B ┌────────────────────────┐ │ │
│ │ · curve: secp256r1 (0x0017) │ 密码套件被正式确定 │ │ │
│ │ · random_server │ 各组件即将依次生效 │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │←──────────────── ③ Certificate ───────────────────────────────────│ │
│ │ · ECDSA 证书(公钥嵌入) ┌────────────────────────┐ │ │
│ │ │ ECDSA: 告知服务器身份 │ │ │
│ │ │ 的公钥凭证类型 │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │←──────────────── ④ ServerKeyExchange ─────────────────────────────│ │
│ │ · curve=secp256r1, server_pub_key ┌────────────────────────┐ │ │
│ │ · ECDSA 签名 (hash=SHA256) │ ECDSA: 签名验证身份 │ │ │
│ │ │ ECDHE: 公钥为交换做准备│ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │←──────────────── ⑤ ServerHelloDone ──────────────────────────────│ │
│ │ │ │
│ │ │ │
│ │── ⑥ ClientKeyExchange ──────────────────────────────────────────→│ │
│ │ · client_ecdh_pub_key ┌────────────────────────┐ │ │
│ │ │ ECDHE: 客户端生成密钥│ │ │
│ │ │ 对,公钥发给服务器 │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────── 密钥派生阶段 ───────────┐ │ │
│ │ │ ECDHE: shared_secret = │ │ │
│ │ │ client_priv × server_pub (secp256) │ │ │
│ │ │ │ │ │
│ │ │ SHA256: master_secret = │ │ │
│ │ │ PRF(shared_secret, ...) │ │ │
│ │ │ │ │ │
│ │ │ SHA256: key_block = │ │ │
│ │ │ PRF(master_secret, ...) │ │ │
│ │ │ → client/server_write_key(16B) │ │ │
│ │ │ → client/server_write_IV(4B) │ │ │
│ │ └────────────────────────────────────┘ │ │
│ │ │ │
│ │── ⑦ ChangeCipherSpec ──────────────────────────────────────────→│ │
│ │ (通知后续消息使用新密钥加密) │ │
│ │ │ │
│ │── ⑧ Finished (encrypted with AES-128-GCM) ─────────────────────→│ │
│ │ · hs_hash = SHA256(所有握手消息) ┌────────────────────────┐ │ │
│ │ · verify_data = PRF(master_secret,…) │ AES128-GCM: 首次加密 │ │ │
│ │ │ SHA256: PRF + 完整性 │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │←──────────────── ⑨ ChangeCipherSpec ──────────────────────────────│ │
│ │ │ │
│ │←──────────────── ⑩ Finished (encrypted) ──────────────────────────│ │
│ │ │ │
│ ╔═════════════════════════════════════════════════════════════════════════╗ │
│ ║ 应用数据阶段(已认证加密) ║ │
│ ╠═════════════════════════════════════════════════════════════════════════╣ │
│ ║ ┌─────────────────────────────────────────────────────────────────┐ ║ │
│ ║ │ AES128-GCM 持续使用: │ ║ │
│ ║ │ · encryptGCM(plaintext, key, iv, seq, type) → ciphertext │ ║ │
│ ║ │ · decryptGCM(ciphertext, key, iv, seq, type) → plaintext │ ║ │
│ ║ │ · SHA256 仍为 PRF/Finished 提供哈希基础 │ ║ │
│ ║ └─────────────────────────────────────────────────────────────────┘ ║ │
│ ║ ║ │
│ ║ 客户端 ──HTTP GET /──→ 服务器 (AES-128-GCM 加密) ║ │
│ ║ 客户端 ←── HTTP 200 ←── 服务器 (AES-128-GCM 加密) ║ │
│ ╚═════════════════════════════════════════════════════════════════════════╝ │
└─────────────────────────────────────────────────────────────────────────────────┘

各组件的详细作用时机

ECDHE — 密钥交换算法(步骤 ⑥ 及密钥派生阶段)

何时起效:握手中期,在服务器消息全部接收完毕后(步骤 ⑥~密钥派生)

具体做什么

阶段 操作
准备 客户端在 ClientHello 中声明支持 ECDHE(通过密码套件 0xC02B 和扩展)
服务器发起 服务器在 ServerKeyExchange 中发送其 ECDHE 公钥 + 签名
客户端生成 客户端生成自己的临时 ECDHE 密钥对
客户端发送 客户端将 ECDHE 公钥封装在 ClientKeyExchange 中发给服务器
计算共享密钥 客户端用自己的私钥 × 服务器的公钥,在 secp256r1 曲线上计算共享密钥

核心价值:共享密钥永不在线传输,双方各自用私钥×对方公钥得出同一结果。即使日后服务器私钥泄露,历史会话仍安全——这就是前向保密。

客户端: priv_client × pub_server = shared_secret  ┐
├─ 同一值 pre_master_secret
服务器: priv_server × pub_client = shared_secret ┘

ECDSA — 签名算法(步骤 ④)

何时起效:服务器证书验证 + ServerKeyExchange 签名验证

具体做什么

阶段 操作
证书解析 Certificate 中解析服务器公钥,确定为 ECDSA 类型
签名验证 服务器对 ServerKeyExchange 内容(随机数+曲线+公钥)做 ECDSA 签名,客户端验证

核心价值:证明”公钥交换参数确实由持有证书私钥的一方发出”,防止中间人攻击者替换 ECDHE 公钥。

ServerKeyExchange 内容:
verifyData = client_random || server_random || curve_type || curve_id || pubkey_len || pubkey

服务器用 ECDSA 私钥签名(verifyData) → signature
客户端用 ECDSA 证书公钥验证(verifyData, signature) → OK/FAIL

SHA256 — 哈希函数(密钥派生阶段 + Finished)

何时起效:密钥派生和完整性校验

具体做什么

用途 操作
密钥派生 PRF master_secret = PRF(sharedSecret, "master secret", clientRand+serverRand) 用 SHA-256 做 HMAC
密钥派生 PRF key_block = PRF(masterSecret, "key expansion", serverRand+clientRand) 派生 4 个密钥/IV
Finished 哈希 hsHash = SHA256(所有握手消息) 用于 Finished 完整性校验
Finished PRF verifyData = PRF(masterSecret, "client finished", hsHash) 产生 12 字节验证数据
ServerKeyExchange 签名哈希 hashBytes = SHA256(verifyData) 作为 ECDSA 签名输入

核心价值:SHA-256 是 TLS 内部的”粘合剂”——密钥派生靠它、握手完整性校验靠它、签名哈希也靠它。

密钥派生链 (全部基于 SHA-256 PRF):

shared_secret (48B)
│ PRF(PRF) = HMAC-SHA256

master_secret (48B)
│ PRF

key_block = client_write_key(16B) || server_write_key(16B)
|| client_write_IV(4B) || server_write_IV(4B)

AES128-GCM — 加密算法(步骤 ⑧ 及之后全程)

何时起效:ChangeCipherSpec 之后的所有消息(Finished + 应用数据)

具体做什么

阶段 操作
激活 ChangeCipherSpec 通知双方后续使用新密钥加密
首次使用 客户端 Finished 消息用 AES-128-GCM 加密发送
应用数据加密 encryptGCM(plaintext, key, iv, seq, type) 加密 HTTP 请求/响应
应用数据解密 decryptGCM(ciphertext, key, iv, seq, type) 解密 HTTP 响应

核心价值:GCM 同时提供机密性(AES 加密)和完整性(16 字节认证标签),一次运算完成两个目标。

AES-128-GCM 加密过程:

输入: plaintext (HTTP 数据)
密钥: client_write_key (16B, 由 PRF 派生)
Nonce: implicit_IV(4B) || explicit_nonce(8B, = 序列号)
AAD: seq(8B) || content_type(1B) || version(2B) || length(2B)

输出: explicit_nonce(8B) || ciphertext || tag(16B)

TLS 记录层: type(1B) || version(2B) || length(2B) || 上述输出