各种加密代理协议的简单对比

目前我们常用的加密代理有协议有 HTTPS,SOCKS5-TLS 和 shadowsocks,此文从各个角度简单分析各个协议的优劣,以帮助各位选择合适的协议。

先简单说些背景知识,以上协议都是基于 TCP 的代理协议,代理协议(Proxy Procotol)与 VPN 不同,仅可被用于通过代理服务器转发 TCP 连接(shadowsocks 支持代理 UDP),而 VPN 可被用于 IP 层上的所有协议,如 TCP、UDP、ICMP 等。所以在使用代理时,ping 等 ICMP 应用是不可以被代理的。

然后简单解释一下什么是 TLS,TLS 又名 SSL,是针对数据流的安全传输协议。简单来说,一个 TCP 链接,把其中原本要传输的数据,按照 TLS 协议去进行加密传输,那么即可保证其中传输的数据安全。这个安全至少体现在以下几个方面:

  1. 数据被加密,任何可以截取到 TCP 数据流的人,无法解密出原始数据;
  2. 数据不可被篡改,一旦篡改会导致解密失败,连接断开;
  3. 服务器身份验证,基于 X509 的证书体系去确认目标服务器是否为真实的服务器。

明文的 HTTP 套上一层 TLS,也就变成了 HTTPS,SOCKS5 套上 TLS,就变成了 SOCKS5-TLS。TLS 协议是整个互联网安全的基石,几乎所有需要安全传输的地方都使用了 TLS,如银行、政府等等。

当被用作代理协议时,HTTP 层和 SOCKS5 层去进行具体的代理连接控制,如进行身份验证、告知需要转发的目标主机名等。所以不需要 TLS 他们也可以用作代理,只不过所有数据都是明文传输,不具备安全性。加上 TLS 后,由 TLS 去保证安全。而 shadowsocks 协议则融合了代理控制和安全保证。所以后文的很多对比实际上是 shadowsocks 和 TLS 的对比。

性能

TLS 协议由于承担了一项额外的功能,需要验证目标服务器身份,导致其握手时会比较复杂。

ping 的时间表示,一个 IP 层数据包从本地发出,到服务器再返回的来回时间,即 RTT(round-trip time)。

在发起代理连接时,首先我们需要进行 TCP 3 次握手,耗时为 1 个 RTT。(此处把最后的 ACK 直接并入后续的数据传输部分)。

然后进行 TLS 握手,因为服务端和客户端需要进行身份验证并协商协议版本号、加密方式等细节,第一次连接时需要 2 个 RTT。当然 TLS 的制定者也发现这太慢了,于是引入了 TLS Session Resumption,当服务端和客户端服务器连接过一次后,之后的连接可以直接复用先前的协商结果,使得 RTT 降低到 1 个 RTT。但这需要服务器和客户端的支持。(这是为什么 Surge benchmark 时,对于 TLS 代理第一次的测试结果可能较慢的原因之一)

对于 HTTPS 协议,当 TLS 连接建立后,客户端通过 HTTP 层发起代理请求,服务端回应连接建立,此后进入正常的代理通讯环节,再耗费 1 个 RTT。

对于 SOCKS5-TLS 协议,当 TLS 连接建立后,如果没有验证的环节,那么需要再耗费 1 个 RTT,如果有验证(用户名密码),那么需要耗费 2 个 RTT。

而对于 shadowsocks,由于使用的是预共享密钥(pre-shared key, PSK),加密方式也是预先约定好的,所以不需要进行协商,只需要在 TCP 建立之后,再耗费一个 1 个 RTT 告知目标主机名。

总结如下:

HTTPS(TLS Session Resumption):3 个 RTT

SOCKS5-TLS 无验证:3 个 RTT

SOCKS5-TLS 有验证:4 个 RTT

shadowsocks:2 个 RTT

(注:最后一个 RTT 并不严谨,因为客户端可以在最后一个 RTT 产生响应前,直接开始后续传输。另外如果使用了 TCP Fast Open,可以看作 TCP 阶段 RTT 为 0。)

对于日常使用,最影响性能的就是握手速度,后续传输过程中的加解密性能,对于现代 CPU 来说基本都不会构成瓶颈。 shadowsocks 由于有 PSK 的特点,在 TCP 协议基础上已经达到极限,不可能有协议能再低于 2 个 RTT。

所以,同等网络环境下,shadowsocks 是明显快于 HTTPS 的。(体现在延迟上,而不是带宽)

另外,最新的 TLS 1.3 协议正力图解决这个问题,由于目前还处于草案阶段,各种工具链不完善,现在不太好评估实际效果。

数据安全性

(此处说的数据安全性,指的是加密后的流量是否会被截取并破解的问题。)

对于 TLS,作为个人用户,丝毫不用担心,TLS 协议如果真的不安全了,世界早就乱套了…

对于 shadowsocks,使用的加密方法也都是工业上成熟的算法,从数据安全性角度考虑也基本不用担心。

抗识别

这个问题有两个角度需要考虑:

  1. 观察一段数据流量,是否能判别这是一个代理协议的流量;
  2. 对于一个仅暴露出 TCP 端口的黑箱,能否判断这个端口提供了代理服务。

对于 shadowsocks 协议,在第一点上,观察者只能判定这个数据流为未知的协议。而第二点,shadowsocks 的几次修改,都是因为在这出了问题,目前最新的 AEAD 加密方式,应该已经解决了这个问题,但还需要时间去检验。

对于 HTTPS 协议,在第一点上,观察者是无法去区分这是一个代理还是一个标准的 HTTPS 网页访问的。而第二点,在妥善配置的情况下,也是完全无法判别。

但在实践中,大部分 HTTPS 代理服务器为了兼容浏览器行为,在直接被当做 HTTPS Web 服务器访问时,会返回代理错误页或者 407 Proxy authentication required,直接暴露了自己是一个代理,如果要抗击第二点,可以将服务端的行为修改为,如果请求的 Header 中,不包含一个有效的 Auth,那么就返回一个标准的 200 页面,这样从理论上杜绝了被嗅探的可能。

总结一下,最新的 shadowsocks 已经能满足抗识别的两个要求,但是观察者得到结论是“未知协议”。而使用 HTTPS,观察者无法判断这是一个 HTTPS 代理还是 HTTPS Web 服务器,这是更优的结果。

Hiding true identities inside a seemingly ordinary. — Person of Interest S03E23

部署难度

HTTPS 协议使用广泛,有众多成熟的工业级工具,如 squid,haproxy,nghttp2 等等,但是由于 HTTPS 协议本身比较复杂,配置起来参数众多,有很多性能相关参数需要自己调优,所以一般用户配置起来会有难度。

shadowsocks 经过多年发展,目前也已经有众多的软件支持,但是对于不同特性的支持度不一。由于参数简单,部署配置起来极其方便。

功能

shadowsocks 目前还存在一些功能上的缺陷:

  1. shadowsocks 没有设计错误回报机制,对于以下错误,客户端看到的行为都是服务器主动断开 TCP 连接:
  • 密钥或者加密方法错误
  • 目标主机名 DNS 解析失败
  • 目标主机不可达
  • 目标主机拒绝连接

这使得客户端没办法根据不同的错误采取进一步的动作,只能简单的向用户返回 Socket closed by remote 错误。

2. shadowsocks 没有考虑用户鉴别,使得服务端 ACL 或者流量统计等功能无法实现,主流的 workaround 是通过不同的端口号去识别不同的用户,但这极其浪费资源且很不优雅。

3. 部分 ISP 对于非 HTTP 和 TLS 的未知流量,会进行降速限制,这个可以通过配置 obfs 解决。