土法炼钢兴趣小组的算法知识备份

【密码学百科】TLS 协议全解析:从握手到 0-RTT

目录

当浏览器地址栏亮起那把小锁时,背后正在运行的便是 TLS(Transport Layer Security)协议。从电子商务到即时通信,从 API 调用到物联网遥测,几乎所有需要在不可信网络上传输敏感数据的场景都依赖 TLS 提供机密性、完整性与身份认证。TLS 1.3 在 2018 年由 RFC 8446 正式发布,标志着互联网安全传输协议经历二十余年迭代后达到的最新里程碑。本文将从协议演进史讲起,逐层拆解 TLS 1.3 的握手消息、密钥调度、零往返时间(0-RTT)数据、证书验证、会话恢复等核心机制,并展望 ECH(Encrypted Client Hello)与后量子迁移等前沿议题。

一、TLS 演进史

SSL 2.0:仓促的开端

1995 年,Netscape 公司发布了 SSL 2.0(Secure Sockets Layer),这是业界首个被广泛部署的安全传输协议。然而 SSL 2.0 的设计存在严重缺陷:它使用 MD5 作为唯一的消息摘要算法,握手过程缺少对消息完整性的保护,攻击者可以在中间截断密码套件列表,迫使双方降级到最弱的算法——这便是最早的降级攻击(downgrade attack)。此外,SSL 2.0 的密钥派生过程过于简单,加密与认证使用同一把密钥,违反了密钥分离(key separation)原则。这些教训在后续版本中反复被引述:协议设计必须假设攻击者可以篡改握手消息中的任何明文字段。

SSL 3.0:奠定框架

1996 年发布的 SSL 3.0 对协议进行了全面重写。它引入了更完善的握手状态机、独立的 MAC 密钥与加密密钥、以及用于防止降级的 Finished 消息——该消息是对整个握手转录(transcript)的哈希校验,任何对握手消息的篡改都会导致 Finished 验证失败。SSL 3.0 的框架极具影响力,后续的 TLS 1.0 至 TLS 1.2 在结构上都是它的继承者。但 SSL 3.0 本身也未能幸免于密码学攻击:2014 年披露的 POODLE 攻击利用了 CBC 模式填充验证(padding oracle)的弱点,宣告了 SSL 3.0 的终结。

TLS 1.0 与 TLS 1.1:渐进修补

TLS 1.0(RFC 2246,1999 年)本质上是 SSL 3.0 的标准化版本,改动幅度有限。它将密钥派生中的自定义伪随机函数替换为基于 HMAC 的 PRF(pseudorandom function),但仍保留了 CBC 模式中可预测的初始化向量(IV),这一缺陷后来被 BEAST 攻击所利用。TLS 1.1(RFC 4346,2006 年)修复了 IV 预测问题,要求每条记录使用显式随机 IV,但在密码套件与握手流程方面未做根本性变革。

TLS 1.2:成熟但臃肿

TLS 1.2(RFC 5246,2008 年)是一次重要升级。它引入了协商哈希算法的能力——此前版本强制使用 MD5 与 SHA-1 的组合——并为 AEAD(Authenticated Encryption with Associated Data)密码套件铺平了道路,其中 AES-GCM 成为最受推荐的选择。TLS 1.2 支持丰富的扩展机制,功能强大却也日趋臃肿:它允许 RSA 密钥传输(不提供前向保密)、静态 Diffie-Hellman 参数、CBC 密码套件、记录层压缩(为 CRIME 攻击提供了条件)、以及重协商(renegotiation)机制。这些可选特性的组合爆炸使得实现者难以全面覆盖测试,也让攻击面居高不下。

TLS 1.3:大刀阔斧的精简

TLS 1.3 的设计哲学可以用一个词概括:做减法。它移除了所有不提供前向保密的密钥交换方式,移除了 CBC 密码套件,移除了压缩,移除了重协商,将握手往返次数从两次缩减为一次(1-RTT),并引入了零往返恢复(0-RTT)模式。同时,握手消息在 ServerHello 之后全部加密传输,极大地缩小了被动监听者能获取的元数据。TLS 1.3 的开发历程长达四年,经过了 28 个草案迭代,并受到形式化验证工具(如 Tamarin 和 ProVerif)的检验,是密码学协议工程的典范。

二、TLS 1.3 全握手

TLS 1.3 的完整握手(full handshake)仅需一个往返时间(1-RTT)即可完成密钥协商与身份认证。以下是消息流的逐步解析。

客户端                                              服务端
  |                                                    |
  |  ClientHello                                       |
  |  + supported_versions(1.3)                         |
  |  + key_share(g^x)                                  |
  |  + signature_algorithms                            |
  |  + supported_groups                                |
  |  + psk_key_exchange_modes [可选]                    |
  |  + pre_shared_key [可选]                            |
  | -------------------------------------------------> |
  |                                                    |
  |                            ServerHello             |
  |                            + supported_versions    |
  |                            + key_share(g^y)        |
  |                   {EncryptedExtensions}             |
  |                   {CertificateRequest*}             |
  |                   {Certificate}                     |
  |                   {CertificateVerify}               |
  |                   {Finished}                        |
  | <------------------------------------------------- |
  |                                                    |
  |  {Certificate*}                                    |
  |  {CertificateVerify*}                              |
  |  {Finished}                                        |
  | -------------------------------------------------> |
  |                                                    |
  |  [Application Data] <==========> [Application Data]|
  |                                                    |

  () = 明文   {} = 握手密钥加密   [] = 应用密钥加密
  *  = 可选消息(取决于是否要求客户端证书)

ClientHello

客户端发送的第一条消息包含以下关键字段。supported_versions 扩展声明客户端支持 TLS 1.3;这一设计取代了旧版本中利用记录层版本号进行协商的方式,避免了中间设备因不认识新版本号而错误地丢弃连接的兼容性问题。key_share 扩展包含一个或多个临时(ephemeral)Diffie-Hellman 公钥,客户端在发送 ClientHello 时便主动提供密钥材料,这是 TLS 1.3 能够在一个往返内完成握手的关键——在 TLS 1.2 中,密钥交换参数要等到第二轮消息才会出现。signature_algorithms 告知服务端客户端支持哪些签名算法,用于验证服务端证书链。supported_groups 列出客户端支持的椭圆曲线或有限域群。如果客户端持有来自先前连接的预共享密钥(PSK),还可以携带 pre_shared_key 扩展实现会话恢复或 0-RTT。

ServerHello

服务端从客户端提供的选项中选定密码套件与密钥交换参数后回复 ServerHello。该消息包含服务端的临时 DH 公钥 key_share(g^y),客户端与服务端此时各自可以计算出共享密钥 g^{xy}。从 ServerHello 之后,所有后续握手消息都将使用从该共享密钥派生的握手流量密钥(handshake traffic key)进行加密。这意味着 EncryptedExtensions、Certificate、CertificateVerify、Finished 等消息对被动窃听者完全不可见——在 TLS 1.2 中,这些消息全部以明文传输。

值得注意的是,如果服务端不支持客户端在 key_share 中提供的任何群,它会发送一条 HelloRetryRequest 消息,指定一个它支持的群,要求客户端重新发送 ClientHello,此时握手退化为 2-RTT,但这种情况在实践中并不常见。

EncryptedExtensions

EncryptedExtensions 消息承载那些不需要用于密码套件协商但又属于握手参数的扩展,例如服务器名称指示(SNI)的确认、应用层协议协商(ALPN)的结果等。它之所以单独成为一条消息而不合并到 ServerHello 中,正是因为这些扩展需要加密保护——ServerHello 本身必须保持明文以完成密钥交换。

Certificate 与 CertificateVerify

Certificate 消息携带服务端的证书链。CertificateVerify 消息是 TLS 1.3 安全性的基石之一:服务端使用其证书私钥对整个握手转录的哈希值进行签名。这里的转录哈希(transcript hash)涵盖了从 ClientHello 到 Certificate 的所有消息。由于 ClientHello 中包含了客户端的随机数和密钥份额,而 ServerHello 中包含了服务端的随机数和密钥份额,因此该签名将服务端的身份与本次特定的密钥交换绑定在一起。任何中间人(MITM)如果试图替换密钥份额,就无法生成有效的 CertificateVerify 签名,因为它不持有服务端证书对应的私钥。

与 TLS 1.2 相比,TLS 1.3 的 CertificateVerify 签名对象是转录哈希,而非仅对密钥交换参数签名,这避免了 TLS 1.2 中存在的跨协议攻击(cross-protocol attack)风险。此外,签名前会加上一段固定的上下文字符串与 0x20 填充,以防止签名被重新用于其他目的。

Finished

Finished 消息是对整个握手转录的 HMAC 校验,使用从握手密钥派生的专用密钥(finished_key)计算。客户端和服务端各自发送一条 Finished 消息。服务端的 Finished 证明它确实参与了握手并持有正确的密钥材料;客户端的 Finished 完成握手确认。在双方都验证了对方的 Finished 之后,握手结束,后续通信切换到应用流量密钥(application traffic key)。

三、密钥调度

TLS 1.3 的密钥调度(key schedule)是协议中最精妙的设计之一,它基于 HKDF(HMAC-based Key Derivation Function,RFC 5869)构建了一条清晰的密钥派生链,实现了密钥分离与前向保密。

HKDF 基础

HKDF 包含两个阶段:提取(Extract)与扩展(Expand)。HKDF-Extract 接受一个盐(salt)和输入密钥材料(IKM),输出一个伪随机密钥(PRK)。HKDF-Expand-Label 在 TLS 1.3 中是 HKDF-Expand 的包装,接受 PRK、一个标签(label)、一个上下文(context)和输出长度,产生派生密钥。标签与上下文的引入确保了即使从同一个 PRK 出发,用于不同目的的密钥也彼此独立——这就是密钥分离原则在实践中的体现。

三级密钥演进

TLS 1.3 的密钥调度沿三级结构演进:

                 0(全零值)
                    |
                    v
PSK -------> HKDF-Extract = Early Secret
                    |
          +--------+--------+
          |                 |
          v                 v
   binder_key       client_early_traffic_secret
                    (用于 0-RTT 数据)
                    |
                    v
(EC)DHE ----> HKDF-Extract = Handshake Secret
                    |
          +--------+--------+
          |                 |
          v                 v
  client_handshake     server_handshake
  _traffic_secret      _traffic_secret
  (加密客户端握手)     (加密服务端握手)
                    |
                    v
    0 --------> HKDF-Extract = Master Secret
                    |
          +--------+--------+
          |                 |
          v                 v
  client_app           server_app
  _traffic_secret      _traffic_secret
  (加密应用数据)       (加密应用数据)

第一级是 Early Secret,由 PSK(如果存在)与全零盐经 HKDF-Extract 生成。如果没有 PSK,则使用全零值替代。Early Secret 派生出用于 0-RTT 数据加密的 client_early_traffic_secret 以及用于 PSK 验证的 binder_key

第二级是 Handshake Secret,将 Early Secret 的派生值作为盐,与 (EC)DHE 共享密钥一起输入 HKDF-Extract。它派生出客户端握手流量密钥和服务端握手流量密钥,分别用于加密双方在握手阶段发送的消息。

第三级是 Master Secret,以 Handshake Secret 的派生值为盐,输入全零值经 HKDF-Extract 生成。它派生出应用数据的读写密钥,以及用于会话恢复的 resumption_master_secret。

密钥分离的安全意义

这一设计的核心优势在于密钥分离:即使攻击者获取了某一层的密钥,也无法推导出其他层的密钥。例如,泄露应用流量密钥不会危及握手消息的机密性,反之亦然。同时,由于每次握手都使用临时 DH 密钥对,即使长期私钥(证书私钥)被泄露,过去的会话记录也无法被解密——这便是前向保密(forward secrecy)的保障。在 TLS 1.2 中使用 RSA 密钥传输时,服务端的 RSA 私钥泄露将导致所有历史会话可被解密,TLS 1.3 从根本上消除了这一风险。

密钥更新

TLS 1.3 还支持在连接存续期间通过 KeyUpdate 消息触发密钥更新。新的应用流量密钥由旧密钥经 HKDF-Expand-Label 派生而来,无需重新握手。这一机制在长寿命连接中限制了单把密钥加密的数据量,降低了密钥磨损风险。

四、0-RTT 数据

设计动机

在现代互联网架构中,延迟是用户体验的关键指标。传统 TLS 握手需要至少一个往返时间(约数十到数百毫秒)才能开始传输应用数据。对于频繁重连的场景——例如移动设备在 Wi-Fi 与蜂窝网络之间切换、CDN 边缘节点的短连接——每次握手的延迟都意味着可感知的卡顿。0-RTT(zero round-trip time)模式允许客户端在发送 ClientHello 的同时附带早期数据(early data),服务端无需等待完整握手即可处理这些数据,从而将有效延迟降为零。

工作机制

0-RTT 的前提是客户端持有来自先前连接的 PSK。客户端利用 PSK 派生 Early Secret,再派生 client_early_traffic_secret,用它加密早期数据(通常是 HTTP 请求的第一个报文)。这些数据随 ClientHello 一起发送。服务端收到后,如果能找到对应的 PSK,即可立刻解密并处理这些早期数据,同时并行完成握手。

客户端                                              服务端
  |                                                    |
  |  ClientHello + key_share + pre_shared_key          |
  |  (early_data 指示)                                 |
  |  [0-RTT 应用数据 — 使用 early traffic key 加密]     |
  | -------------------------------------------------> |
  |                                                    |
  |                            ServerHello             |
  |                   {EncryptedExtensions}             |
  |                     (early_data 接受/拒绝)          |
  |                   {Finished}                        |
  | <------------------------------------------------- |
  |                                                    |
  |  {Finished}                                        |
  | -------------------------------------------------> |
  |                                                    |
  |  [Application Data] <==========> [Application Data]|

重放攻击风险

0-RTT 数据面临一个根本性的安全限制:它不具备抗重放性(replay resistance)。在标准的 1-RTT 握手中,服务端的 ServerHello 提供了新鲜的随机值,确保每次握手唯一;但 0-RTT 数据在收到服务端回复之前就已发出,缺少服务端贡献的新鲜性。攻击者可以捕获并重放包含 0-RTT 数据的 ClientHello,如果服务端不加防范,就会重复处理这些请求。

想象一个场景:客户端通过 0-RTT 发送了一笔转账请求。攻击者录制了该网络报文并多次重发给服务端,每次重发都可能触发一笔新的转账。这便是 0-RTT 重放攻击的典型危害。

反重放机制

TLS 1.3 规范建议了两种反重放策略。第一种是一次性票据(single-use ticket):服务端为每张会话票据维护一个使用标记,一旦 PSK 被用于 0-RTT 就立即作废。这种方案在分布式环境中实现成本较高,因为多台服务器需要共享状态。第二种是时间窗口检测(client hello recording):服务端记录一个时间窗口内收到的所有 ClientHello 的哈希值,拒绝重复的请求。两种方案各有权衡,但无论采用哪种,应用层也应当对 0-RTT 数据实施幂等性检查——仅允许幂等操作(如 HTTP GET)通过 0-RTT 发送,将非幂等操作(如 POST、PUT)延迟到握手完成后再发送。

何时使用 0-RTT

总结而言,0-RTT 适合以下场景:请求本身是幂等的;服务端部署了有效的反重放机制;延迟敏感度高于安全敏感度。对于金融交易、状态变更等操作,绝不应通过 0-RTT 发送。RFC 8446 明确指出,0-RTT 数据的安全属性弱于 1-RTT 数据,协议实现者和应用开发者都必须充分理解这一权衡。

笔者认为,0-RTT 的重放困境是整个安全协议设计领域一个更深层张力的微缩样本:每一个成熟的网络协议,最终都会面临”你愿意用多少安全性来换取一个往返时间”这一根本性问题。TCP Fast Open 面临过它,QUIC 面临过它,DNS over HTTPS 正在面临它。0-RTT 的独特价值在于,TLS 1.3 的设计者没有试图假装这个问题有完美解答——他们选择了透明地暴露权衡:0-RTT 数据明确标记为安全属性弱于 1-RTT 数据,抗重放责任被显式地上推到应用层(幂等性检查)。这种”承认不完美并明确界定边界”的设计哲学,比试图在协议层面隐藏所有复杂性要诚实得多,也安全得多。此外,TLS 1.3 长达七年、历经 28 个草案版本的标准化过程本身就值得深思。这一过程中捕获了三次握手攻击(Triple Handshake)、DROWN、以及多个降级攻击的变种——这些问题在较短的审查周期中几乎不可能被发现。从工程实践来看,密码协议标准化的”慢”恰恰是其最重要的安全特性:每一个额外的草案版本都意味着更多的密码分析者有时间从更多的角度审视协议。快速发布的”安全”协议几乎总是在部署后才暴露致命缺陷——WEP 用了不到两年就被彻底攻破。TLS 1.3 用七年的耐心换来了一个在部署数年后仍未出现重大安全缺陷的协议,这笔交易无论怎么算都是值得的。

五、TLS 1.3 vs TLS 1.2

TLS 1.3 相对于 TLS 1.2 的改进可以从”移除”与”新增”两个维度来理解。

移除的特性

RSA 密钥传输:在 TLS 1.2 中,客户端可以直接用服务端的 RSA 公钥加密预主密钥(pre-master secret),这种方式不提供前向保密——一旦 RSA 私钥泄露,所有使用该密钥协商的历史会话都可被解密。TLS 1.3 强制要求使用临时 (EC)DHE 密钥交换,从协议层面保证了前向保密。

静态 Diffie-Hellman:类似地,TLS 1.2 允许使用固定的 DH 参数,同样不提供前向保密。TLS 1.3 仅允许临时密钥。

CBC 密码套件:CBC 模式在 TLS 中的历史充满了填充预言攻击(padding oracle attack),从 POODLE 到 Lucky Thirteen,每一次攻击都源于 MAC-then-encrypt 架构下填充验证的时序泄露。TLS 1.3 彻底移除了 CBC 模式,仅保留 AEAD 算法。

压缩:TLS 记录层压缩被 CRIME 和 BREACH 攻击利用,通过观察压缩后密文长度的变化来推断明文内容。TLS 1.3 移除了协议层压缩。

重协商:TLS 1.2 的重协商机制曾被用于注入攻击(renegotiation attack),虽然后来通过 RFC 5746 打了补丁,但 TLS 1.3 选择从根本上移除这一机制,用 KeyUpdate 和 post-handshake 认证来替代其合法用途。

自定义 DHE 群:TLS 1.2 允许服务端发送任意 DH 参数,曾导致 Logjam 攻击——攻击者利用小群进行离散对数预计算,然后实时降级连接。TLS 1.3 仅允许使用标准化的命名群(named groups)。

新增与改进

握手消息加密、1-RTT 握手、0-RTT 恢复、HKDF 密钥调度、简化的密码套件协商——这些改进在前文已详细讨论。此外,TLS 1.3 将 ChangeCipherSpec 消息降格为兼容性占位符(仅在 middlebox compatibility mode 中发送),不再承担实际的密码状态切换功能,进一步简化了状态机。

从工程实践来看,TLS 1.3 最深远的贡献或许不是任何单一的安全改进,而是它对复杂性的激进削减。TLS 1.2 支持数百种密码套件组合这一事实本身就是一个安全漏洞——不是因为其中每个组合都不安全,而是因为组合爆炸使得穷举测试所有交互路径变得不可能。每增加一个密码套件选项,实现中的代码路径数量就可能翻倍,而安全漏洞往往藏在那些极少被执行、因此极少被测试的代码路径中。POODLE 攻击利用的是 CBC 回退路径,FREAK 利用的是出口级密码套件路径,Logjam 利用的是小群 DH 路径——这三个攻击都发生在”理论上不应该被使用”但”实际上必须被实现”的边缘代码中。TLS 1.3 将密码套件从数百个压缩到五个,这一决策消灭的不仅是弱算法,更是所有为支持弱算法而存在的代码路径。在安全工程中,最有效的防御往往不是加固现有代码,而是删除不需要存在的代码。

六、密码套件简化

TLS 1.2 定义了数百种密码套件组合,涵盖密钥交换算法、认证算法、对称加密算法和哈希算法的笛卡尔积。这种灵活性在理论上允许细粒度的安全策略定制,但实践中带来了灾难性的复杂度:实现者难以正确支持所有组合,配置者难以选择安全的子集,攻击者则可以利用降级攻击迫使双方选择最弱的组合。

TLS 1.3 采取了截然不同的策略:密码套件仅指定 AEAD 算法与哈希算法,密钥交换与认证算法独立协商。RFC 8446 定义的密码套件仅有五个:

密码套件 AEAD 算法 哈希算法
TLS_AES_128_GCM_SHA256 AES-128-GCM SHA-256
TLS_AES_256_GCM_SHA384 AES-256-GCM SHA-384
TLS_CHACHA20_POLY1305_SHA256 ChaCha20-Poly1305 SHA-256
TLS_AES_128_CCM_SHA256 AES-128-CCM SHA-256
TLS_AES_128_CCM_8_SHA256 AES-128-CCM-8 SHA-256

这五个套件全部基于 AEAD,确保加密与认证一体化完成,消除了 MAC-then-encrypt 带来的各种攻击面。AES-GCM 与 ChaCha20-Poly1305 覆盖了主流硬件平台的性能需求:AES-GCM 在具备 AES-NI 指令集的处理器上性能优异,而 ChaCha20-Poly1305 在缺乏硬件加速的移动设备和嵌入式平台上表现更佳。CCM 模式主要面向物联网等资源受限环境。

从安全角度看,密码套件的大幅缩减意味着:每一种组合都经过了充分的密码学分析和实现审计;降级攻击的收益大大降低(所有可选项都是强算法);配置错误的概率显著下降。这是”少即是多”原则在安全协议设计中的经典案例。

七、证书验证

CertificateVerify 的核心作用

在 TLS 握手中,服务端需要向客户端证明自己确实是证书所声称的实体。仅仅出示证书是不够的——证书是公开信息,任何人都可以获取。关键在于证明持有证书对应的私钥。CertificateVerify 消息正是实现这一证明的机制。

签名内容

TLS 1.3 的 CertificateVerify 签名覆盖以下结构:一段由 64 个 0x20 字节组成的填充、一个上下文字符串(服务端为 “TLS 1.3, server CertificateVerify”,客户端为 “TLS 1.3, client CertificateVerify”)、一个 0x00 分隔符、以及当前握手转录的哈希值。转录哈希从 ClientHello 开始,一直到 CertificateVerify 之前的所有握手消息。

这一设计有几重安全考量。首先,转录哈希将签名与本次特定握手绑定,防止签名被跨会话重用。其次,上下文字符串区分了客户端与服务端的签名,防止一方的签名被伪装为另一方。再次,0x20 填充确保签名输入不会以任何 TLS 消息类型的字节值开头,防止与其他协议的签名方案产生混淆(cross-protocol confusion)。

防降级保护

CertificateVerify 签名覆盖了整个握手转录,这意味着如果中间人试图修改 ClientHello 中的 supported_versions 扩展来将连接降级到 TLS 1.2,转录哈希将不匹配,CertificateVerify 验证将失败。此外,TLS 1.3 在 ServerHello 的 random 字段末尾嵌入了特殊的哨兵值(sentinel value):如果服务端支持 TLS 1.3 但被迫协商 TLS 1.2 或更低版本,它会在 ServerHello.random 的最后 8 字节填入预定义值,客户端据此检测降级攻击并中止连接。这种多层防御策略使得 TLS 1.3 对降级攻击具有极强的抵抗力。

为何这能防止中间人攻击

中间人攻击的核心在于攻击者在客户端与服务端之间分别建立两个独立的 TLS 连接,伪装成对方。然而在 TLS 1.3 中,CertificateVerify 要求签名者对包含 DH 密钥份额的转录进行签名。攻击者无法同时控制两侧的 DH 密钥份额并生成有效签名——因为它不持有合法服务端的私钥。即使攻击者拥有一张由受信任 CA 签发的其他域名证书,转录哈希中包含的 SNI 扩展也会暴露域名不匹配。

八、会话恢复与 PSK

NewSessionTicket

TLS 1.3 的会话恢复不再使用 TLS 1.2 中的会话 ID(session ID)或会话票据(session ticket)的旧机制,而是统一通过 PSK(Pre-Shared Key)实现。在完成握手后,服务端可以向客户端发送一条或多条 NewSessionTicket 消息。每张票据包含一个不透明的票据值(ticket value)、一个过期时间(ticket_lifetime)和一个随机数(ticket_nonce)。客户端使用 resumption_master_secretticket_nonce 经 HKDF-Expand-Label 派生出与该票据关联的 PSK,并在后续连接的 ClientHello 中通过 pre_shared_key 扩展发送票据标识。

PSK 恢复模式

客户端在 psk_key_exchange_modes 扩展中声明它支持的 PSK 使用方式:psk_ke(仅 PSK)或 psk_dhe_ke(PSK + (EC)DHE)。纯 PSK 模式不执行 Diffie-Hellman 密钥交换,因此不提供前向保密——如果 PSK 泄露,使用该 PSK 的所有会话都将暴露。PSK + (EC)DHE 模式则在 PSK 的基础上额外执行临时密钥交换,即使 PSK 日后泄露,会话密钥仍然安全。因此,RFC 8446 强烈建议实现者优先使用 psk_dhe_ke 模式。

票据的实现策略

服务端可以选择两种票据实现策略。第一种是自加密票据(self-encrypted ticket):服务端使用只有自己知道的密钥加密会话状态,将密文作为票据值发送给客户端。恢复时,服务端解密票据即可恢复状态,无需维护服务端存储。这种方案适合无状态的分布式部署。第二种是数据库查找(database lookup):票据值仅为一个索引,实际状态存储在服务端的分布式缓存中。这种方案对票据撤销更友好。

与 0-RTT 的关系

PSK 是 0-RTT 数据的前提。只有当客户端持有有效的 PSK 且服务端在 NewSessionTicket 中设置了 max_early_data_size 参数时,客户端才能发送 0-RTT 数据。PSK 的存在使得客户端在握手开始前就能派生 Early Secret,进而加密早期数据。但如前所述,纯 PSK 模式的 0-RTT 数据不具备前向保密性,这进一步强调了使用 psk_dhe_ke 的重要性。

全握手与恢复握手并列时序图

以下将 TLS 1.3 的三种握手模式并列对照——从完整的 1-RTT 全握手,到 PSK 恢复握手,再到 0-RTT 早期数据——可以直观地看出每种模式在往返次数、消息数量和安全属性上的递进权衡:

(1) 完整 1-RTT 握手                 (2) PSK 恢复握手                 (3) 0-RTT + PSK 握手
(首次连接,无 PSK)                 (持有 PSK,1-RTT)              (持有 PSK,0-RTT 早期数据)

Client           Server             Client           Server          Client           Server
  |                |                  |                |               |                |
  | ClientHello    |                  | ClientHello    |               | ClientHello    |
  | + key_share    |                  | + key_share    |               | + key_share    |
  | + sig_algs     |                  | + psk_ke_modes |               | + psk_ke_modes |
  | -------------> |                  | + pre_shared_  |               | + pre_shared_  |
  |                |                  |   key          |               |   key          |
  |                |                  | + early_data   |               | + early_data   |
  |   ServerHello  |                  | -------------> |               | -------------> |
  |   + key_share  |                  |                |               |                |
  |  {Encrypted    |                  |   ServerHello  |               | (早期数据)      |
  |   Extensions}  |                  |   + key_share  |               | [App Data]     |
  |  {Certificate  |                  |   + pre_shared_|               | -------------> |
  |   Request*}    |                  |     key        |               |                |
  |  {Certificate} |                  |  {Encrypted    |               |   ServerHello  |
  |  {Certificate  |                  |   Extensions}  |               |   + key_share  |
  |   Verify}      |                  |  {Finished}    |               |   + pre_shared_|
  |  {Finished}    |                  | <------------- |               |     key        |
  | <------------- |                  |                |               |  {Encrypted    |
  |                |                  |  {Finished}    |               |   Extensions}  |
  |  {Certificate*}|                  | -------------> |               |  {Finished}    |
  |  {Certificate  |                  |                |               | <------------- |
  |   Verify*}     |                  | [App Data] <=> |               |                |
  |  {Finished}    |                  |                |               |  {End Of Early |
  | -------------> |                  |                |               |   Data}        |
  |                |                  |                |               |  {Finished}    |
  | [App] <=> [App]|                  |                |               | -------------> |
  |                |                  |                |               |                |
                                                                       | [App] <=> [App]|

往返次数: 1-RTT                      往返次数: 1-RTT                  往返次数: 0-RTT(早期数据)
消息数量: 最多 9 条                   消息数量: 5 条                   消息数量: 7 条
身份认证: 证书签名                    身份认证: PSK                    身份认证: PSK
前向保密: 完整(DHE)                 前向保密: 完整(PSK+DHE)         前向保密: 仅 1-RTT 数据
重放保护: 完整                        重放保护: 完整                   重放保护: 需额外机制

() = 明文   {} = 握手密钥加密   [] = 应用密钥加密   * = 可选消息

从这三张并列的时序图中,我想分享一个常被忽视的观察:TLS 1.3 握手简化带来的安全增益,可能比其密码学改进本身还要深远。TLS 1.2 的全握手涉及至少十几种消息组合和复杂的状态机转换——ChangeCipherSpec 的时序、重协商的嵌套、多种密钥交换模式的笛卡尔积——每一个额外的状态都是一个潜在的实现漏洞。POODLE、Lucky Thirteen、FREAK、Logjam——这些攻击无一不是从协议状态机的复杂性缝隙中钻出来的。TLS 1.3 将状态数压缩到最小,不是因为极简主义的美学偏好,而是因为每一个被移除的状态,都是一个永远不会再被利用的攻击面。这或许是安全协议设计中最反直觉的教训:真正的安全提升,往往不是来自增加新的防御机制,而是来自移除不必要的复杂性。

九、ECH 与 TLS 的未来

SNI 隐私问题

TLS 1.3 加密了大部分握手消息,但 ClientHello 中的服务器名称指示(SNI)扩展仍以明文传输。SNI 告诉服务端客户端想要连接的域名,以便在同一 IP 地址托管多个 HTTPS 站点时选择正确的证书。然而,这也意味着网络路径上的任何观察者——ISP、防火墙、政府审查机构——都可以看到用户正在访问哪个网站,即使无法看到具体内容。

从 ESNI 到 ECH

IETF 最初提出了加密 SNI(Encrypted SNI,ESNI)方案,通过将 SNI 字段用服务端发布在 DNS 中的公钥加密来保护域名隐私。然而,仅加密 SNI 而留下其他 ClientHello 字段(如 ALPN)明文传输,可能泄露足够的侧信道信息来推断目标域名。因此,ESNI 演进为 ECH(Encrypted Client Hello),将整个 ClientHello 分为外层(ClientHelloOuter)和内层(ClientHelloInner)。外层包含一个不敏感的”掩护” SNI(通常是 CDN 的域名),内层包含真实的 SNI 及所有敏感扩展,并使用 HPKE(Hybrid Public Key Encryption)加密。服务端解密内层后按真实 SNI 处理请求。

ECH 的部署依赖 DNS over HTTPS(DoH)或 DNS over TLS(DoT)来安全地分发 ECH 公钥——如果 DNS 查询本身被劫持,攻击者可以替换公钥从而解密内层 ClientHello。这意味着 ECH 的隐私保障建立在整个链路(DNS 查询 + TLS 握手)的安全性之上。

形式化验证

TLS 1.3 是历史上受形式化验证检验最为深入的网络协议之一。在协议设计阶段,研究者使用 Tamarin Prover 和 ProVerif 等符号化验证工具对握手协议的安全属性进行了建模与证明,包括认证性(authentication)、机密性(secrecy)、前向保密(forward secrecy)和抗降级性(downgrade resistance)。这些验证覆盖了各种模式组合——全握手、PSK 恢复、0-RTT——并发现了若干早期草案中的微妙缺陷,推动了协议的修正。此外,密码学研究者还对 TLS 1.3 的密钥调度进行了计算安全性证明(computational proof),在标准密码学假设下证明了其满足多阶段密钥交换(multi-stage key exchange)的安全定义。

形式化方法的引入代表了协议工程的范式转变:从”设计→实现→发现攻击→打补丁”的被动循环,转向”设计→验证→实现”的主动防御。TLS 1.3 的成功经验正在影响其他协议的设计,例如 MLS(Messaging Layer Security)和 Noise 框架。

后量子迁移

量子计算机对 TLS 的威胁主要体现在两个方面:Shor 算法可以攻破基于整数分解和离散对数的密钥交换与签名算法(RSA、ECDH、ECDSA);Grover 算法对对称密钥算法的安全强度减半(AES-128 降至 64 位安全)。TLS 1.3 的模块化设计为后量子迁移提供了良好的基础:密钥交换算法在 supported_groupskey_share 扩展中协商,签名算法在 signature_algorithms 扩展中协商,两者均可独立替换为后量子方案。

目前,IETF 正在标准化混合密钥交换(hybrid key exchange)方案,将传统的 ECDH 与后量子 KEM(如 ML-KEM,前称 Kyber)组合使用,确保即使其中一个算法被攻破,连接仍然安全。混合方案的 ClientHello 会携带更大的 key_share 扩展,可能面临 UDP 分片或 TCP 初始窗口不足等工程挑战。后量子签名算法(如 ML-DSA,前称 Dilithium)的签名和公钥尺寸也显著大于 ECDSA,对证书链传输和握手延迟构成压力。

TLS 的未来注定是一场在安全性、性能、兼容性和隐私之间持续博弈的演化过程。从 SSL 2.0 的草创到 TLS 1.3 的精炼,再到 ECH 的隐私增强与后量子的算法迁移,每一步都在回应同一个核心命题:如何在开放的互联网上建立可信赖的安全通道。协议设计者二十余年积累的教训——密钥分离、前向保密、最小化可选性、转录绑定、形式化验证——构成了这一领域最宝贵的工程智慧,也将继续指引下一代安全传输协议的演进方向。


密码学百科系列 · 第 22 篇

← 上一篇:填充方案 | 系列目录 | 下一篇:PKI 与数字证书


By .