1 unstable release
| 0.1.0 | May 27, 2020 |
|---|
#1375 in Cryptography
150KB
3.5K
SLoC
Shadowrocks
Throwing rocks against the wall
Shadowrocks is a shadowsocks port written in pure async/.await Rust.
At the moment it only does the basics: tunneling from a local SOCKS5 server to a remote server, with
proper encryption. The implementation is thoroughly tested and is compatible with the original
python version with --compatible-mode.
The official Rust implementation of shadowsocks can be found here. It has way more
functionality.
How to run
To start the local SOCKS5 server at port 51980, run
cargo run -- -l 51980 -s 127.0.0.1 -p 51986 -k test-password
In the meantime, start the remote shadow server by running
cargo run -- --shadow -s 127.0.0.1 -p 51986 -k test-password
The server address (-s), server port (-p) and password (-k) flags must match.
JSON configuration files (-c) are also supported. ss:// URLs described
in SIP002 will come soon.
Encryption
Five types of ciphers are supported:
chacha20-ietf-poly1305provided by sodiumxchacha20-ietf-poly1305provided by sodiumaes-128-gcmby OpenSSLaes-192-gcmby OpenSSLaes-256-gcmby OpenSSL
All of them are AEAD ciphers.
Compatibility
In non-compatible mode, a few changes are made to the traffic between the socks server and shadow server.
- Master key is derived using
PBKDF2, as opposite toPBKDF1used in the original version. Master key is still derived from the password. - Sub-keys are derived using
HKDFwithSHA256, instead ofSHA1, which is no longer considered secure. The input key toHKDFis still the master key. - During encryption handshake, the salt used by the socks server to encrypt outgoing traffic is designated by the shadow server, while the salt used by the shadow server is designated by the socks server. The is the opposite to the original version, where each server decides their own salt.
Item #3 helps defend against replay attacks. If we can reasonably assume that salt generated is different each time, then both servers have to re-encrypt traffic for every new connection. Attackers will need to derive a different sub-key for the replied session, which cannot be done without the master key.
In compatible mode, shadowrocks behaves the same as the original version.
Features
- TCP tunneling
- Integrate Clippy
- Benchmarks
- Integration testing
- Crate level documentation
- Document the code in
src/cryptoin detail - UDP tunneling with optional fake-tcp
- Replay attack mitigation in compatible mode
- Replay attack mitigation in non-compatible mode
- Native obfuscation
- Manager API to create servers on the fly
-
ss://URL and JSON config file
Crypto dependencies
Both the ring crate (BoringSSL) and the openssl crate are used.
The functionality largely overlaps between those two. ring was originally
used as a reference point and sanity check to openssl, when the author is
unfamiliar with the crypto used in shadowsocks.
ring and openssl feature table
| features | ring |
openssl |
|---|---|---|
PBKDF1 |
✅ | |
PBKDF2 |
✅ | ✅ |
HKDF-SHA1 |
✅ | ✅ |
HKDF-SHA256 |
✅ | ✅ |
AES-128-GCM |
✅ | ✅ |
AES-192-GCM |
✅ | |
AES-256-GCM |
✅ | ✅ |
HKDF-SHA1 support was recently added to ring.
The ring crate can be disabled by disabling feature ring-crypto. The
openssl crate cannot be completely disabled at the moment.
Improvements
- Reduce memory allocation in the encryption / decryption process.
The current implementation does a lot of small memory allocations for each connection. For example, to send the SOCKS 5 address from socks server to remote shadow server, the following process is followed.
- Turning SOCKS 5 address into bytes.
- Turning packet length into bytes (
xbytes,x = 2). - Turning nonce into bytes (4 bytes).
- An OpenSSL crypter object.
- Ciphertext of packet length (
xbytes,x = 2). - Tag for the encryption (16 bytes).
- Concatenation of ciphertext and tag (18 bytes).
- Repeat 2-7 for SOCKS5 address with
xvaries.
To summarize, 13 allocations for each packet. Each encryption costs 6 allocations, and each packet we have to encrypt twice: once for packet length and once for the actual information.
The process for reading is similar.
- Ciphertext of packet length.
- Tag for encryption
- Turning nonce into bytes
- An OpenSSL crypter object.
- Packet length plaintext.
- Repeat 1-5 for packet content.
We saved one step for the "ciphertext without tag" part. Nonetheless this is still terrible.
Dependencies
~36MB
~412K SLoC