From cbf8c6705baac1373d4b6b2cdc55d5d7ec01354b Mon Sep 17 00:00:00 2001 From: clowwindy Date: Wed, 5 Nov 2014 18:27:18 +0800 Subject: [PATCH] refactor table into a single cipher plugin --- .travis.yml | 1 + shadowsocks/crypto/rc4_md5.py | 2 +- shadowsocks/crypto/table.py | 178 ++++++++++++++++++++++++++++++++++ shadowsocks/encrypt.py | 146 ++++++++++------------------ shadowsocks/local.py | 2 +- shadowsocks/server.py | 2 +- shadowsocks/utils.py | 10 +- 7 files changed, 237 insertions(+), 104 deletions(-) create mode 100644 shadowsocks/crypto/table.py diff --git a/.travis.yml b/.travis.yml index 403c88a..8864e32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ script: - python tests/test.py -c tests/aes-cfb8.json - python tests/test.py -c tests/rc4-md5.json - python tests/test.py -c tests/salsa20.json + - python tests/test.py -c tests/table.json - python tests/test.py -c tests/server-multi-ports.json - python tests/test.py -c tests/server-multi-passwd.json tests/server-multi-passwd-client-side.json - python tests/test.py -c tests/workers.json diff --git a/shadowsocks/crypto/rc4_md5.py b/shadowsocks/crypto/rc4_md5.py index 7b212d3..3062dcc 100644 --- a/shadowsocks/crypto/rc4_md5.py +++ b/shadowsocks/crypto/rc4_md5.py @@ -55,7 +55,7 @@ def test(): from shadowsocks.crypto import util cipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 1) - decipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 1) + decipher = create_cipher(b'rc4-md5', b'k' * 32, b'i' * 16, 0) util.run_cipher(cipher, decipher) diff --git a/shadowsocks/crypto/table.py b/shadowsocks/crypto/table.py new file mode 100644 index 0000000..2492987 --- /dev/null +++ b/shadowsocks/crypto/table.py @@ -0,0 +1,178 @@ +# !/usr/bin/env python + +# Copyright (c) 2014 clowwindy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import, division, print_function, \ + with_statement + +import string +import struct +import hashlib + + +__all__ = ['ciphers'] + +cached_tables = {} + +if hasattr(string, 'maketrans'): + maketrans = string.maketrans + translate = string.translate +else: + maketrans = bytes.maketrans + translate = bytes.translate + + +def get_table(key): + m = hashlib.md5() + m.update(key) + s = m.digest() + a, b = struct.unpack(' 0: key, iv_ = EVP_BytesToKey(password, m[0], m[1]) - if iv is None: - iv = iv_ - iv = iv[:m[1]] - if op == 1: - # this iv is for cipher not decipher - self.cipher_iv = iv[:m[1]] - return m[2](method, key, iv, op) + else: + # key_length == 0 indicates we should use the key directly + key, iv = password, b'' - logging.error('method %s not supported' % method) - sys.exit(1) + iv = iv[:m[1]] + if op == 1: + # this iv is for cipher not decipher + self.cipher_iv = iv[:m[1]] + return m[2](method, key, iv, op) def encrypt(self, buf): if len(buf) == 0: return buf - if not self.method: - return translate(buf, self.encrypt_table) + if self.iv_sent: + return self.cipher.update(buf) else: - if self.iv_sent: - return self.cipher.update(buf) - else: - self.iv_sent = True - return self.cipher_iv + self.cipher.update(buf) + self.iv_sent = True + return self.cipher_iv + self.cipher.update(buf) def decrypt(self, buf): if len(buf) == 0: return buf - if not self.method: - return translate(buf, self.decrypt_table) - else: - if self.decipher is None: - decipher_iv_len = self.get_cipher_param(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:] - if len(buf) == 0: - return buf - return self.decipher.update(buf) + if self.decipher is None: + decipher_iv_len = self._method_info[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:] + if len(buf) == 0: + return buf + return self.decipher.update(buf) def encrypt_all(password, method, op, data): - if method is not None and method.lower() == b'table': - method = None - if not method: - [encrypt_table, decrypt_table] = init_table(password) - if op: - return translate(data, encrypt_table) - else: - return translate(data, decrypt_table) + result = [] + method = method.lower() + (key_len, iv_len, m) = method_supported[method] + (key, _) = EVP_BytesToKey(password, key_len, iv_len) + if op: + iv = random_string(iv_len) + result.append(iv) else: - result = [] - method = method.lower() - (key_len, iv_len, m) = method_supported[method] - (key, _) = EVP_BytesToKey(password, key_len, iv_len) - if op: - iv = random_string(iv_len) - result.append(iv) - else: - iv = data[:iv_len] - data = data[iv_len:] - cipher = m(method, key, iv, op) - result.append(cipher.update(data)) - return b''.join(result) + iv = data[:iv_len] + data = data[iv_len:] + cipher = m(method, key, iv, op) + result.append(cipher.update(data)) + return b''.join(result) diff --git a/shadowsocks/local.py b/shadowsocks/local.py index 0c627a5..794d929 100755 --- a/shadowsocks/local.py +++ b/shadowsocks/local.py @@ -46,7 +46,7 @@ def main(): utils.print_shadowsocks() - encrypt.init_table(config['password'], config['method']) + encrypt.test_cipher(config['password'], config['method']) try: logging.info("starting local at %s:%d" % diff --git a/shadowsocks/server.py b/shadowsocks/server.py index 2ee74bd..ce36944 100755 --- a/shadowsocks/server.py +++ b/shadowsocks/server.py @@ -54,7 +54,7 @@ def main(): else: config['port_password'][str(server_port)] = config['password'] - encrypt.init_table(config['password'], config['method']) + encrypt.test_cipher(config['password'], config['method']) tcp_servers = [] udp_servers = [] dns_resolver = asyncdns.DNSResolver() diff --git a/shadowsocks/utils.py b/shadowsocks/utils.py index 9b6cd3a..b610f40 100644 --- a/shadowsocks/utils.py +++ b/shadowsocks/utils.py @@ -68,15 +68,15 @@ def find_config(): def check_config(config): - if config.get('local_address', '') in ['0.0.0.0']: + if config.get('local_address', '') in [b'0.0.0.0']: logging.warn('warning: local set to listen 0.0.0.0, which is not safe') - if config.get('server', '') in ['127.0.0.1', 'localhost']: + if config.get('server', '') in [b'127.0.0.1', b'localhost']: logging.warn('warning: server set to listen %s:%s, are you sure?' % (config['server'], config['server_port'])) - if (config.get('method', '') or '').lower() == '': + if (config.get('method', '') or '').lower() == b'table': logging.warn('warning: table is not safe; please use a safer cipher, ' 'like AES-256-CFB') - if (config.get('method', '') or '').lower() == 'rc4': + if (config.get('method', '') or '').lower() == b'rc4': logging.warn('warning: RC4 is not safe; please use a safer cipher, ' 'like AES-256-CFB') if config.get('timeout', 300) < 100: @@ -85,7 +85,7 @@ def check_config(config): if config.get('timeout', 300) > 600: logging.warn('warning: your timeout %d seems too long' % int(config.get('timeout'))) - if config.get('password') in ['mypassword']: + if config.get('password') in [b'mypassword']: logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your ' 'config.json!') exit(1)