入门基础

TLS 1.3 握手详解:逐条消息拆开看

作者: · 最后核实

TLS 1.3 握手只有 1 个往返:客户端发出带 key share 的 ClientHello,服务器回 ServerHello 和一组加密消息(EncryptedExtensions、Certificate、CertificateVerify、Finished),客户端再回一个 Finished 就完成了。ServerHello 之后的内容全部加密。消息定义以现行规范 RFC 9846 为准,它在 2026 年 7 月取代了最初的 RFC 8446,版本号不变、向后兼容。

完整握手全景图

TLS 1.3 完整握手共三轮发送,下图包含可选的客户端认证。花括号表示用握手密钥加密,方括号表示用应用数据密钥加密,* 表示可选消息。

客户端                                                      服务器

ClientHello
  + supported_versions, key_share, signature_algorithms,
  + server_name, ALPN, psk_key_exchange_modes,
  + pre_shared_key*, early_data*                 -------->
(0-RTT [应用数据*])                            -------->
                                                       ServerHello
                                                 + supported_versions
                                                 + key_share
                                                 + pre_shared_key*
                                             {EncryptedExtensions}
                                             {CertificateRequest*}
                                                    {Certificate*}
                                              {CertificateVerify*}
                                                        {Finished}
                                 <--------           [应用数据*]
{EndOfEarlyData*}
{Certificate*}
{CertificateVerify*}
{Finished}                                       -------->
                                 <--------     [NewSessionTicket*]
[应用数据]                       <------->            [应用数据]

图里出现三组密钥:早期数据密钥(只给 0-RTT 用)、握手密钥(ServerHello 到 Finished 之间)、应用数据密钥(Finished 之后)。它们都出自同一个密钥调度过程,后文有图。

ClientHello:客户端的开场报价

ClientHello 把服务器选参数、算密钥所需的东西一次带齐。固定字段大多是历史包袱,真正的协商都在扩展里。

字段或扩展 在 TLS 1.3 中的作用
legacy_version 为了兼容固定写 0x0303(TLS 1.2),真实版本看 supported_versions
random 32 字节随机数,进入握手记录哈希
legacy_session_id 兼容模式下是随机 32 字节,否则为空
cipher_suites TLS 1.3 套件如 TLS_AES_128_GCM_SHA256;仍支持 1.2 的客户端还会列出 1.2 套件
supported_versions 支持的版本,如 0x0304(TLS 1.3)、0x0303(TLS 1.2)
supported_groups 可接受的密钥交换组,按偏好排序(X25519MLKEM768、X25519、P-256……)
key_share 针对其中一个或几个组的真实公钥,相当于押注服务器会选哪个
signature_algorithms 客户端能验证的签名算法,如 ecdsa_secp256r1_sha256、rsa_pss_rsae_sha256、ed25519
server_name 要访问的域名(SNI,RFC 6066),服务器据此选证书
application_layer_protocol_negotiation ALPN 列表,如 h2、http/1.1
psk_key_exchange_modes 会话恢复时能否跳过 (EC)DHE(psk_ke)还是必须带上(psk_dhe_ke)
pre_shared_key 用于会话恢复的票据标识,必须是最后一个扩展
early_data 表示后面跟着 0-RTT 数据

浏览器还会塞进 GREASE 值(RFC 8701):一些随机的保留编号,用来逼服务器正确忽略不认识的值,避免协议“锈死”。

key share 让 ClientHello 比以前大了很多。用 OpenSSL 3.6 实测,默认同时发送 X25519MLKEM768 和 X25519 两个 key share,ClientHello 共 1533 字节,其中 key_share 扩展就占 1258 字节:混合 share 是 1184 字节的 ML-KEM-768 公钥加 32 字节的 X25519 公钥。ClientHello 超出一个 TCP 报文段后,浏览器刚启用混合密钥交换时曾让一些有缺陷的中间设备出问题,详见后量子 TLS。

HelloRetryRequest:押错了 key share

如果客户端带的 key share 服务器一个都不支持,但客户端列出的其他组里有服务器支持的,服务器就回一个 HelloRetryRequest,客户端换上对应的 key share 重发 ClientHello,握手因此多 1 个往返。

HelloRetryRequest 在线路上其实是一条 ServerHello,只是它的 random 是一个固定的特殊值(字符串 "HelloRetryRequest" 的 SHA-256)。它可以带 cookie 扩展让客户端原样带回,方便服务器做无状态处理。为了控制握手记录的长度,第一条 ClientHello 在哈希里会被替换成一条合成的 message_hash 消息。

下面是对一台只接受 P-384 的测试服务器的实测(OpenSSL 3.6,-msg 输出,去掉了记录头行):

>>> TLS 1.3, Handshake [length 0601], ClientHello
<<< TLS 1.3, Handshake [length 0058], ServerHello        <- 这条是 HelloRetryRequest
>>> TLS 1.3, Handshake [length 017e], ClientHello        <- 换成 secp384r1 重发
<<< TLS 1.3, Handshake [length 00bb], ServerHello
<<< TLS 1.3, Handshake [length 0006], EncryptedExtensions
<<< TLS 1.3, Handshake [length 0193], Certificate
<<< TLS 1.3, Handshake [length 004f], CertificateVerify
<<< TLS 1.3, Handshake [length 0034], Finished
>>> TLS 1.3, Handshake [length 0034], Finished

服务器应当支持主流客户端会直接发 share 的组(X25519;截至 2026 年 11 月,X25519MLKEM768 也越来越普遍),避免这笔额外开销。

ServerHello:服务器拍板

ServerHello 确定密码套件并给出服务器的 key share,之后双方都能算出握手秘密。它包含:

  • cipher_suite:从客户端列表里选中的一个 TLS 1.3 套件;
  • supported_versions:0x0304,即真实协议版本;
  • key_share:服务器在所选组上的公钥。X25519MLKEM768 下它是 1120 字节,即 1088 字节的 ML-KEM 密文加 32 字节 X25519 公钥;
  • pre_shared_key:会话恢复时接受了哪张票据。

服务器的 random 还承担降级保护:支持 TLS 1.3 的服务器如果最终协商成 TLS 1.2 或更低版本,会把最后 8 字节写成 DOWNGRD 加 01 或 00,支持 TLS 1.3 的客户端看到就中止连接。random 受服务器签名保护,攻击者无法抹掉这个标记。

EncryptedExtensions

EncryptedExtensions 是第一条加密消息,装的是所有不参与密钥计算的协商结果:服务器选定的 ALPN 协议(如 h2)、对 server_name 的空确认、服务器的 supported_groups 偏好(供下次参考)、是否接受 0-RTT 的 early_data 等。这些信息挪出 ServerHello 后,被动观察者就看不到了。

CertificateRequest(可选)

只有服务器要求客户端证书时才发 CertificateRequest,也就是双向 TLS 的场景。它包含 certificate_request_context、服务器接受的 signature_algorithms,以及可选的 certificate_authorities(可接受的签发者列表)。客户端如果声明了 post_handshake_auth,TLS 1.3 还允许握手完成后再要证书。

Certificate

Certificate 消息携带服务器证书链:先是站点证书,再是中间证书。每个证书条目都可以带自己的扩展,所以 OCSP 装订响应(status_request)和 SCT 时间戳会挂在对应证书上一起发送。根证书一般不发,因为客户端本来就得信任它;漏发中间证书则是缺少中间证书报错的经典原因。证书链还可以用 RFC 8879 压缩,比如 Chrome 会协商 Brotli 压缩。

CertificateVerify

CertificateVerify 证明服务器确实持有证书私钥。服务器对“64 个空格 + 上下文字符串 TLS 1.3, server CertificateVerify + 0 字节 + 目前为止的握手哈希”签名,上下文字符串防止签名被挪用到别的协议。使用 RSA 密钥的服务器必须用 RSA-PSS 签这条消息(rsa_pss_rsae_sha256 等),哪怕证书本身是 PKCS#1 v1.5 签的。客户端这边,RFC 9963(2026 年 4 月)新增了 rsa_pkcs1_sha256_legacy 等编号,让只能做 PKCS#1 v1.5 签名的智能卡、U 盾等硬件也能在 TLS 1.3 下完成客户端认证,前提是服务器在 CertificateRequest 里提供这些编号。

Finished

Finished 是用握手密钥派生出的 finished_key 对整个握手记录做的 HMAC。它确认双方看到的消息完全一致、算出的密钥也一致;ClientHello 被篡改或有人试图降级,这一步都会失败。服务器发完 Finished 就可以开始发应用数据(所谓 0.5-RTT 数据),因为此时它已经有了密钥,只是还没验证客户端身份。

客户端的第二轮发送

客户端验证证书链、域名和 CertificateVerify 签名,校验服务器的 Finished,然后发送:

  1. EndOfEarlyData:只有发过且被接受了 0-RTT 数据时才发;
  2. Certificate 和 CertificateVerify:只有服务器发过 CertificateRequest 时才发,没有证书的客户端发一条空的 Certificate;
  3. Finished。

应用数据可以和这一轮一起发出,所以 HTTP 请求在 ClientHello 之后 1 个往返就上路了。

NewSessionTicket 与会话恢复

握手完成后,服务器可以发一张或多张 NewSessionTicket。每张票据是一段不透明数据,附带有效期(最长 604800 秒,即 7 天)、年龄混淆值和 nonce。下次连接时客户端把票据放进 pre_shared_key。使用 psk_dhe_ke 模式时,恢复握手仍会做一次新的 (EC)DHE,保留前向保密,而服务器可以省掉 Certificate 和 CertificateVerify。见会话恢复。

多台服务器挂在负载均衡后面时(比如阿里云 SLB、腾讯云 CLB 后面的多台 Nginx),票据加密密钥要么在各台之间同步,要么在负载均衡上统一终止 TLS,否则请求落到另一台机器上就恢复不了会话。

0-RTT 早期数据

持有允许 0-RTT 的票据时,客户端可以在第一轮就发应用数据,用客户端早期数据密钥加密。省下一个往返,但有两个弱点:它相对票据密钥不具备前向保密,而且攻击者可以把它重放给服务器。TLS 1.3 规范建议服务器做防重放处理,RFC 8470 规定了 HTTP 的处理方式:服务器可以返回 425 Too Early,代理转发早期数据时加上 Early-Data: 1 头。只有 GET 这类幂等请求才适合用 0-RTT 发送。

一张图看懂密钥调度

TLS 1.3 的所有密钥都来自三次 HKDF-Extract(RFC 5869)。简化如下:

           PSK(没有就用全 0)
                 |
                 v
          HKDF-Extract ----> Early Secret ----> binder key、client_early_traffic_secret
                 |
           Derive-Secret("derived")
                 |
   (EC)DHE ----> HKDF-Extract ----> Handshake Secret
                 |                    +--> client_handshake_traffic_secret(CH..SH 的哈希)
                 |                    +--> server_handshake_traffic_secret
           Derive-Secret("derived")
                 |
      全 0 ----> HKDF-Extract ----> Main Secret
                                      +--> client/server_application_traffic_secret_0
                                      |      (CH..服务器 Finished 的哈希)
                                      +--> exporter_secret
                                      +--> resumption_secret -> 票据 PSK

每个流量密钥都绑定在某一时刻的握手哈希上,密钥因此和实际交换的消息一一对应。长连接后续还可以用 KeyUpdate 消息把应用密钥向前滚动更新。

图中用的是 RFC 9846 的命名:它把 RFC 8446 里带 "master" 的叫法改成了 "main" 或更短的名字,Master Secret 改称 Main Secret,exporter_master_secret、resumption_master_secret 改称 exporter_secret、resumption_secret。RFC 9846 还禁止在不同连接之间复用 key share,限制了 KeyUpdate 消息的数量,并把“在达到密钥使用上限之前发起 KeyUpdate”提升为必须遵守的要求。

TLS 1.2 与 TLS 1.3 握手对比

TLS 1.2(RFC 5246,现已被 RFC 9846 取代) TLS 1.3(RFC 8446,现为 RFC 9846)
完整握手往返数 2 1(遇到 HelloRetryRequest 为 2)
会话恢复带数据 1 个往返 可以 0-RTT
密钥交换 RSA、DHE 或 ECDHE,放在 ServerKeyExchange (EC)DHE 或混合算法,放在 key_share;没有静态 RSA
证书对旁观者可见 可见,明文发送 不可见,加密发送
服务器如何证明持有私钥 签 ServerKeyExchange,或用 RSA 解密 CertificateVerify 签整段握手记录
ChangeCipherSpec 必需 只是兼容中间设备的假消息
重协商 支持 删除,由 KeyUpdate 和握手后认证代替

用 OpenSSL 观察握手

openssl s_client -msg 会打印每条握手消息。下面是对一台运行 OpenSSL 3.6、使用 ECDSA 证书的测试服务器的实测输出(去掉了记录头行):

openssl s_client -connect example.com:443 -servername example.com -msg </dev/null 2>&1 | grep -E '^(<<<|>>>)'
>>> TLS 1.3, Handshake [length 0601], ClientHello
<<< TLS 1.3, Handshake [length 04ba], ServerHello
<<< TLS 1.3, ChangeCipherSpec [length 0001]
<<< TLS 1.3, Handshake [length 0006], EncryptedExtensions
<<< TLS 1.3, Handshake [length 0193], Certificate
<<< TLS 1.3, Handshake [length 004f], CertificateVerify
<<< TLS 1.3, Handshake [length 0034], Finished
>>> TLS 1.3, ChangeCipherSpec [length 0001]
>>> TLS 1.3, Handshake [length 0034], Finished

-trace(OpenSSL 编译时带 enable-ssl-trace 才有,Homebrew 版本带了)会逐个解码扩展:

    ClientHello, Length=1533
        extension_type=server_name(0), length=16
        extension_type=supported_groups(10), length=18
        extension_type=signature_algorithms(13), length=54
        extension_type=supported_versions(43), length=5
        extension_type=psk_key_exchange_modes(45), length=2
        extension_type=key_share(51), length=1258
            NamedGroup: X25519MLKEM768 (4588)
            NamedGroup: ecdh_x25519 (29)
    ServerHello, Length=1206
      cipher_suite {0x13, 0x02} TLS_AES_256_GCM_SHA384
        extension_type=supported_versions(43), length=2
        extension_type=key_share(51), length=1124
            NamedGroup: X25519MLKEM768 (4588)

CentOS 7 自带的 OpenSSL 1.0.2 不支持 TLS 1.3,看不到这些消息;macOS 自带的是 LibreSSL,参数也不完全相同。想在 Wireshark 里看加密部分,给 s_client 加 -keylogfile keys.txt,或给浏览器设置 SSLKEYLOGFILE 环境变量,再把文件导入 Wireshark。更多命令见连接测试命令手册。

常见问题

TLS 1.3 握手要几个往返?

完整握手 1 个往返;服务器需要发 HelloRetryRequest 时是 2 个。会话恢复配合 0-RTT 可以在第一个包里就带上应用数据。TLS 1.2 完整握手需要 2 个往返。

TLS 1.3 里服务器证书是加密传输的吗?

是的。Certificate 消息在 ServerHello 之后发送,用握手密钥加密,被动抓包看不到证书链。但 ClientHello 里的 SNI 域名仍是明文,除非启用了 ECH。

抓包看到 TLS 1.3 连接里有 ChangeCipherSpec,正常吗?

正常。这是中间设备兼容模式:双方故意发一条无意义的 ChangeCipherSpec,让连接在老防火墙眼里像 TLS 1.2 的会话恢复。在 TLS 1.3 里它没有任何密码学作用。

怎么用 Wireshark 解密 TLS 1.3 握手?

让客户端导出密钥日志:openssl s_client 加 -keylogfile keys.txt,浏览器或 curl 设置环境变量 SSLKEYLOGFILE,然后在 Wireshark 的 TLS 协议设置里指定这个文件。只在自己的测试环境这样做,密钥日志等同于会话密钥。

资料来源

  1. RFC 9846: TLS 1.3(第 2、4、7 节),2026 年 7 月
  2. RFC 8446: TLS 1.3 最初版本(已被 RFC 9846 取代)
  3. RFC 8448: Example Handshake Traces for TLS 1.3
  4. RFC 5869: HKDF
  5. RFC 8470: Using Early Data in HTTP
  6. RFC 9963: Legacy RSASSA-PKCS1-v1_5 Code Points for TLS 1.3
  7. RFC 5246: TLS 1.2(已被 RFC 9846 取代)
  8. OpenSSL 文档:openssl-s_client