ESNI 功能简要概述
0. 背景
目前访问网站时,基本上都会采用TLS加密的方式,用于保护数据的安全性。但是为了例如 CDN 等需要在同一个机器上部署多个网站的服务,那么 SNI(Server Name Indication)将会被使用。相关流程如下图所示

可以明确的看到 SNI 是明文传输的。这样不怀好意的人就可以通过监听 SNI 来获取用户访问了什么网站。
1. 解决方案
1.1 思路1:不发送SNI
既然SNI是明文传输的,那么不发送 SNI 似乎就可以解决这个问题。事实上,确实有项目做到了这一点,例如revolter-firefox。这个项目可以正常访问部分网站。
但正如前文所说,如果一个服务器上部署了多个网站,那么服务端需要根据 SNI 来选择对应的网站。如果不发送 SNI,那么服务端就无法知道用户想要访问哪个网站,进而无法正确响应证书。
1.2 思路2:加密SNI
既然 SNI 是明文传输的,那么我们可以对 SNI 进行加密,这样就可以解决这个问题。这就是 ESNI(Encrypted SNI)的思路。
2. ESNI 工作原理
首先我们肯定会优先想到使用网站证书对 SNI 进行加密。但很可惜,在未收到 SNI 的条件下,服务端根本无法知道用户想要访问哪个网站,进而无法正确响应证书用于加密 SNI。
这就形成了一个悖论:服务端需要 SNI 来选择正确的证书提供给客户端,但是传输 SNI 需要服务端先提供客户端正确的证书。
破局方法就是与很多技术相近,即使用DNS。
服务器在已知的DNS记录上发布一个公钥,客户端可以在连接之前获取该公钥。而后客户端使用该公钥对 SNI 进行加密,然后发送给服务器。服务器解密获得 SNI,然后选择正确的证书提供给客户端。
这样就解决了悖论,同时也解决了 SNI 明文传输的问题。

3. 新的问题
这样会有什么问题?在查询对方服务器 DNS 记录的时候,由于 DNS 请求是明文传输的,那么就会暴露用户访问的网站。
为了解决这个问题,可以使用DNS over HTTPS(DoH)或者DNS over TLS(DoT)来加密 DNS 请求。
4. 进阶
TLS的ClientHello还有很多可能暴露信息的地方,因此考虑将整个ClientHello都加密,这就是 ECH(Encrypted Client Hello)。相关内容可以参考这篇文章。