在准备写上一篇《反代加速的秘密》时想起域前置和ECH很像,于是就决定再写一篇ECH的。

之前我看到了Cloudflare的一篇文章,文章提到了之前的关闭的ECH功能重新开放了,我还以为这是灰度测试,可能要很久才能对我的博客开放,毕竟一段时间前ECH的开关还是不可用的状态。去Cloudflare控制面板一看,ECH的开关好像是找不到了,是默认开启了?我用开启浏览器的ECH访问一下,果然,Clash的控制面板里已经看不到teapotium.com了,反而多了一个cloudflare-ech.com

ECH的用途是?

SNI,服务器名称指示(Server Name Indication),CDN服务器利用SNI的内容决定向客户端发送哪一个网站的证书,简单来说就是CDN利用SNI的内容确定你要访问的是哪一个网站。

ECH全称 Encrypted Client Hello ,ECH是为了解决TLS流量中明文SNI带来的隐私问题,ECH和它的前辈ESNI不同,ESNI只加密了SNI,而ECH则加密了包括SNI在内的整个Client Hello,已经把TLS的全部明文弱点都解决了,目前开启ECH方法是在浏览器中配置DoH类型的DNS服务器,不同浏览器的配置方法略有不同。

ESNI,加密的SNI

想要加密SNI的前提是客户端先获取到用于加密的公钥(你可以认为是一个只有你的收件人才能打开的锁,用来把你的数据锁住),在ESNI设计时选择靠DNS分发这个公钥,不过通过明文的DNS请求记录就能推断出目标网站了,所以需要使用DoH这样的加密请求才行。

ESNI的思路就是利用一个加密的DNS获取用来加密的公钥,再用公钥把SNI加密后发送给服务器,服务器解密出内容后提供对应的证书,待客户端认证后加密就可以继续了。

ESNI的困境

理想很丰满,现实很骨感。加密SNI的公钥写在了_esni.你要访问的域名的TXT记录里,为保证安全公钥需要经常更换,DNS是有缓存的,如果客户端用了一个过期的公钥加密SNI那服务器就无法解密了!这样连接就不能继续了,因为服务器完全不知道你要访问哪一个网站,而且加密还没建立服务器又不能直接把新的公钥发给客户端。一方面DNS的记录是旧的,另一方面又无法与服务器建立连接,客户端无法使用ESNI访问这个网站了。

有些网站同时使用了多个CDN,但是加密的公钥却只能有一个,对于这种多CDN的站点就不能用ESNI了。

ESNI也只是加密了SNI,为何不把整个Client Hello都加密呢?最后还是需要一个比ESNI更可靠更安全的新协议,就这样ESNI草草收场。

ECH,加密的Client Hello

ECH与ESNI同样使用DNS分发公钥,不过不再是txt记录,而是HTTPS记录。另外把原本的Client Hello分为内外两个部分,内部的是用公钥加密的,其中是目标网站的SNI和一些其他加密要用到的数据,外部还是明文的,记录CDN的SNI和一些与CDN服务器加密要用到的内容。

从外界看ECH的连接和普通的明文SNI连接没什么不同,只是目标网站变成了其他的内容。

如果HTTPS记录中的公钥失效也不影响访问,客户端依然可以用明文的外部Client Hello和服务器建立加密连接,服务器可以在此时提供新的公钥继续接下来的ECH连接。

可以绕过SNI阻断的ECH

ECH就结果上来看域前置几乎一致,所以可以像域前置那样绕过SNI阻断,也可以用来访问一些无法访问的网站。
但是ECH可能不会隐藏自己是ECH流量的事实,比如Cloudflare的ECH连接的SNI内容是cloudflare-ech.com,这就是外部Client Hello的SNI,如果想要封禁ECH流量就只需要切断所有SNI内容是cloudflare-ech.com的连接。

带给代理分流的问题

读取SNI也是代理客户端探测目标网站为数不多的手段之一,ECH需要使用DoH解析域名,这种加密解析让代理客户端也无法通过DNS解析情况判断目标网站,剩下就只能靠读取SNI内容获取目标网站,在使用了ECH后SNI也变成了其他内容,代理客户端的分流也只能按照这个SNI进行,比如在开启ECH后访问teapotium.com在代理客户端眼里你是在访问cloudflare-ech.com,至于是直连还是代理都只能靠判断cloudflare-ech.com做决定,再也不能单独让teapotium.com直连了。