shadowsocks-org icon indicating copy to clipboard operation
shadowsocks-org copied to clipboard

设计一个随机密钥滚动下发机制,实现前向安全、防长期重放、防潜在攻击等

Open RPRX opened this issue 4 years ago • 50 comments

Shadowsocks 现有的设计存在一个非常明显的问题:无前向安全。而对于 TLS,非 FS 套件早已被视为不够安全了。 “前向安全”指的是攻击者拿到现有的密钥时无法解密过往的通讯内容,实现前向安全需要依赖动态密钥及可信源。

由于现在的国产安卓系统都有云备份功能,手机上的 SS 密码没有安全保证,它可以被用来解密一切通讯内容。

此外,由于 SS 的加密不依赖于时间戳,防重放只能靠缓存,但缓存并不是无限的,这就导致实质上的 无法防重放。 虽然 VMess 也没有前向安全,但它的头部加密依赖于时间戳,至少做到了防重放(不过,时间戳并不是唯一解)

顺便提一下:Shadowsocks 没有 UoT 结构,各个实现都是 TCP、UDP 同端口,这并不常见,是非常明显的特征。


为了解决现在的一些问题,SS 需要设计一个随机密钥滚动下发机制,以下是我的构想:

  1. 首先,这只是内层明文结构的改变,复用现有的加密方式,比如 AEAD 系列
  2. 初次配置时,初始密钥 数等于客户端数量,需通过可信通道传递(显然这不是问题,SSH 或 HTTPS 均可)
  3. 对某个客户端而言,初次连接服务端时用的是初始密钥,并发连接也是如此
  4. 对于服务端,当某个密钥被使用了一定次数(按连接计),后续的连接返回数据时需要带同一个新的 随机密钥 一起返回
  5. 客户端一旦收到了新的密钥,就要存起来,新的连接必须使用它,同时逐步忘记旧的密钥(直到旧的连接全关闭)
  6. 服务端对于同一个客户端,只存最近的几个密钥,而且每个密钥都有 使用次数限制
  7. 注意客户端只持久化收到的最新的密钥。若新增了一个客户端,需要服务端下发新的初始密钥(可以 API 接口)

通过上述机制可以实现:

  1. 前向安全。即使攻击者拿到了现有的密钥,也无法解密绝大多数过往的通信内容。
  2. 后向安全。即使攻击者拿到了现有的密钥,他还必须能拿到后续的所有连接,不然跟不上密钥的更新节奏。
  3. 防长期重放。很简单,因为服务端只存最近的几个密钥。
  4. 防潜在攻击。很简单,因为限制了每个密钥的使用次数。至于是什么攻击,过几天才可以说。
  5. ~~限制设备数。~~

可以看到,相对于现有的机制,上述机制并没有明显增加配置复杂度,却大大增强了各方面的安全性。欢迎补充建议。

RPRX avatar Jan 03 '21 10:01 RPRX

这个机制和OTA有什么不同吗?

dev4u avatar Jan 03 '21 10:01 dev4u

@dev4u 完全不是同一个东西

RPRX avatar Jan 03 '21 10:01 RPRX

看起来若要实现这个机制,明文 TCP 代理协议结构只需一处小改动:返回的数据前加一个字节代表新密钥长度,0 代表无新密钥

2021 年了,只用 AEAD 吧

  • FS_AEAD_CHACHA20_POLY1305 fs-chacha20-poly1305
  • FS_AEAD_AES_256_GCM fs-aes-256-gcm
  • FS_AEAD_AES_128_GCM fs-aes-128-gcm

各方面细节问题已经考虑清楚,有空时我将先用 Xray-core 实现它们,进行一些初步的试验

RPRX avatar Jan 03 '21 17:01 RPRX

如果没什么问题,此方案可以成为 SIP010,作为 SIP004 和 SIP007 的延续。SIP008 可以提供相应的 API 支持。

RPRX avatar Jan 03 '21 20:01 RPRX

这个 @fortuna 应该会感兴趣,Outline好像也在考虑前向安全的问题。

felixding avatar Jan 03 '21 23:01 felixding

此方案存在几点细节:

  1. 如何区分客户端?此方案相当于每一个客户端与服务器之间建立唯一的Session,目前的机制无法准确识别每一个单一客户端实例。存在几种可能的方案:

    • 增加SessionID标识,每次新建立请求时跟着IV / SALT请求到服务端。或者为ClientID, UserID,本质相同
    • 按端口标识,即Client与Server严格保持一对一关系
  2. 不需要在加密方式前增加FS_前缀,此方案没有修改加密方式,而是协议层面有修改,可有几种方式

    • 配置文件增加 protocol_fs: true
    • 或增加协议版本号 protocol: aead_fs
  3. 通讯协议具体细节未定义

    • 若涉及协议修改,可考虑定义为可变长Header结构,方便后续增加新字段
    • 按当前协议描述,新密钥增加在服务端返回的 IV / SALT 之后
      +--------+--------+--------+--------+
      |  IV / SALT      | EXT_LENGTH      | <- FIXED HEADER
      +-----------------+-----------------+
      | EXT_TY | EXT_VALUE                | <- VARIABLE HEADER
      +-----------------------------------+
      | ... DATA STREAM                   | 
      

zonyitoo avatar Jan 04 '21 01:01 zonyitoo

@zonyitoo

  1. 似乎不需要额外的机制,因为核心目的不是限制设备数量,而且,若多个客户端共享初始密钥,很快就会只剩一个能连上
  2. 确实更合理
  3. 这个。。。如果需要可扩展性,甚至可以直接插 PB,比如 VLESS(协议结构可扩展性极强)

RPRX avatar Jan 04 '21 04:01 RPRX

似乎不需要额外的机制,因为核心目的不是限制设备数量,而且,若多个客户端共享初始密钥,很快就会只剩一个能连上

如果我确实需要实现多设备使用的话要如何实现呢?服务端根据密钥来区分Client么?

zonyitoo avatar Jan 04 '21 05:01 zonyitoo

@zonyitoo

是的,需要生成多个初始密钥,如果同端口则需要进行解密尝试,但由于只判断第一个包,所以代价很低

RPRX avatar Jan 04 '21 05:01 RPRX

多加几个字段是否可以让各实现之间歧义更少?服务端也可以更明确知道具体是什么client在连接。

zonyitoo avatar Jan 04 '21 05:01 zonyitoo

@zonyitoo

我觉得按照规范,独立密钥足以同时作为认证机制了?

RPRX avatar Jan 04 '21 05:01 RPRX

@Mygod 说出你的想法

RPRX avatar Jan 04 '21 05:01 RPRX

@QuantumGhost 又来一个

RPRX avatar Jan 04 '21 05:01 RPRX

我觉得:

  1. Shadowsocks 的主要目的不是保证流量安全,而是保证无法识别,翻墙不受阻碍
  2. 上述机制对于实现 1 并无帮助,而且相对来说较为复杂
  3. Shadowsocks 有插件机制(SIP003),如果有实验性的想法可以通过插件来进行验证

QuantumGhost avatar Jan 04 '21 05:01 QuantumGhost

Implement it with SIP003, that's quite a good idea.

zonyitoo avatar Jan 04 '21 06:01 zonyitoo

  1. 我认为后向安全性没办法得到保证,攻击者拿到当前秘钥后很容易“跟上步伐”,因为在绝大多数情况下我们默认攻击者有能力记录所有包。
  2. 另外,该方案还是无法彻底解决“云上贵州”问题,同问题1。

mzz2017 avatar Jan 04 '21 06:01 mzz2017

@QuantumGhost

  1. 我很难赞同你的观点,或许你看看这里 https://github.com/shadowsocks/shadowsocks-windows/issues/3059
  2. 就我个人而言,缺乏前向安全的东西是不敢用的,不希望通信内容哪天被拉清单
  3. Shadowsocks 同端口未知流量就是很明显的特征,不如先把这个改掉?
  4. 注意我公开的想法都不是实验性的,而是一定会实现的,只是看 SS 组织有无意愿一起标准化
  5. 所以如果无意愿,我更乐意直接实现一个 Secure Shadowsocks,并向人们普及一下什么叫前向安全
  6. 可能你并不知道过几天会有一篇文章出来,而文中的机制可以解决问题

话说回来,若只是为了翻墙,如果你不介意买个域名,XTLS 可能是现阶段更好的选择。

RPRX avatar Jan 04 '21 06:01 RPRX

@mzz2017

事实上后向安全是很难得到保证的,TLS 也做不到这一点,但动态更新的密钥为后向安全提供了基本的可能性,这一点比 TLS 强

RPRX avatar Jan 04 '21 06:01 RPRX

@rprx YOMV

另外补充一点,

由于现在的国产安卓系统都有云备份功能,手机上的 SS 密码没有安全保证,它可以被用来解密一切通讯内容。

如果你要解决终端不受信任的问题,这一些变更远远不够(这是一个非常非常困难的问题,如果我是你我就不会在这上面花时间,除非我在攻读相关领域的博士)

QuantumGhost avatar Jan 04 '21 06:01 QuantumGhost

@QuantumGhost

这个问题很难解决,我只是描述了一个尴尬的事实:由于缺乏前向安全,在国产手机上使用现有的 SS 是风险极高的行为

RPRX avatar Jan 04 '21 06:01 RPRX

就算有密钥协商,系统依然可以记录你协商得到的短期密钥,所以这些方案并不能确保前向安全

我个人认为,如果你不信任你的手机 / 系统,除了更换设备之外并没有其他解决方案

QuantumGhost avatar Jan 04 '21 06:01 QuantumGhost

@QuantumGhost

注意云备份是定期备份,不是实时监控,就这一点而言,TLS 方案比 SS 方案更安全

RPRX avatar Jan 04 '21 06:01 RPRX

或者我这么说,假设你一开始是在电脑上使用 SS,此时只要你电脑不丢,就是相对安全的

某一天你想在手机上用 SS,扫个二维码,晚上系统给你云端一下,boom,连你电脑的 SS 也跟着不安全了,甚至包括历史通讯

RPRX avatar Jan 04 '21 07:01 RPRX

1、SS的设计目的主要是防止识别,并不是保证流量安全性。

2、SS本身虽然无法保证向前安全,但是用户在访问各种网站的时候仍然会受到TLS保护。由于TLS本身具备了向前安全的保障,所以即使SS流量能被解密也不会被随便窥探到流量内容。

题外话: 基于安全原则,我们不应该信任任何运营商提供的基础设施。 事实上,即使SS提供了向前安全,我们仍然需要TLS来保护流量。既然如此,SS是否提供向前安全这个问题就并不是那么重要了。

3、如果终端无法被信任,那么这个终端上发生的一切同样都不应该信任。

4、为不同客户端随机更换密钥的方案我不认为是一个好方案。 理由 4.1、由于密钥要求是定时或者不定时更换,服务端无法确定客户端用什么密钥来连接服务器。所以为了能响应客户端的访问,服务端必须尝试所有有效的密钥来解密或者采用明文通知的方式。 如果必须尝试所有有效密钥,那么客户端数量多了以后绝对是一个灾难。 如果是采用类似明文来通知的方式,显然是增加了特征,违背了设计初衷。 4.2、如果假设终端会上传客户端保存的密钥,那么如何保证上传的密钥不会保存N个版本?我认为是没办法保证的。 如果保存了N个版本那更换密钥与不更换本身是没有区别的。

wevsty avatar Jan 04 '21 07:01 wevsty

@wevsty

  1. 对我而言,在稳定 FQ 方面,XTLS 远比 SS 更合适且高效,但 SS 可以是一个不强依赖第三方的可信隧道方案,即特定客户端与服务器间的无特征可信隧道。况且,无论用途,安全性始终很重要,若能做到更安全,为何不做?不能不思进取。
  2. 即使承载的大部分流量是 TLS,它也并远未普及 ESNI/ECH,相信你知道我在说什么。此外,SS 可被破解意味着可被实锤。
  3. 这个说法非黑即白了,终端并不是完全无法被信任,只是现代的云备份会对 SS 静态密码造成致命打击。
  4. 建议重新看一遍我描述的方案。另外,即使你非要同端口多用户,也只需要判断第一个包,成本极低。

说一下前向安全:

  1. 一般指的是密钥的第一次泄露,比如密钥第一次被手机泄露时,电脑的历史通讯还是不可破解的、安全的。
  2. 当然对于我的方案,上述场景压根就不存在,因为不同设备间必须是独立的。
  3. 事实上随着你的使用,动态密钥一天内就会被更新 N 次,即使每天快照一次,也远比现在的静态密码强,至少增加了跟踪难度,而不是随便拿到一段此前或此后的流量就可以直接解密。

RPRX avatar Jan 04 '21 09:01 RPRX

没救了

RPRX avatar Jan 04 '21 09:01 RPRX

@rprx 我问跟OTA有什么区别,意思是我认为你这种想法,会为ss的通讯带来了特征和爆破点。

特征不多说了。

引入的爆破点在于,客户端怎么知道,返回的密钥是中间人告诉他的?还是真实服务端告诉他? 此处是不是应该有个主密钥,来验证这个信息?主密钥从哪来……是不是又回到ss的原点? 我承认ss仍有不足的地方,但我相信这一切都会好起来的。

dev4u avatar Jan 04 '21 09:01 dev4u

@dev4u

所以,会带来什么特征?

初始密钥由 SSH/TLS 可信通道传输,而新的密钥是 AEAD 解密出来的,又有什么问题?

RPRX avatar Jan 04 '21 10:01 RPRX

@dev4u

所以,会带来什么特征?

初始密钥由 SSH/TLS 可信通道传输,而新的密钥是 AEAD 解密出来的,又有什么问题?

如你前面所说: “若要实现这个机制,明文 TCP 代理协议结构只需一处小改动:返回的数据前加一个字节代表新密钥长度,0 代表无新密钥” “由于只判断第一个包,所以代价很低”

有条金玉良言:绝不要让密码在“空中”传播。

dev4u avatar Jan 04 '21 10:01 dev4u

@dev4u (顺便 @wevsty

存在一些误解,我 kindly 说明一下

若要实现这个机制,明文 TCP 代理协议结构只需一处小改动:返回的数据前加一个字节代表新密钥长度,0 代表无新密钥

这里指的是服务端 返回 数据时的 明文 协议结构,是还没有被加密的

由于只判断第一个包,所以代价很低

这里指的是服务端 接收 请求时,只需尝试解密第一个 packet 即可找出同源后续大量数据的所属用户,实现低成本端口复用

RPRX avatar Jan 04 '21 12:01 RPRX