Skip to content

TLS

SSL

SSL(Secure Sockets Layer)是最早期的加密协议,用来保障数据在传输过程中的安全。 SSL 已被废弃,因为存在安全漏洞。但很多人习惯口头上还说SSL证书,其实现在都是 TLS。

SSL 证书

在互联网安全通信中,TLS 协议需要确认通信的对方身份,以防止中间人冒充。

为此,TLS 使用 X.509 数字证书。这种证书最初并不是为 TLS 专门设计的,而是更早期的身份认证标准。后来被 SSL/TLS 协议采纳,用来:

  • 证明服务器(或客户端)的身份;

  • 提供公钥,支持安全的密钥交换。

由于 SSL 协议在早期被广泛使用,这类证书被俗称为SSL 证书。其实,无论今天是 HTTPS、FTPS 还是 IMAPS,背后用的都是同样的 X.509 证书,只不过现在协议主流是 TLS。

常见的 xxxS 协议(如 HTTPS = HTTP over TLS)通常表示在原协议上加了一层加密保护。但需要注意:历史上有些 S 协议最早是基于 SSL,而不是现在的 TLS,有的还支持 STARTTLS 这样的方式。所以 S = TLS 并不是绝对规律。

TLS

TLS(Transport Layer Security,传输层安全协议)是继承自 SSL 的安全通信协议,目前已经发展到 TLS 1.3,这是现阶段最安全、应用最广泛的版本。

TLS 本质上是一个独立的安全层协议,它并非专为 HTTP 设计,而是基于 TCP 的通用安全机制。常见应用包括:

  • HTTPSHTTP over TLS,即 HTTP 流量通过 TLS 加密传输
  • IMAPS / SMTPS / POP3S:邮件协议通过 TLS 加密
  • FTPS:FTP 基于 TLS 的安全传输

此外,当在 UDP 上使用时,TLS 的变体称为 DTLS(Datagram TLS),并非直接套用 TLS。

TLS握手

ClientHello

协议版本

  • 内容:客户端支持的 TLS 版本(通常是 TLS 1.3,但会兼容低版本)。

  • 作用:让服务端知道客户端我至少能用 TLS 1.2/1.3,你选一个大家都支持的

Client Random(随机数)

  • 内容:一个 32 字节的随机值。

  • 作用:后续生成密钥时的输入之一,确保密钥是每次握手唯一的,防止重放攻击。

Session ID / PSK 信息(可选)

  • 内容:可能包含上次会话的 ID 或者基于 PSK (Pre-Shared Key) 的参数。

  • 作用:支持 会话恢复 和 0-RTT(客户端在握手未完成前就能发数据)。

Cipher Suites(密码套件列表)

  • 内容:客户端支持的一组加密算法组合。

  • 作用:让服务端从中选择一个双方都支持的安全算法。

以下是官方推荐的 5 种套件

套件名称加密算法哈希算法适用场景
TLS_AES_256_GCM_SHA384AES-256-GCMSHA-384最高安全级别(推荐生产环境)
TLS_AES_128_GCM_SHA256AES-128-GCMSHA-256平衡安全与性能(通用场景)
TLS_CHACHA20_POLY1305_SHA256ChaCha20-Poly1305SHA-256移动设备/无
TLS_AES_128_CCM_SHA256AES-128-CCMSHA-256特殊硬件需求(极少使用)
TLS_AES_128_CCM_8_SHA256AES-128-CCM-8(短标签)SHA-256资源严格受限设备(不推荐)

Compression Methods

  • 内容:TLS 1.3 已废弃压缩,这里基本为空, TLS 1.3 固定为 null

  • 作用:防御 CRIME 攻击(TLS 压缩漏洞)。

KeyShare(密钥交换参数)

  • 内容:客户端的 ECDHE 公钥(曲线 ID + 公钥点)。

  • 作用:提供 DH/ECDHE 公钥参数,服务端用它和自己的私钥算共享秘密。

  • 注意:这是 TLS 1.3 最大的改进之一,首次握手就发公钥,避免了 TLS 1.2 的多往返

Supported Groups(椭圆曲线/群 ID)

  • 内容:客户端支持的椭圆曲线或有限域 DH 群(如 secp256r1, x25519)。

  • 作用:告诉服务端我能做哪些曲线上的 ECDHE,避免选一个客户端不会的。

Signature Algorithms

  • 内容:客户端支持的数字签名算法(如 rsa_pss_rsae_sha256, ecdsa_secp256r1_sha256)。

  • 作用:服务端在发证书和签名时,需要选择一种客户端认可的算法。

Supported Versions(TLS 1.3 专有)

  • 内容:客户端支持的 TLS 版本列表(比如 [0x0304=TLS 1.3, 0x0303=TLS 1.2])。

  • 作用:避免降级攻击(防止攻击者强迫客户端退回到 TLS 1.0 之类的老版本)。

Extensions(扩展)

TLS 1.3 里,ClientHello 的扩展字段极其重要:

  • SNI(Server Name Indication):告诉服务器目标域名,便于虚拟主机区分不同网站。

  • ALPN(Application Layer Protocol Negotiation):比如选择 http/1.1 或 h2 (HTTP/2)。

  • OCSP stapling:要求服务端附带证书状态响应。

  • PSK / Early Data:如果要用 0-RTT,可以在这里声明。


ServerHello

协议版本

  • 内容:服务端选择的最终版本(通常就是 TLS 1.3)。

  • 作用:明确告诉客户端我们这次会话使用 TLS 1.3,防止降级攻击。

Server Random(随机数)

  • 内容:一个 32 字节的随机值。

  • 作用:与 Client Random 一起参与密钥派生,确保每次会话的密钥都是唯一的。

Session ID Echo(会话 ID 回显,可选)

  • 内容:如果客户端提供了 Session ID,这里可能会回显。

  • 作用:TLS 1.3 中不常用,主要为了兼容性(TLS 1.2 的会话恢复机制)。

Cipher Suite(密码套件)

  • 内容:从客户端提供的候选列表里选一个,比如 TLS_AES_128_GCM_SHA256。

  • 作用:确定双方后续通信的加密算法(对称加密 + 哈希函数)。

Compression Method(固定 null)

  • 内容:TLS 1.3 废弃了压缩,这里基本固定为 null。

  • 作用:防止 CRIME 攻击。

KeyShare(服务端的 ECDHE 公钥参数)

  • 内容:服务端的 ECDHE 公钥(曲线 ID + 公钥点)。

  • 作用:和客户端的 KeyShare 一起,通过 ECDHE 算法计算共享秘密。

Supported Versions(确认版本)

  • 内容:确认使用的 TLS 版本(一般就是 TLS 1.3)。

  • 作用:再次防止降级攻击,确保双方对版本达成一致。

其他扩展(Extensions)

  • ALPN(应用层协议协商):选择 HTTP/2 (h2) 或 HTTP/1.1。

  • PSK / Early Data:如果使用了会话恢复或 0-RTT,在这里确认。

  • 其他扩展:可能包括加密算法参数协商结果。


字段对照

类别ClientHello 发的内容ServerHello 的回应作用
协议版本Supported Versions(支持的版本列表)选择一个版本确认最终使用的 TLS 版本,防止降级攻击
随机数Client Random(32字节随机数)Server Random(32字节随机数)两个随机数一起参与密钥派生,保证每次会话唯一
会话恢复 / 0-RTTSession ID / PSK 信息Session ID 回显 / PSK 接受支持会话恢复和 0-RTT 数据传输
密码套件Cipher Suites(候选列表)从列表中选定一个确定最终对称加密和哈希算法
压缩方式TLS 1.3 固定 null固定 null为避免 CRIME 攻击, TLS 1.3 废弃压缩
密钥交换参数KeyShare(ECDHE 公钥,曲线 ID + 公钥点)KeyShare(服务端 ECDHE 公钥)双方用 ECDHE 公钥计算共享秘密,生成后续密钥材料
支持的曲线 / 群Supported Groups已通过 KeyShare 表现确认使用哪条曲线进行 ECDHE
支持的签名算法Signature Algorithms在后续 CertificateVerify 使用确保服务器证书签名客户端能验证
扩展信息确定访问的虚拟主机、上层协议,以及证书状态等

计算共享秘密

计算过程

项目服务端持有客户端持有来源说明
ECDHE 私钥服务端自己生成客户端自己生成各自本地产生,不交换
ECDHE 公钥ClientHello 里获得客户端公钥ServerHello 里获得服务端公钥公钥由对方发送,自己保存
曲线 ID从 ClientHello.KeyShare 里看到候选曲线并选择从 ServerHello.KeyShare 里得知服务端选择的曲线客户端提议,服务端选择并确认

结果: 双方用自己的私钥 + 对方的公钥 = 算出相同的 ECDHE共享秘密(ECDHE_SharedSecret)

计算原理

ECDHE 的基本数学背景

以 椭圆曲线 Diffie-Hellman(ECDH) 为例:

  • 双方事先约定好一条椭圆曲线(比如 secp256r1)和一个基点 G
  • 客户端生成一个随机私钥 a,并计算公钥 A = aG
  • 服务端生成一个随机私钥 b,并计算公钥 B = bG
  • 双方交换各自的公钥 A 和 B。

双方计算共享秘密

  • 客户端拿到服务端公钥 B,用自己的私钥 a 计算:Sc = aB = a(bG) = (ab)G

  • 服务端拿到客户端公钥 A,用自己的私钥 b 计算:Ss = bA = b(aG) = (ab)G

也就是说,虽然双方只知道自己的私钥,但因为群运算的交换律,最后计算出来的点完全相同

为什么攻击者拿不到

  • 攻击者只能看到:基点 G,客户端公钥 A=aG,服务端公钥 B=bG。

  • 他要想计算共享秘密 (ab)G,必须知道 a 或 b。

  • 但根据 椭圆曲线离散对数问题(ECDLP),从 A=aG 推出 a 在现有计算能力下是不可行的。

  • 所以,攻击者不能计算出 (ab)G,而客户端和服务端可以。


派生阶段密钥

问题背景

  • 共享秘密只是一个椭圆曲线点的坐标,分布不均匀,不适合直接作为对称加密密钥。
  • 要派生多个不同用途的密钥,即使一个阶段的密钥泄露,其他阶段的密钥也不会受影响。

HKDF介绍

HKDF (HMAC-based Key Derivation Function) 是一个基于 HMAC 的密钥派生函数。 它的作用是:从一个原始秘密中,安全地派生出多个独立、用途不同的子密钥。

具体流程(***)

  1. 双方 HKDF-Extract 对共享秘密进行标准化, 获得标准化的Handshake Secret
  2. 双方 HKDF-Expand 派生出握手阶段的密钥(client/server handshake traffic key)
  3. Server 使用 server handshake traffic key 分别加密发送以下消息
    • EncryptedExtensions(用 s hs traffic key 加密)

    • Certificate(用 s hs traffic key 加密)

    • CertificateVerify(用 s hs traffic key 加密,内部用证书私钥对 transcript_hash 签名)

    • Finished(Server)(用 s hs traffic key 加密,内部用 finished key 对 transcript_hash 做 HMAC)

  4. 客户端每收到一条握手消息就 立刻解密并更新 transcript_hash,不是等全部收完才一起解。
    • 验证证书链(CA → 网站证书)

    • 验证 CertificateVerify 签名(用证书公钥)

    • 验证 Finished(Server)(用 finished key 校验 HMAC)

  5. 客户端用 c hs traffic key 加密发送 Finished(Client),内部同样是用 finished key 对 transcript_hash 做 HMAC。
  6. 服务端解密并验证, 至此握手完成
  7. 双方 HKDF-Extract 对共享秘密进行标准化, 获得标准化的 Master Secret
  8. 双方 HKDF-Expand 派生出握手阶段的密钥(client/server application traffic key)
  9. 丢弃 handshake traffic key, 进入应用数据加密阶段(HTTP/2、HTTP/3 等)

TIP

里面有些概念还不理解没关系, 下面有详细的介绍


HKDF-Extract

从下列表格的产物中得到3个标准化后的结果: EarlySecret, HandshakeSecret, MasterSecret

产物saltIKM公式表达
EarlySecret0如果用了 PSK(Pre-Shared Key,例如会话恢复/0-RTT), 则为PSK, 否则为0EarlySecret = HKDF-Extract(0, PSK(0))
HandshakeSecretEarlySecretECDHE共享秘密HandshakeSecret = HKDF_Extract(EarlySecret,ECDHE_SharedSecret)
MasterSecretHandshakeSecret0MasterSecret = HKDF_Extract(HandshakeSecret,0)

transcript_hash

在每个阶段,双方都会根据 ServerHello 选定的密码套件里规定的 hash 算法把到目前为止的所有握手消息做一次哈希。

alt text

作用

  • 保证密钥和握手消息强绑定,防止攻击者篡改握手消息。

  • 如果中间有人修改了 ClientHello 或 ServerHello,hash 就会不同 → 派生出的密钥也不同 → 最终 Finished 校验会失败

HKDF-Expand

输入材料

HKDF-Expand-Label(secret, label, context, length)

  • secret:输入的阶段 Secret(例如 Handshake Secret、Master Secret)
  • label:密钥用途标签,由协议固定定义,如:
    • "c hs traffic":客户端握手流量密钥
    • "s hs traffic":服务端握手流量密钥
    • "c ap traffic":客户端应用流量密钥
    • "s ap traffic":服务端应用流量密钥
    • "finished":Finished 报文校验密钥
  • context:通常是握手消息的 transcript_hash,用来保证密钥和所有握手内容强绑定
  • length:要输出的密钥长度,由密码套件 (cipher suite) 决定,例如 AES-128-GCM 需要 16 字节,AES-256-GCM 需要 32 字节

工作原理

Expand 实际上是反复运行 HMAC:

T(1) = HMAC(secret, info || 0x01)
T(2) = HMAC(secret, T(1) || info || 0x02)
...
OKM = T(1) || T(2) || ... 直到满足所需长度
  • info = "tls13 " + label + context
  • counter:保证能生成足够长的输出,一般只到 1 就够了(因为 TLS 密钥长度不大)

产出产品

握手阶段

基于的标准化Secret派生结果LabelContext (info)Length 来源用途
Handshake Secretclient handshake traffic secret"c hs traffic"transcript_hash(ClientHello+ServerHello)cipher suite客户端握手阶段加密/解密
server handshake traffic secret"s hs traffic"transcript_hash(ClientHello+ServerHello)cipher suite服务端握手阶段加密/解密
finished key (client/server)"finished"transcript_hash(到当前消息)HMAC 输出长度生成/校验 Finished 报文

应用阶段

基于的标准化Secret派生结果LabelContext (info)Length 来源用途
Master Secretclient application traffic secret"c ap traffic"transcript_hash(握手消息全集)cipher suite客户端应用数据加密/解密
server application traffic secret"s ap traffic"transcript_hash(握手消息全集)cipher suite服务端应用数据加密/解密
exporter master secret"exp master"transcript_hash(握手消息全集)HMAC 输出长度应用层导出密钥(外部使用)
resumption master secret"res master"transcript_hash(握手消息全集)HMAC 输出长度生成 PSK 用于未来会话恢复

0-RTT 阶段(可选)

基于的标准化Secret派生结果LabelContext (info)Length 来源用途
Early Secretbinder key"res binder"transcript_hash(ClientHello[without binders])HMAC 输出长度验证 PSK 绑定(0-RTT/会话恢复)
client early traffic secret"c e traffic"transcript_hash(ClientHello)cipher suite0-RTT 数据加密
early exporter master secret"e exp master"transcript_hash(ClientHello)HMAC 输出长度提供 0-RTT 的密钥导出

证书验证

CA

CA的全称是证书颁发机构(Certificate Authority),它是由受信任的第三方机构负责签发和管理数字证书,以验证网络实体(如网站、用户、设备)的身份,并保障通信安全


根CA证书

操作系统厂商(Microsoft Windows、Apple、Linux 发行版)、浏览器厂商(Chrome、Firefox)会有自己的信任根计划(Root Program)

他们有自己的密钥对A, 用私钥A对自己的信息和公钥进行加密获得数字签名, 并将数字签名和公钥A放入证书, 并预先安装在浏览器或者操作系统里

根CA的私钥一旦泄露,全世界的信任体系就崩溃(任何人都能伪造合法证书)。 所以根CA的私钥通常保存在 HSM(硬件安全模块),离线存储、物理隔离。

平时不会直接用根私钥签发终端证书,而是只用它签发中间CA证书和每隔几年更新一次中间CA证书

当签发中间CA时,根CA可能举行线下签发仪式(有摄像、多人见证、密钥分片恢复)。 一旦签好中间CA证书,就把根私钥再锁回保险柜。


中间CA证书

中间CA需要根CA签发中间CA证书(Intermediate Cert), 以便于证明中间CA是中间CA, 因此也有自己的密钥对B。

中间CA将公钥B和自己的信息一并交给根CA, 根CA使用私钥A进行数字签名后, 将中间CA证书签发给中间CA。


网站证书

每个网站都有一对专属密钥C, 将公钥C和域名信息、有效期、证书等信息等交给中间CA。

中间CA将这些信息和公钥C进行哈希运算得到哈希值, 再用私钥B对哈希值进行加密得到中间CA的数字签名。

最后将公钥C、数字签名还有上述信息放入网站证书(Leaf Cert), 签发给网站主人


用户访问网站

当用户在浏览器中输入 https://example.com 时,服务器会返回一份 证书链,其中通常包含:

  • 网站证书(Leaf Certificate)
  • 中间 CA 证书(Intermediate Certificate)

客户端收到证书链后,会依次验证,形成一条信任链(Chain of Trust)


验证网站证书

  • 网站证书上带有 中间 CA 的签名
  • 客户端需要使用 中间 CA 的公钥 B 来验证该签名。
  • 公钥 B 就存放在中间 CA 证书中。
  • 因此,在验证网站证书前,客户端必须先验证中间 CA 证书的有效性。

验证中间 CA 证书

  • 中间 CA 证书上带有 根 CA 的签名
  • 客户端需要使用 根 CA 的公钥 A 来验证该签名。
  • 公钥 A 存放在 根 CA 证书 中。

根 CA 证书

  • 根 CA 证书是自签的,即它的签名是自己生成的。
  • 浏览器并不会尝试去验证这个签名,而是直接检查根证书是否存在于操作系统或浏览器的 受信任列表(Root Store)
  • 如果该根证书已预装且被信任,则整个信任链才能继续。

信任链的逐级验证过程

  1. 验证根 CA:确认根证书在本机信任库中。
  2. 用根 CA 公钥 A 验证中间 CA 证书的签名。
  3. 用中间 CA 公钥 B 验证网站证书的签名。
  4. 在验证网站证书时:
    • 客户端先用公钥 B 对网站证书中的数字签名进行解密,得到一个哈希值。
    • 同时,客户端对网站证书的内容(域名、有效期、公钥等)进行同样的哈希运算。
    • 如果这两个哈希值相同,则说明证书内容未被篡改,网站证书可信。

前向安全(PFS)

即使服务器的长期私钥在未来泄露,也无法解密历史通信。
原因是每次会话密钥都由临时密钥 ab 生成,与长期私钥无关。


会话恢复与 0-RTT

  1. Session Resumption(会话恢复 / PSK / Ticket)

    • 服务器在首次会话结束时发放一个 Session Ticket
    • 下次连接时,客户端带上 Ticket 作为 PSK,免去证书链传输和签名验证,加速握手。
    • 推荐使用 PSK + DHE(PSK-DHE):即便票据泄露,仍可保持前向安全。
  2. 0-RTT Early Data(零往返数据)

    • 使用 PSK 时,客户端可在服务器最终确认前直接发送少量早期数据(0-RTT)。
    • 风险:0-RTT 数据可被 重放(Replay)
    • 因此对涉及写操作(如下单、转账)必须禁用 0-RTT,或实现 幂等性 / 防重放机制