diff --git a/README.md b/README.md index aede012..64b00bd 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ shadowsocks =========== -[![Build Status](https://travis-ci.org/clowwindy/shadowsocks.png)](https://travis-ci.org/clowwindy/shadowsocks) +[![Build Status](https://travis-ci.org/clowwindy/shadowsocks.png)](https://travis-ci.org/clowwindy/shadowsocks) Current version: 1.1.1 shadowsocks is a lightweight tunnel proxy which can help you get through firewalls Other ports and clients can be found [here](https://github.com/clowwindy/shadowsocks/wiki/Ports-and-Clients). +Warning: AES, DES, blowfish encryption methods are not implemented the same as other versions(Nodejs, etc) yet, please don't use those methods +if you want to use Python version together with other versions! + usage ----------- diff --git a/config.json b/config.json index 67c6b65..d1a412c 100644 --- a/config.json +++ b/config.json @@ -3,5 +3,6 @@ "server_port":8388, "local_port":1080, "password":"barfoo!", - "timeout":600 + "timeout":600, + "method":null } diff --git a/encrypt.py b/encrypt.py index 57319a5..79be657 100644 --- a/encrypt.py +++ b/encrypt.py @@ -27,6 +27,11 @@ import struct import logging +def random_string(length): + import M2Crypto.Rand + return M2Crypto.Rand.rand_bytes(length) + + def get_table(key): m = hashlib.md5() m.update(key) @@ -55,7 +60,7 @@ def init_table(key, method=None): encrypt_table = ''.join(get_table(key)) decrypt_table = string.maketrans(encrypt_table, string.maketrans('', '')) else: - get_cipher(key, method, 1) + Encryptor(key, method) # make an Encryptor to test if the settings if OK def EVP_BytesToKey(password, key_len, iv_len): @@ -89,39 +94,62 @@ method_supported = { } -def get_cipher(password, method, op): - import M2Crypto.EVP - password = password.encode('utf-8') - method = method.lower() - m = method_supported.get(method, None) - if m: - key, iv = EVP_BytesToKey(password, m[0], m[1]) - return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, op, key_as_bytes=0, d='md5', salt=None, i=1, padding=1) - - logging.error('method %s not supported' % method) - sys.exit(1) - - class Encryptor(object): def __init__(self, key, method=None): if method == 'table': method = None + self.key = key self.method = method + self.iv = None + self.iv_sent = False + self.cipher_iv = '' + self.decipher = None if method is not None: - self.cipher = get_cipher(key, method, 1) - self.decipher = get_cipher(key, method, 0) + self.cipher = self.get_cipher(key, method, 1, iv=random_string(32)) else: self.cipher = None - self.decipher = None + + def get_cipher_len(self, method): + method = method.lower() + m = method_supported.get(method, None) + return m + + def iv_len(self): + return len(self.cipher_iv) + + def get_cipher(self, password, method, op, iv=None): + import M2Crypto.EVP + password = password.encode('utf-8') + method = method.lower() + m = self.get_cipher_len(method) + if m: + key, iv_ = EVP_BytesToKey(password, m[0], m[1]) + if iv is None: + iv = iv_[:m[1]] + if op == 1: + self.cipher_iv = iv[:m[1]] # this iv is for cipher, not decipher + return M2Crypto.EVP.Cipher(method.replace('-', '_'), key, iv, op, key_as_bytes=0, d='md5', salt=None, i=1, padding=1) + + logging.error('method %s not supported' % method) + sys.exit(1) def encrypt(self, buf): - if self.cipher is None: + if self.method is None: return string.translate(buf, encrypt_table) else: - return self.cipher.update(buf) + if self.iv_sent: + return self.cipher.update(buf) + else: + self.iv_sent = True + return self.cipher_iv + self.cipher.update(buf) def decrypt(self, buf): - if self.cipher is None: + if self.method is None: return string.translate(buf, decrypt_table) else: + if self.decipher is None: + decipher_iv_len = self.get_cipher_len(self.method)[1] + decipher_iv = buf[:decipher_iv_len] + self.decipher = self.get_cipher(self.key, self.method, 0, iv=decipher_iv) + buf = buf[decipher_iv_len:] return self.decipher.update(buf) diff --git a/local.py b/local.py index d9fb85f..1f7018e 100755 --- a/local.py +++ b/local.py @@ -67,18 +67,18 @@ class Socks5Server(SocketServer.StreamRequestHandler): while True: r, w, e = select.select(fdset, [], []) if sock in r: - data = sock.recv(4096) + data = self.encrypt(sock.recv(4096)) if len(data) <= 0: break - result = send_all(remote, self.encrypt(data)) + result = send_all(remote, data) if result < len(data): raise Exception('failed to send all data') if remote in r: - data = remote.recv(4096) + data = self.decrypt(remote.recv(4096)) if len(data) <= 0: break - result = send_all(sock, self.decrypt(data)) + result = send_all(sock, data) if result < len(data): raise Exception('failed to send all data') finally: diff --git a/server.py b/server.py index affc37d..0e334b9 100755 --- a/server.py +++ b/server.py @@ -67,17 +67,17 @@ class Socks5Server(SocketServer.StreamRequestHandler): while True: r, w, e = select.select(fdset, [], []) if sock in r: - data = sock.recv(4096) + data = self.decrypt(sock.recv(4096)) if len(data) <= 0: break - result = send_all(remote, self.decrypt(data)) + result = send_all(remote, data) if result < len(data): raise Exception('failed to send all data') if remote in r: - data = remote.recv(4096) + data = self.encrypt(remote.recv(4096)) if len(data) <= 0: break - result = send_all(sock, self.encrypt(data)) + result = send_all(sock, data) if result < len(data): raise Exception('failed to send all data') @@ -95,6 +95,9 @@ class Socks5Server(SocketServer.StreamRequestHandler): try: self.encryptor = encrypt.Encryptor(KEY, METHOD) sock = self.connection + iv_len = self.encryptor.iv_len() + if iv_len: + self.decrypt(sock.recv(iv_len)) addrtype = ord(self.decrypt(sock.recv(1))) if addrtype == 1: addr = socket.inet_ntoa(self.decrypt(self.rfile.read(4)))