tags: shadowsocks
代理
[TOC]
# Shadowsocks: 一个安全socks5代理
S.D.T
January 4, 2019
## 1. 概述
Shadowsocks (后面简称ss)是一个基于[SOCKS5](https://tools.ietf.org/html/rfc1928)的安全代理。
`客户端 <---> ss-client <--[加密传输]--> SS-server <---> 受限主机`
ss-client 扮演了传统上的SOCKS5代理服务的功能,ss-client加密并转发客户端的数据到ss-server,ss-server将会解密并转发数据到远端受限的主机。受限主机的响应将会被ss-server加密并转发给ss-client, 最后客户端解密并发送数据到发起请求的客户端。
### 1.1 ss的地址/寻址方式
代表ss服务的地址和SOCKS5的地址格式保持一致:
`[1-byte type][variable-length host][2-byte port]`
有三种类型的地址类型被定义:
1. 0x01: host是一个4-byte的IPV4地址。
2. 0x03: host是一个变长字符串。第一个byte是一个变长字符串长度, 接下来是一个最长255-byte的域名。
3. 0x04: host是一个IPV6地址。
最后的2-byte port是一个大端模式的无符号整形(big-endian unsigned integer)。
### 1.2 TCP数据寻址
ss-client初始化一个TCP连接到ss-server, 然后ss-client发送加密的数据流到ss-server, 数据流格式如下:
`[target address][payload]`
以上内容会被客户端加密,加密由ss-server和ss-client配置的加密算法决定(双方算法必须保持一致)。
ss-server收到ss-client发送过来的加密数据,解密之后解析其中的目标地址,并与之简历一个新的TCP连接并转发payload到目标地址。ss-server随后会收到目标地址发送的响应,ss-server把响应内容加密并回传给ss-client,这个过程一致重复直到ss-client关闭连接。
### 1.3 UDP数据寻址
## 2. 数据流加密
数据流加密算法只提供数据保密性,而数据的完整性和正确性并不能保证。用户尽可能使用AEAD算法。
下面的算法提供了合理的保密性。
| 名称 | Key Size | IV Length |
| ---------------- | -------- | --------- |
| aes-128-ctr | 16 | 16 |
| aes-192-ctr | 24 | 16 |
| aes-256-ctr | 32 | 16 |
| aes-128-cfb | 16 | 16 |
| aes-192-cfb | 24 | 16 |
| aes-256-cfb | 32 | 16 |
| camellia-128-cfb | 16 | 16 |
| camellia-192-cfb | 24 | 16 |
| camellia-256-cfb | 32 | 16 |
| chacha20-ietf | 32 | 12 |
### 2.1 数据流加解密
stream_encrypt是一个这样的加密函数:它接收一个密钥(secret key),一个初始向量(init vector),一条数据(message), 函数输出一条与数据想通长度的密文(ciphertext),过程表示如下:
`stream_encrypt(secret_key, IV, message) => ciphertext`
stream_decrypt是一个解密函数,它还原原始的数据(original message),过程如下:
`stream_decrypt(secret_key, IV, ciphertext) => message`
secret key可以是用户指定,也可以从一个(用户的)密码生成。secret key的生成遵从 OpenSSl里的 [EVP_bytesToKe](https://www.openssl.org/docs/man1.0.2/man3/EVP_BytesToKey.html),详情可以参考 https://wiki.openssl.org/index.php/Manual:EVP_BytesToKey(3)
### 2.2 TCP
密文流由一个随机生成的IV紧跟着被加密的payload data组成:
`[IV][encrypted payload]`
### 2.3 UDP
`[IV][encrypted payload]`
## 3. AEAD 密码
[AEAD](https://en.wikipedia.org/wiki/Authenticated_encryption), ([这篇讲的更好点](https://zhuanlan.zhihu.com/p/28566058))代表 Authenticated Encryption with Associated Data。AEAD密码同事提供了保密性,完整性和真实性。这种算法在现代硬件上具有卓越的性能和效率。用户在任何时候应该尽量选择AEAD密码。
下表的AEAD密码是我们推荐的,相关的ss实现必须支持`chacha20-ietf-poly1305`。在具备AES硬件加速的设备上的ss实现必须支持`aes-128-gcm`, `aes-192-gcm`和`aes-256-gcm`。
| Name | Key Size | Salt Size | Nonce Size | Tag Size |
| ---------------------- | -------- | --------- | ---------- | -------- |
| chacha20-ietf-poly1305 | 32 | 32 | 12 | 16 |
| aes-256-gcm | 32 | 32 | 12 | 16 |
| aes-192-gcm | 24 | 24 | 12 | 16 |
| aes-128-gcm | 16 | 16 | 12 | 16 |
ss如何使用AEAD密码在 [SIP004](https://github.com/shadowsocks/shadowsocks-org/issues/30)中做了规范,并在 [SIP007](https://github.com/shadowsocks/shadowsocks-org/issues/42)中做了修订。
### 3.1 key的生成
主key可以是用户直接输入的,也可以从用户密码生成。生成算法依旧沿用OpenSSL的`EVP_BytesToKey(3)`。
[HKDF_SHA1](https://tools.ietf.org/html/rfc5869)是这样一个函数:接收一个secret key, 一个非保密salt, 一个字符串,最终输出一个密码学上来说高强度的subkey, 即使输入的secrey key比较弱。
`HKDF_SHA1(secret key, salt, info_string) => subkey`
info_string在应用上下文与subkey强绑定。
我们利用函数HKDF_SHA1利用预先双方共享的主key(我觉得可以简单理解为ss-client的密码)生成了每次会话的临时subkey, salt 需要保证在主key不变的情况下保持唯一性。
### 3.2 经验证的加解密 (Authenticated Encryption/Decryption)
**AE_encrypt** 是一个函数,它接收一个secret key, 一个非保密的一次性随机字符串(nonce),一条message, 最后输出密文和认证标签(authentication tag)。使用同一key的每次调用中,一次性随机串必须保持唯一(也就是对于同一个key调用函数时,nonce必须不一样)。
`AE_encrypt(secret key, nonce, message) => (ciphertext, tag)`
**AE_decrypt**是一个函数,它接收一个secrey key, 非保密一次性字符串(nonce), 密文(ciphertext), 认证标签(authentication tag),最终输出原始数据。如果任何一个数据被篡改,解密就会失败。
`AE_decrypt(secret key, nonce, ciphertext, tag) => message`
### 3.3 TCP
AEAD 加密的TCP数据流由一个随机盐值开头(用来生成per-session subkey), 后面跟随着任意数量的加密数据块,每个块拥有如下结构:
`[encrypted payload length][length tag][encrypted payload][payload tag]`
开头的payload length是2-byte的大端无符号整形,被限定在最大0x3FFF。最高两位保留,目前总是被设置为0。因此payload的最大长度为 $$ 2^{14}-1 $$ bytes。
### 3.4 UDP
一个AEAD加密的UDP包结构如下:
`[salt][encrypted payload][tag]`
salt被用来生成per-session的subkey,salt必须保证随机性和唯一性。每个UDP包使用salt生成的subkey和全0的nonce独立加解密。
## 4. 传输插件
### 4.1 架构概述
ss的插件和tor的 [Pluggable Transport](https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt)(简称PT)类似,但是和PT里的SOCKS5代理不同的是, ss的每个SIP003插件都作为一个tunnel(或者叫做local port forwarding)。这个设计的目的是为了避免PT里每连接参数(per-connection arguments), 从而使得ss的插件容易实现。
+-----------+ +--------------------------+
| SS Client +-- Local Loopback --+ Plugin Client (Tunnel) +--+
+-----------+ +--------------------------+ |
|
Public Internet (Obfuscated/Transformed traffic) ==> |
|
+-----------+ +--------------------------+ |
| SS Server +-- Local Loopback --+ Plugin Server (Tunnel) +--+
+-----------+ +--------------------------+
### 4.2 插件的生命周期
和PT类似,插件的client/server作为ss client/server的子进程存在。
如果插件中有错误发生,那么作为插件的子进程就会退出(exit),并给出error code。然后ss父进程也会停止(SIGCHLD)。
当用户停止了ss client/server时,插件自然也被终止。
### 4.3 向插件传递参数
插件通过环境变量接收参数
1. 4个必须参数环境变量为:
| SS_REMOTE_HOST | 远端插件服务host |
| -------------- | -------------------- |
| SS_REMOTE_PORT | 远端插件服务port |
| SS_LOCAL_HOST | 本地ss服务或插件host |
| SS_LOCAL_PORT | 本地ss服务或插件port |
2. 1个可选环境变量 SS_PLUGIN_OPTIONS。如果插件有个性化参数需要传递可以放在这个环境变量里。值例如 `obfs=http;obfs-host=www.baidu.com`,如果值里有分号和等号需要进行backslash转义。
### 4.4 与PT的兼容性
为了使用tor project的插件,有2种方法可行:
1. fork tor的插件,按照SIP003进行修改
2. 对PT进行适配以符合SIP003
### 4.5 插件的许可
所有插件必须运行在独立的进程中,插件开发者可以选择任何许可证。对插件提供者来说并不存在GPL许可的限制。
### 4.6 插件的限制
1. 插件不支持互相串联。ss启动的时候只有一个插件被启动。如果真的想这样,那么就按照SIP003来实现一个plugin-over-plugin
2. 只有TCP数据被转发,UDP目前还不支持
### 4.7 插件样例工程
1. 流量混淆, SIP003 标准插件 [simple-obfs](https://github.com/shadowsocks/simple-obfs)
2. 基于SIP003实现的ss https://github.com/shadowsocks/shadowsocks-libev
## 5. URL scheme
ss的url模式遵循 [RFC3986](https://www.ietf.org/rfc/rfc3986.txt)标准
`SS-URI = "ss://" userinfo "@" hostname ":" port ["/"] ["?"plugin] ["#" tag]
userinfo = websafe-base64-encode-utf8(method ":" password)`
url最后的/必须加上,如果有插件。但如果只有tag(#开图),最后的/可以省掉。
例子:
1. `ss://YWVzLTEyOC1nY206dGVzdA==@192.168.100.1:8888#Example1`
2. `ss://cmM0LW1kNTpwYXNzd2Q=@192.168.100.1:8888/?plugin=obfs-local%3Bobfs%3Dhttp#Example2`
## 6. 官方ss具体实现
### 6.1 服务端实现
1. [shadowsocks](https://github.com/shadowsocks/shadowsocks) python
2. [shadowsocks-libev](https://github.com/shadowsocks/shadowsocks-libev) C
3. [shadowsocks-go](https://github.com/shadowsocks/shadowsocks-go) Go
4. [go-shadowsocks2](https://github.com/shadowsocks/go-shadowsocks2) Go
#### 6.1.1 服务端功能比较
| | ss | ss-libev | ss-go | go-ss2 |
| -------------- | ---- | -------- | ----- | ------ |
| TCP fast open | Y | Y | N | N |
| Multiuser | Y | Y | Y | N |
| Management API | Y | Y | N | N |
| Redirect mode | N | Y | N | Y |
| Tunnel mode | Y | Y | N | Y |
| UDP Relay | Y | Y | Y | Y |
| AEAD ciphers | Y | Y | N | Y |
| Plugin | N | Y | N | N |
### 6.2 客户端
#### 6.2.1 客户端功能对比