解析 ECH(RFC 9849)的原理:外层与内层 ClientHello、DNS 中的密钥、运营商与 CDN 仍能看到什么,以及如何自查你的连接。
多年来,HTTPS 连接里仍以明文点名网站的,只剩 TLS 握手中的 Server Name Indication(SNI)字段。Encrypted Client Hello(ECH)是标准化的修复方案:浏览器把真正的 ClientHello 加密,再包进一个看似无害的外层 ClientHello 里。但它很容易被夸大。ECH 只是把秘密从“路径上的所有人”缩小到“负责解密的那台服务器”,而且握手中仍可见的部分比多数介绍所说的要多。本文讲清楚机制,并逐方说明每个观察者能读到什么、读不到什么。
核心要点
- ECH 已成为标准。 规范以 RFC 9849 发布(Proposed Standard,2026 年 3 月),此前多年是草案
draft-ietf-tls-esni。 - 一次连接,两个 ClientHello。 浏览器发送一个可读的外层 ClientHello,其 SNI 填的是面向客户端的服务器的
public_name;真正的内层 ClientHello(含真实 SNI 和 ALPN 列表)加密后藏在其中。 - 密钥来自 DNS。 服务器的 ECH 配置发布在 HTTPS/SVCB DNS 记录中(RFC 9460),所以只有这次查询本身不可被读取,ECH 才有意义。RFC 明确指出,不加密 DNS 查询,SNI 加密的价值会大打折扣。
- 网络侧仍可能对外层握手做指纹。 外层是真实可读的 ClientHello,且规范允许客户端把内层的字段复制进去,TLS 库的习惯未必会从线路上消失。
- 解密的服务器能看到一切。 ECH 改变的是网络能看到什么,而不是 CDN 或源站能看到什么。
ECH 要解决的问题
TLS 1.3 加密了服务器证书和大部分握手,但 ClientHello 必须在密钥建立之前就可读。因此告诉服务器你要访问它众多主机名中哪一个的 SNI,只能以明文传输。RFC 称它“可能是 TLS 1.3 中仍未加密的最敏感信息”。更早的需求文档 RFC 8744 列出了此前各种 SNI 加密方案为何难以实现,这也是标准直到 2026 年才定稿的原因。关于 SNI 会向 ISP 或 Wi-Fi 运营方泄露什么,请看使用 HTTPS 时,运营商还能看到什么;本文只讲修复方案如何运作。
外层与内层 ClientHello
启用 ECH 后,浏览器会构造两条消息:
- **ClientHelloInner(内层)**是真正的握手消息:真实 SNI、ALPN 列表(声明 HTTP/2 或 HTTP/3 等协议)以及其他扩展。它用服务器的公钥通过 HPKE(混合公钥加密)加密,并做填充以减少长度泄露。
- ClientHelloOuter(外层)看起来是一条普通的 ClientHello。它的
server_name被设为 ECH 配置中的public_name,即面向客户端的服务器的名字,例如某个 CDN 的共享主机名。加密后的内层消息放在encrypted_client_hello扩展里随之发出。
面向客户端的服务器会尝试解密该扩展。成功则以内层 hello 继续握手,连接通往真实站点;失败(密钥不对,或该扩展只是占位数据)则把外层 hello 当作全部内容,按 public_name 处理。规范描述了两种部署形态:共享模式,同一服务商既是面向客户端的服务器又是源站;分离模式,面向客户端的服务器把解密后的 hello 转发给另一台后端服务器。
RFC 直接写明了一个后果:ECH“会暴露客户端正在连接某个服务提供方,但不会暴露是该匿名集合中的哪台服务器终止了连接”。如果某个站点独占一个 IP,目的 IP 本身就暴露了它。ECH 的效果取决于有多少站点共用同一个入口。
密钥从哪来:DNS
浏览器需要在连接之前拿到服务器的公钥,而它是从 DNS 获取的。服务器把 ECHConfigList(一个或多个 ECHConfig,含密钥、配置 ID 和 public_name)发布在 HTTPS 或 SVCB DNS 记录中,该记录类型由 RFC 9460 定义。
这就形成了依赖链。如果你查询 HTTPS 记录的 DNS 请求是明文的,那么在 ECH 隐藏任何东西之前,你的解析器和路径上的任何人就已经知道了主机名。RFC 还指出,加密 DNS 也能防止本地网络攻击者篡改 ECH 配置,并且 SNI 加密“在不加密 DNS 查询的情况下作用有限”。实践中这意味着 DNS over HTTPS 或 DNS over TLS。设置与测试方法见 DNS 泄露防护。
谁能看到什么:三方观察者对照表
| 路径上的网络(ISP、Wi-Fi) | 面向客户端的服务器(如 CDN) | 其后的源站 | |
|---|---|---|---|
| 目的 IP | 能 | 能 | 能 |
外层 SNI(public_name) | 能 | 能 | 分离模式:不能 |
| 真实 SNI 与内层 ALPN | 不能(已加密) | 解密后能 | 能 |
| 复制进外层 hello 的其他字段 | 能 | 能 | 不适用 |
| 完整内层 ClientHello | 不能 | 能 | 能 |
| 明文 DNS 查询(若使用) | 能 | 不适用 | 不适用 |
有两行值得强调。第一,真实 SNI 对网络隐藏,但对终止连接的一方从不隐藏。ECH 不等于匿名:CDN 仍然知道你访问了它的哪个客户。第二,“其他字段”这一行最容易让人意外。
网络侧的 JA3/JA4 还能读到什么
一种常见的简化说法(本站的 TLS 指纹核心文章也这样写)是:ECH 让观察者只剩一条极简的外层 ClientHello,从而限制指纹识别。RFC 支持前半句,对后半句则更为谨慎。客户端构造外层 hello 时遵循一小组规则(提供 TLS 1.3 或更高版本、复制会话 ID、使用全新的随机数、把 public_name 放进 server_name),然后按原文,“除 ClientHelloInner.random 外,它可以(MAY)复制内层 ClientHello 的任何其他字段”。规范在讨论匿名集合时还补充说,扩展在 TLS 消息中的顺序等实现细节,即便配置一致,也可能造成外部可见行为的差异。
所以外层 hello 是一条真实的 ClientHello:有密码套件、扩展、支持的曲线组和顺序,全都能在线路上读到。被动观察者可以为这条消息计算 JA3 或 JA4 类指纹。变化的是指纹描述的是哪一条 hello,以及哪些字段被隐藏:真实 SNI 和内层 ALPN 列表不再可见,JA4 所记录的 SNI 有无与 ALPN 取值,也来自外层 hello。外层指纹是否像今天一样有区分度,取决于各浏览器的实现,RFC 对此并无保证。因此,“ECH 能击败网络侧 TLS 指纹”尚未得到证明,更准确的说法是“观察者读到的变少了”。(QUIC 同理,见 HTTP/3 与 QUIC 指纹。)
服务器侧则不同。面向客户端的服务器会解密内层 hello,看到完整的客户端指纹,所以在 CDN 层做机器人检测的厂商不会因 ECH 损失任何信息。
GREASE ECH:为什么该扩展本身说明不了什么
即使浏览器没有某个站点的 ECH 配置,也会发送一个 encrypted_client_hello 扩展,里面填满长度合理的随机字节。RFC 称之为 GREASE ECH(思路与 RFC 8701 中的占位值相同)。它的作用是让真正的 ECH 不显眼,使中间设备无法在不破坏普通连接的前提下轻易封锁“ECH 扩展”。无法解密它的服务器会直接忽略。
实际的结论是:在抓包中看到这个扩展,并不能说明连接受到了保护。只有握手成功且 ECH 被接受才算数。规范也坦率地说明,GREASE 与真实 ECH 只对能力有限的被动观察者才难以区分;外层 SNI、配置 ID 等取值仍可能成为区分依据。
失败模式:重试、回退与封锁
- 密钥过期与 retry_configs。 DNS 缓存和密钥轮换会让浏览器手里的密钥过期。服务器无法解密时,会用外层 hello 完成握手,并在加密消息中返回新的
retry_configs。客户端先按public_name校验证书,再用新配置重试。规范要求客户端不要在重试所引发的连接中再次接受retry_configs,因为这意味着服务器配置有误。 ech_required。 如果客户端提供了真正的 ECH,连接最终却没有用上,客户端会在发送应用数据之前中止,而不是悄悄回退到明文 SNI。- 完全没有配置。 如果 DNS 记录里没有 ECH 参数(或 HTTPS 记录被剥离),浏览器就没有可用的加密密钥,只能按旧方式连接;若支持,则会带上 GREASE。此时浏览器报错还是继续,属于产品决策,因此 ECH 应视为尽力而为。
- 不欢迎 ECH 的网络。 RFC 承认,有些运营方可能封锁真正的 ECH 而放行 GREASE;依赖 SNI 做过滤或负载均衡的受管环境,也可能通过策略禁用 ECH。它并未声称 ECH 无法被封锁,只是说它被设计成对被动观察者而言难以与 GREASE 区分。
本文不给出普及率数字,因为单一数字取决于统计哪些站点和客户端。想了解大型 CDN 如何部署以及对运营方意味着什么,可以阅读 Cloudflare 的文章。
如何自查你的连接
任何通过 Cloudflare 提供服务的站点都有诊断页 /cdn-cgi/trace。打开它,看 sni= 这一行:sni=encrypted 表示连接使用了 ECH,sni=plaintext 表示真实服务器名以明文穿过了网络。我们在 2026-10-12 用未启用 ECH 的客户端请求 browserinsight.net/cdn-cgi/trace,返回的是 sni=plaintext,对于没有已发布密钥或未启用 ECH 的客户端,这是预期结果。该检查只适用于由 Cloudflare 提供服务的站点,BrowserInsight 自身的工具并不检测 ECH。
条件满足,结果就会改善:使用支持 ECH 的浏览器,启用加密 DNS,并访问已发布含 ECH 配置的 HTTPS 记录的站点。要了解即便如此仍有什么会暴露,可以用 IP 情报检测 查看服务器看到的地址;无论是否启用 ECH,目的 IP 对网络始终可见。
常见问题
ECH 会向我的 ISP 隐藏我访问的网站吗?
部分隐藏。它隐藏握手中的真实服务器名,但 ISP 仍能看到目的 IP 地址和外层 public_name。对于与许多站点共用 IP 的网站,例如大型 CDN 背后的站点,这确实留下了模糊空间;对于独占 IP 的站点,仅凭地址就能识别。
ECH 能代替 VPN 吗?
不能。VPN 改变的是观察者所处的位置:ISP 只能看到 VPN,而 VPN 服务商能看到原本 ISP 能看到的内容。ECH 对网络隐藏的只是一个字段,目的 IP、流量时序和体量依然可见。
不用加密 DNS,ECH 能工作吗?
能连上,但隐私收益会削弱。密钥是从 DNS 记录中获取的,明文查询会在握手开始之前就把主机名暴露给解析器和路径上的设备。
网站能知道我是否使用了 ECH 吗?
能。面向客户端的服务器知道自己是否解密了 ECH 扩展,CDN 也会对外暴露这一点,其 trace 输出中的 sni= 一行即是例证。该服务器可以完整看到内层 ClientHello。
抓包里出现该扩展,就证明用了 ECH 吗?
不能。浏览器在没有配置时会发送 GREASE ECH,所以未受保护的连接上也可能出现这个扩展。


