为何shadowsocks弃用一次性验证(OTA)
近日,shadowsocks提出了SIP004草案,计划用AEAD算法替代不安全的流加密 + OTA,并因此弃用了“一次性验证”(OTA)。
这一新协议的推出标志着shadowsocks的重大进步,因此我撰写了这篇文章,以帮助不太理解英文的朋友们了解“为何OTA被迅速淘汰”以及“为何应当采用新协议”。
一、OTA的概念
OTA(One Time Auth,一次性验证)是shadowsocks为提高安全性、抵御选择密文攻击(CCA)而引入的实验性功能。
很多人或许都听说过这个功能——即便不清楚OTA的具体含义,至少也在shadowsocks各个分支的客户端中见过“一次性验证”的选项。虽然这个名字可能让人感到困惑(笑)。
接下来,我将解释当初为何引入OTA功能。
二、原协议的不足
原shadowsocks协议的漏洞早在2015年就被@breakwa11提出。当时正值@clowwindy遭遇问题,此问题引发了激烈的讨论,经过一段时间后,才开始有了技术上的深入探讨。
如果你想了解当时的情况,可以查看相关issue,这里简单概括一下提出的漏洞。
2.1 shadowsocks协议概述
原shadowsocks协议的TCP握手包(加密后)格式如下:
+-------+----------+
| IV | Payload |
+-------+----------+
| Fixed | Variable |
+-------+----------+
其中IV(Initialization Vector,初始化向量)是由随机数生成器生成的固定长度输入值。引入IV的目的是使得相同的明文和密钥生成不同的密文,从而增加破解的难度。
shadowsocks服务器利用IV和预共享密钥(通常是用户设置的密码)来解密TCP数据包中的payload。
解密后的数据格式如下:
+--------------+---------------------+------------------+----------+
| Address Type | Destination Address | Destination Port | Data |
+--------------+---------------------+------------------+----------+
| 1 | Variable | 2 | Variable |
+--------------+---------------------+------------------+----------+
Address Type (ATYP)占一个字节,可能的取值包括01, 03, 04,分别对应IPv4、hostname和IPv6类型的地址。这些标准在RFC1928中有定义,有兴趣的话可以查阅。
握手完成后,shadowsocks中继工作于流模式,后续所有TCP数据包不再携带IV,而是使用握手时协商的IV。
接下来讨论该协议的不足之处。
2.2 原协议的缺陷
如上所示,原始shadowsocks协议中TCP握手包的IV字段是固定长度的。不同加密算法的IV长度各异,对于rc4-md5和aes系列等常用算法,长度为16字节。详细信息可以在官方文档 – Cipher中查阅。
为了判断数据有效性,服务器会检查数据包中表示地址的字节,看其是否为上述三个可能值之一。如果是,便尝试解析后续地址和端口进行连接;如果不是,立即断开连接。
这一行为使得主动探测成为可能。
2.2.1 主动探测的原理
该方法由@breakwa11提供
通常,表示地址类型的字节是经过加密发送的,因此第三方无法精确修改它。然而,shadowsocks所用的加密方式均为流加密,其特点在于明文数据流与密钥流一一对应。
简单来说,攻击者可以通过伪造TCP数据包进行主动探测。只需暴力尝试修改加密数据包中IV后的下一个字节(假设IV长度为16字节,那么修改第17个字节),穷举256种可能性,如果在一种到三种情况下服务器未立即断开连接,便可判断该端口开放的是shadowsocks服务。
这种主动探测方法可能正在被GFW广泛使用,谁知道呢?你使用的原版shadowsocks代理随时可能被封锁。
2.2.2 防范主动探测
经过讨论,发现上述漏洞确实存在,因此大部分shadowsocks分支已经采取了相应的防范措施(如shadowsocks-libev v2.5.5+),即“随机超时抵抗”,而不是立即断开连接,配合自动黑名单等机制可有效降低被探测的风险。
尽管如此,这种方法并非长久之计,解决方案是什么呢?

三、OTA的引入
上述情况下,主动探测得逞的原因在于服务器对接收到的数据包未进行校验,任何发送的数据包,不论是否被恶意篡改,原shadowsocks服务器都会做出相同反应。
此时,@madeye(目前的shadowsocks维护者)提出了One Time Auth即“一次性验证”的方案,为原shadowsocks协议添加数据包验证。
3.1 OTA协议概述
开启OTA后的shadowsocks握手包(加密前)格式如下:
+------+---------------------+------------------+-----------+
| ATYP | Destination Address | Destination Port | HMAC-SHA1 |
+------+---------------------+------------------+-----------+
| 1 | Variable | 2 | 10 |
+------+---------------------+------------------+-----------+
可以看到,新增了一个HMAC-SHA1字段,该字段通过HMAC-SHA1算法(以IV + PSK为key)对除了DATA以外的数据进行计算。此外,数据包头部的ATYP添加了一个标志位,用于指示OTA是否开启(ATYP & 0x10 == 0x10)。
+----------+-----------+----------+----
| DATA.LEN | HMAC-SHA1 | DATA | ...
+----------+-----------+----------+----
| 2 | 10 | Variable | ...
+----------+-----------+----------+----
握手完成后,后续TCP数据包在原始协议包上添加了DATA.LEN(包长度)和HMAC-SHA1字段。这样,服务器能够对数据包进行完整性校验,从而识别被篡改的数据包。
3.2 OTA的缺点
尽管OTA增强了安全性,有效防范CCA,并解决了原协议数据包容易被篡改的问题,但其实现中,shadowsocks-libev及大部分分支假设第一个数据包必须包含整个带有SHA1-MAC的头部,否则会断开连接。
这又为主动探测提供了机会。不过,这种主动探测可以通过之前提到的“随机超时抵抗”进行防范,真正令人担忧的是:
该方法由@breakwa11提供
还记得流加密的特点吗?攻击者可以同样的方式修改数据包中的DATA.LEN字段,并通过观察服务器的反应来判断这是否是shadowsocks服务器。
例如,攻击者构造DATA.LEN的高位字节密文,使得解密后的DATA.LEN数值异常大(但后续DATA大小未变),shadowsocks服务器就会继续等待实际上并不存在的数据传输完成,直到超时。因此,只需在发送恶意数据包后观察服务器是否“不会断开连接且至少等待一分钟无任何数据包”,即可确定该服务器是否开启shadowsocks服务。
这种检测方法比检测原版协议更加隐蔽,甚至不会在服务端留下任何可疑痕迹。OTA原本是为了增强原版协议的流加密安全性,却潜在地带来了更大的风险,这就是shadowsocks-org急于弃用OTA的原因。
四、新协议AEAD
4.1 原协议缺陷汇总
原版shadowsocks协议最大的问题在于未对数据包完整性进行校验,加上流加密的特性,使得攻击者能够通过穷举方式修改密文进行主动探测。
OTA协议虽然在数据包尾部添加HMAC-SHA1字段以验证DATA的完整性,但包首部的DATA.LEN并未经过验证。这使得攻击者能够构造高位的DATA.LEN密文,从而进行更隐蔽的主动探测。
因此,在新协议草案的讨论中借鉴了shadowsocksR协议的一个重要改进——对DATA.LEN进行单独校验,详情请参阅:ShadowsocksR协议插件文档。
4.2 AEAD的定义
在密码学应用中,保密性通过加密实现,而消息认证通过MAC(Message Authentication Code,消息验证码)实现。这两种算法的组合方式,曾引发许多安全漏洞,过去有三种方法:
- Encrypt-and-MAC (E&M)
- MAC-then-Encrypt (MtE) <- 即OTA的方式
- Encrypt-then-MAC (EtM) <- 新协议的方法
但后来发现,E&M和MtE均存在安全问题,因此自2008年起,逐渐提出了“通过单一算法同时实现加密和认证”的理念,称为AEAD (Authenticated Encryption with Associated Data)。在AEAD概念中,cipher + MAC模式被一个AEAD算法取代。
使用AEAD算法的新协议本质上是更完善的流加密 + 认证,尽管依然使用流加密,但通过更全面的数据包完整性验证机制消除了上述篡改密文的可能性。
注:截至本文发布时,新协议均使用流加密 + 认证,但AEAD设计使其能够使用块加密,因此上述说法并非绝对。
为了实现认证加密(Authenticated Encryption),新协议必须将TCP流分割成不同块并分别验证。有兴趣了解新协议数据包定义的人可以查阅官方文档 – AEAD,本文不再详述。
4.3 新协议支持的AEAD算法
目前shadowsocks-libev已支持以下AEAD算法,其他分支也在跟进中:
- AES-128-GCM
- AES-192-GCM
- AES-256-GCM
- ChaCha20-IETF-Poly1305
- XChaCha20-IETF-Poly1305
这些新加密算法本质上是流加密 + 验证,原先的其他简单流加密算法不适用于新协议。
4.4 新协议的优缺点
采用AEAD算法的新协议能有效解决上述原版/OTA协议的所有问题,能够防范CCA和中间人攻击,减少主动探测的风险。我能想到的唯一缺点可能是性能,但这又能影响多少呢?基准测试参见此处。
