Add AEAD ciphers support (#775)

* Add AEAD ciphers support, add manger api

* fix test_encrypt_all

* Add manager api requirements

* #775 fix UDP decrypt_all issue

* fix udp replay: decrypt_all return a list
remove manager api

* fix indent according pep8

* remove abc requirement

* remove unused import

* fix pep8 format

* fix test_aes_256_gcm()
This commit is contained in:
Zou Yong 2017-03-04 14:37:29 +08:00 committed by mengskysama
parent c4731de532
commit 445a3c9c7e
15 changed files with 1086 additions and 80 deletions

8
.gitignore vendored
View File

@ -29,13 +29,17 @@ htmlcov
.DS_Store
.idea
tags
#Emacs
.#*
venv/
#vscode
# VS-code
.vscode/
# Pycharm
.idea
.vscode
#ss
config.json

View File

@ -7,7 +7,7 @@ with codecs.open('README.rst', encoding='utf-8') as f:
setup(
name="shadowsocks",
version="2.9.1",
version="3.0.0",
license='http://www.apache.org/licenses/LICENSE-2.0',
description="A fast tunnel proxy that help you get through firewalls",
author='clowwindy',

285
shadowsocks/crypto/aead.py Normal file
View File

@ -0,0 +1,285 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Void Copyright NO ONE
#
# Void License
#
# The code belongs to no one. Do whatever you want.
# Forget about boring open source license.
#
# AEAD cipher for shadowsocks
#
from __future__ import absolute_import, division, print_function, \
with_statement
from ctypes import create_string_buffer
import hashlib
from struct import pack, unpack
from shadowsocks.crypto import hkdf
from shadowsocks.common import ord, chr
EVP_CTRL_GCM_SET_IVLEN = 0x9
EVP_CTRL_GCM_GET_TAG = 0x10
EVP_CTRL_GCM_SET_TAG = 0x11
EVP_CTRL_CCM_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN
EVP_CTRL_CCM_GET_TAG = EVP_CTRL_GCM_GET_TAG
EVP_CTRL_CCM_SET_TAG = EVP_CTRL_GCM_SET_TAG
EVP_CTRL_AEAD_SET_IVLEN = EVP_CTRL_GCM_SET_IVLEN
EVP_CTRL_AEAD_SET_TAG = EVP_CTRL_GCM_SET_TAG
EVP_CTRL_AEAD_GET_TAG = EVP_CTRL_GCM_GET_TAG
AEAD_MSG_LEN_UNKNOWN = 0
AEAD_CHUNK_SIZE_LEN = 2
AEAD_CHUNK_SIZE_MASK = 0x3FFF
CIPHER_NONCE_LEN = {
'aes-128-gcm': 12,
'aes-192-gcm': 12,
'aes-256-gcm': 12,
'chacha20-poly1305': 12,
'chacha20-ietf-poly1305': 12,
'xchacha20-ietf-poly1305': 24,
}
CIPHER_TAG_LEN = {
'aes-128-gcm': 16,
'aes-192-gcm': 16,
'aes-256-gcm': 16,
'chacha20-poly1305': 16,
'chacha20-ietf-poly1305': 16,
'xchacha20-ietf-poly1305': 16,
}
SUBKEY_INFO = b"ss-subkey"
def nonce_increment(nonce, nlen):
"""
Increase nonce by 1 in little endian
From libsodium sodium_increment():
for (; i < nlen; i++) {
c += (uint_fast16_t) n[i];
n[i] = (unsigned char) c;
c >>= 8;
}
:param nonce: string_buffer nonce
:param nlen: nonce length
:return: nonce plus by 1
"""
c = 1
i = 0
# n = create_string_buffer(nlen)
while i < nlen:
c += ord(nonce[i])
nonce[i] = chr(c & 0xFF)
c >>= 8
i += 1
return # n.raw
class AeadCryptoBase(object):
"""
Handles basic aead process of shadowsocks protocol
TCP Chunk (after encryption, *ciphertext*)
+--------------+---------------+--------------+------------+
| *DataLen* | DataLen_TAG | *Data* | Data_TAG |
+--------------+---------------+--------------+------------+
| 2 | Fixed | Variable | Fixed |
+--------------+---------------+--------------+------------+
UDP (after encryption, *ciphertext*)
+--------+-----------+-----------+
| NONCE | *Data* | Data_TAG |
+-------+-----------+-----------+
| Fixed | Variable | Fixed |
+--------+-----------+-----------+
"""
def __init__(self, cipher_name, key, iv, op):
self._op = int(op)
self._salt = iv
self._nlen = CIPHER_NONCE_LEN[cipher_name]
self._nonce = create_string_buffer(self._nlen)
self._tlen = CIPHER_TAG_LEN[cipher_name]
crypto_hkdf = hkdf.Hkdf(iv, key, algorithm=hashlib.sha1)
self._skey = crypto_hkdf.expand(info=SUBKEY_INFO, length=len(key))
# _chunk['mlen']:
# -1, waiting data len header
# n, n > 0, waiting data
self._chunk = {'mlen': AEAD_MSG_LEN_UNKNOWN, 'data': b''}
self.encrypt_once = self.aead_encrypt
self.decrypt_once = self.aead_decrypt
def cipher_ctx_init(self):
"""
Increase nonce to make it unique for the same key
:return: void
"""
nonce_increment(self._nonce, self._nlen)
# print("".join("%02x" % ord(b) for b in self._nonce))
def aead_encrypt(self, data):
"""
Encrypt data with authenticate tag
:param data: plain text
:return: cipher text with tag
"""
raise Exception("Must implement aead_encrypt method")
def encrypt_chunk(self, data):
"""
Encrypt a chunk for TCP chunks
:param data: str
:return: (str, int)
"""
plen = len(data)
l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2
# network byte order
ctext = self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))
if len(ctext) != AEAD_CHUNK_SIZE_LEN + self._tlen:
raise Exception("data length invalid")
self.cipher_ctx_init()
ctext += self.aead_encrypt(data)
if len(ctext) != l:
raise Exception("data length invalid")
self.cipher_ctx_init()
return ctext, l
def encrypt(self, data):
"""
Encrypt data, for TCP divided into chunks
For UDP data, call aead_encrypt instead
:param data: str data bytes
:return: str encrypted data
"""
plen = len(data)
if plen <= AEAD_CHUNK_SIZE_MASK:
ctext, _ = self.encrypt_chunk(data)
return ctext
ctext, clen = b"", 0
while plen > 0:
mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \
else AEAD_CHUNK_SIZE_MASK
r, l = self.encrypt_chunk(data[:mlen])
ctext += r
clen += l
data = data[mlen:]
plen -= mlen
return ctext
def aead_decrypt(self, data):
"""
Decrypt data and authenticate tag
:param data: str cipher text with tag
:return: str plain text
"""
raise Exception("Must implement aead_decrypt method")
def decrypt_chunk_size(self, data):
"""
Decrypt chunk size
:param data: str encrypted msg
:return: (int, str) msg length and remaining encrypted data
"""
if self._chunk['mlen'] > 0:
return self._chunk['mlen'], data
data = self._chunk['data'] + data
self._chunk['data'] = b""
hlen = AEAD_CHUNK_SIZE_LEN + self._tlen
if hlen > len(data):
self._chunk['data'] = data
return 0, b""
plen = self.aead_decrypt(data[:hlen])
plen, = unpack("!H", plen)
if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0:
raise Exception('Invalid message length')
self.cipher_ctx_init()
return plen, data[hlen:]
def decrypt_chunk_payload(self, plen, data):
"""
Decrypted encrypted msg payload
:param plen: int payload length
:param data: str encrypted data
:return: (str, str) plain text and remaining encrypted data
"""
data = self._chunk['data'] + data
if len(data) < plen + self._tlen:
self._chunk['mlen'] = plen
self._chunk['data'] = data
return b"", b""
self._chunk['mlen'] = AEAD_MSG_LEN_UNKNOWN
self._chunk['data'] = b""
plaintext = self.aead_decrypt(data[:plen + self._tlen])
if len(plaintext) != plen:
raise Exception("plaintext length invalid")
self.cipher_ctx_init()
return plaintext, data[plen + self._tlen:]
def decrypt_chunk(self, data):
"""
Decrypt a TCP chunk
:param data: str encrypted msg
:return: (str, str) decrypted msg and remaining encrypted data
"""
plen, data = self.decrypt_chunk_size(data)
if plen <= 0:
return b"", b""
return self.decrypt_chunk_payload(plen, data)
def decrypt(self, data):
"""
Decrypt data for TCP data divided into chunks
For UDP data, call aead_decrypt instead
:param data: str
:return: str
"""
ptext, left = self.decrypt_chunk(data)
while len(left) > 0:
pnext, left = self.decrypt_chunk(left)
ptext += pnext
return ptext
def test_nonce_increment():
buf = create_string_buffer(12)
print("".join("%02x" % ord(b) for b in buf))
nonce_increment(buf, 12)
nonce_increment(buf, 12)
nonce_increment(buf, 12)
nonce_increment(buf, 12)
print("".join("%02x" % ord(b) for b in buf))
for i in range(256):
nonce_increment(buf, 12)
print("".join("%02x" % ord(b) for b in buf))
if __name__ == '__main__':
test_nonce_increment()

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Void Copyright NO ONE
#
# Void License
#
# The code belongs to no one. Do whatever you want.
# Forget about boring open source license.
#
# HKDF for AEAD ciphers
#
from __future__ import division
import hmac
import hashlib
import sys
if sys.version_info[0] == 3:
def buffer(x):
return x
def hkdf_extract(salt, input_key_material, algorithm=hashlib.sha256):
"""
Extract a pseudorandom key suitable for use with hkdf_expand
from the input_key_material and a salt using HMAC with the
provided hash (default SHA-256).
salt should be a random, application-specific byte string. If
salt is None or the empty string, an all-zeros string of the same
length as the hash's block size will be used instead per the RFC.
See the HKDF draft RFC and paper for usage notes.
"""
hash_len = algorithm().digest_size
if salt is None or len(salt) == 0:
salt = bytearray((0,) * hash_len)
return hmac.new(bytes(salt), buffer(input_key_material), algorithm)\
.digest()
def hkdf_expand(pseudo_random_key, info=b"", length=32,
algorithm=hashlib.sha256):
"""
Expand `pseudo_random_key` and `info` into a key of length `bytes` using
HKDF's expand function based on HMAC with the provided hash (default
SHA-256). See the HKDF draft RFC and paper for usage notes.
"""
hash_len = algorithm().digest_size
length = int(length)
if length > 255 * hash_len:
raise Exception("Cannot expand to more than 255 * %d = %d "
"bytes using the specified hash function" %
(hash_len, 255 * hash_len))
blocks_needed = length // hash_len \
+ (0 if length % hash_len == 0 else 1) # ceil
okm = b""
output_block = b""
for counter in range(blocks_needed):
output_block = hmac.new(
pseudo_random_key,
buffer(output_block + info + bytearray((counter + 1,))),
algorithm
).digest()
okm += output_block
return okm[:length]
class Hkdf(object):
"""
Wrapper class for HKDF extract and expand functions
"""
def __init__(self, salt, input_key_material, algorithm=hashlib.sha256):
"""
Extract a pseudorandom key from `salt` and `input_key_material`
arguments.
See the HKDF draft RFC for guidance on setting these values.
The constructor optionally takes a `algorithm` argument defining
the hash function use, defaulting to hashlib.sha256.
"""
self._hash = algorithm
self._prk = hkdf_extract(salt, input_key_material, self._hash)
def expand(self, info, length=32):
"""
Generate output key material based on an `info` value
Arguments:
- info - context to generate the OKM
- length - length in bytes of the key to generate
See the HKDF draft RFC for guidance.
"""
return hkdf_expand(self._prk, info, length, self._hash)

View File

@ -22,6 +22,8 @@ from ctypes import c_char_p, c_int, c_long, byref,\
from shadowsocks import common
from shadowsocks.crypto import util
from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \
nonce_increment, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG
__all__ = ['ciphers']
@ -30,6 +32,8 @@ loaded = False
buf_size = 2048
CIPHER_ENC_UNCHANGED = -1
def load_openssl():
global loaded, libcrypto, buf, ctx_cleanup
@ -45,10 +49,13 @@ def load_openssl():
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
c_char_p, c_char_p, c_int)
libcrypto.EVP_CIPHER_CTX_ctrl.argtypes = (c_void_p, c_int, c_int, c_void_p)
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
c_char_p, c_int)
libcrypto.EVP_CipherFinal_ex.argtypes = (c_void_p, c_void_p, c_void_p)
try:
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
ctx_cleanup = libcrypto.EVP_CIPHER_CTX_cleanup
@ -64,7 +71,7 @@ def load_openssl():
def load_cipher(cipher_name):
func_name = 'EVP_' + cipher_name.replace('-', '_')
func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
if bytes != str:
func_name = str(func_name, 'utf-8')
cipher = getattr(libcrypto, func_name, None)
@ -74,9 +81,13 @@ def load_cipher(cipher_name):
return None
class OpenSSLCrypto(object):
def __init__(self, cipher_name, key, iv, op):
class OpenSSLCryptoBase(object):
"""
OpenSSL crypto base class
"""
def __init__(self, cipher_name):
self._ctx = None
self._cipher = None
if not loaded:
load_openssl()
cipher_name = common.to_bytes(cipher_name)
@ -85,26 +96,30 @@ class OpenSSLCrypto(object):
cipher = load_cipher(cipher_name)
if not cipher:
raise Exception('cipher %s not found in libcrypto' % cipher_name)
key_ptr = c_char_p(key)
iv_ptr = c_char_p(iv)
self._ctx = libcrypto.EVP_CIPHER_CTX_new()
self._cipher = cipher
if not self._ctx:
raise Exception('can not create cipher context')
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
key_ptr, iv_ptr, c_int(op))
if not r:
self.clean()
raise Exception('can not initialize cipher context')
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data):
"""
Encrypt/decrypt data
:param data: str
:return: str
"""
global buf_size, buf
cipher_out_len = c_long(0)
l = len(data)
if buf_size < l:
buf_size = l * 2
buf = create_string_buffer(buf_size)
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
byref(cipher_out_len), c_char_p(data), l)
libcrypto.EVP_CipherUpdate(
self._ctx, byref(buf),
byref(cipher_out_len), c_char_p(data), l
)
# buf is copied to a str object when we access buf.raw
return buf.raw[:cipher_out_len.value]
@ -117,39 +132,190 @@ class OpenSSLCrypto(object):
libcrypto.EVP_CIPHER_CTX_free(self._ctx)
class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
"""
Implement OpenSSL Aead mode: gcm, ocb
"""
def __init__(self, cipher_name, key, iv, op):
super(OpenSSLAeadCrypto, self).__init__(cipher_name)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op)
r = libcrypto.EVP_CipherInit_ex(
self._ctx,
self._cipher, None,
None, None, c_int(op)
)
if not r:
self.clean()
raise Exception('can not initialize cipher context')
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_SET_IVLEN),
c_int(self._nlen),
None
)
if not r:
raise Exception('Set ivlen failed')
self.cipher_ctx_init()
def cipher_ctx_init(self):
"""
Need init cipher context after EVP_CipherFinal_ex to reuse context
:return: void
"""
key_ptr = c_char_p(self._skey)
iv_ptr = c_char_p(self._nonce.raw)
r = libcrypto.EVP_CipherInit_ex(
self._ctx,
None, None,
key_ptr, iv_ptr,
c_int(CIPHER_ENC_UNCHANGED)
)
if not r:
self.clean()
raise Exception('can not initialize cipher context')
nonce_increment(self._nonce, self._nlen)
# print("".join("%02x" % ord(b) for b in self._nonce))
def set_tag(self, tag):
"""
Set tag before decrypt any data (update)
:param tag: authenticated tag
:return: void
"""
tag_len = self._tlen
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_SET_TAG),
c_int(tag_len), c_char_p(tag)
)
if not r:
raise Exception('Set tag failed')
def get_tag(self):
"""
Get authenticated tag, called after EVP_CipherFinal_ex
:return: str
"""
tag_len = self._tlen
tag_buf = create_string_buffer(tag_len)
r = libcrypto.EVP_CIPHER_CTX_ctrl(
self._ctx,
c_int(EVP_CTRL_AEAD_GET_TAG),
c_int(tag_len), byref(tag_buf)
)
if not r:
raise Exception('Get tag failed')
return tag_buf.raw[:tag_len]
def final(self):
"""
Finish encrypt/decrypt a chunk (<= 0x3FFF)
:return: str
"""
global buf_size, buf
cipher_out_len = c_long(0)
r = libcrypto.EVP_CipherFinal_ex(
self._ctx,
byref(buf), byref(cipher_out_len)
)
if not r:
# print(self._nonce.raw, r, cipher_out_len)
raise Exception('Verify data failed')
return buf.raw[:cipher_out_len.value]
def aead_encrypt(self, data):
"""
Encrypt data with authenticate tag
:param data: plain text
:return: cipher text with tag
"""
ctext = self.update(data) + self.final() + self.get_tag()
return ctext
def aead_decrypt(self, data):
"""
Decrypt data and authenticate tag
:param data: cipher text with tag
:return: plain text
"""
clen = len(data)
if clen < self._tlen:
raise Exception('Data too short')
self.set_tag(data[clen - self._tlen:])
plaintext = self.update(data[:clen - self._tlen]) + self.final()
return plaintext
class OpenSSLStreamCrypto(OpenSSLCryptoBase):
"""
Crypto for stream modes: cfb, ofb, ctr
"""
def __init__(self, cipher_name, key, iv, op):
super(OpenSSLStreamCrypto, self).__init__(cipher_name)
key_ptr = c_char_p(key)
iv_ptr = c_char_p(iv)
r = libcrypto.EVP_CipherInit_ex(self._ctx, self._cipher, None,
key_ptr, iv_ptr, c_int(op))
if not r:
self.clean()
raise Exception('can not initialize cipher context')
self.encrypt = self.update
self.decrypt = self.update
ciphers = {
'aes-128-cfb': (16, 16, OpenSSLCrypto),
'aes-192-cfb': (24, 16, OpenSSLCrypto),
'aes-256-cfb': (32, 16, OpenSSLCrypto),
'aes-128-ofb': (16, 16, OpenSSLCrypto),
'aes-192-ofb': (24, 16, OpenSSLCrypto),
'aes-256-ofb': (32, 16, OpenSSLCrypto),
'aes-128-ctr': (16, 16, OpenSSLCrypto),
'aes-192-ctr': (24, 16, OpenSSLCrypto),
'aes-256-ctr': (32, 16, OpenSSLCrypto),
'aes-128-cfb8': (16, 16, OpenSSLCrypto),
'aes-192-cfb8': (24, 16, OpenSSLCrypto),
'aes-256-cfb8': (32, 16, OpenSSLCrypto),
'aes-128-cfb1': (16, 16, OpenSSLCrypto),
'aes-192-cfb1': (24, 16, OpenSSLCrypto),
'aes-256-cfb1': (32, 16, OpenSSLCrypto),
'bf-cfb': (16, 8, OpenSSLCrypto),
'camellia-128-cfb': (16, 16, OpenSSLCrypto),
'camellia-192-cfb': (24, 16, OpenSSLCrypto),
'camellia-256-cfb': (32, 16, OpenSSLCrypto),
'cast5-cfb': (16, 8, OpenSSLCrypto),
'des-cfb': (8, 8, OpenSSLCrypto),
'idea-cfb': (16, 8, OpenSSLCrypto),
'rc2-cfb': (16, 8, OpenSSLCrypto),
'rc4': (16, 0, OpenSSLCrypto),
'seed-cfb': (16, 16, OpenSSLCrypto),
'aes-128-cfb': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb': (32, 16, OpenSSLStreamCrypto),
'aes-128-gcm': (16, 16, OpenSSLAeadCrypto),
'aes-192-gcm': (24, 24, OpenSSLAeadCrypto),
'aes-256-gcm': (32, 32, OpenSSLAeadCrypto),
'aes-128-ofb': (16, 16, OpenSSLStreamCrypto),
'aes-192-ofb': (24, 16, OpenSSLStreamCrypto),
'aes-256-ofb': (32, 16, OpenSSLStreamCrypto),
'aes-128-ctr': (16, 16, OpenSSLStreamCrypto),
'aes-192-ctr': (24, 16, OpenSSLStreamCrypto),
'aes-256-ctr': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb8': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb8': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb8': (32, 16, OpenSSLStreamCrypto),
'aes-128-cfb1': (16, 16, OpenSSLStreamCrypto),
'aes-192-cfb1': (24, 16, OpenSSLStreamCrypto),
'aes-256-cfb1': (32, 16, OpenSSLStreamCrypto),
'bf-cfb': (16, 8, OpenSSLStreamCrypto),
'camellia-128-cfb': (16, 16, OpenSSLStreamCrypto),
'camellia-192-cfb': (24, 16, OpenSSLStreamCrypto),
'camellia-256-cfb': (32, 16, OpenSSLStreamCrypto),
'cast5-cfb': (16, 8, OpenSSLStreamCrypto),
'des-cfb': (8, 8, OpenSSLStreamCrypto),
'idea-cfb': (16, 8, OpenSSLStreamCrypto),
'rc2-cfb': (16, 8, OpenSSLStreamCrypto),
'rc4': (16, 0, OpenSSLStreamCrypto),
'seed-cfb': (16, 16, OpenSSLStreamCrypto),
}
def run_method(method):
cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0)
cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0)
util.run_cipher(cipher, decipher)
def run_aead_method(method, key_len=16):
key_len = int(key_len)
cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0)
util.run_cipher(cipher, decipher)
@ -158,6 +324,16 @@ def test_aes_128_cfb():
run_method('aes-128-cfb')
def test_aes_gcm(bits=128):
method = "aes-{0}-gcm".format(bits)
print(method, int(bits / 8))
run_aead_method(method, bits / 8)
def test_aes_256_gcm():
test_aes_gcm(256)
def test_aes_256_cfb():
run_method('aes-256-cfb')
@ -184,3 +360,7 @@ def test_rc4():
if __name__ == '__main__':
test_aes_128_cfb()
test_aes_gcm(128)
test_aes_gcm(192)
test_aes_gcm(256)
test_aes_256_gcm()

View File

@ -18,7 +18,6 @@ from __future__ import absolute_import, division, print_function, \
with_statement
import hashlib
from shadowsocks.crypto import openssl
__all__ = ['ciphers']
@ -30,7 +29,7 @@ def create_cipher(alg, key, iv, op, key_as_bytes=0, d=None, salt=None,
md5.update(key)
md5.update(iv)
rc4_key = md5.digest()
return openssl.OpenSSLCrypto(b'rc4', rc4_key, b'', op)
return openssl.OpenSSLStreamCrypto(b'rc4', rc4_key, b'', op)
ciphers = {

View File

@ -21,6 +21,7 @@ from ctypes import c_char_p, c_int, c_ulonglong, byref, c_ulong, \
create_string_buffer, c_void_p
from shadowsocks.crypto import util
from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers']
@ -59,6 +60,67 @@ def load_libsodium():
c_ulong,
c_char_p)
# chacha20-poly1305
libsodium.crypto_aead_chacha20poly1305_encrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_encrypt.argtypes = (
c_void_p, c_void_p, # c, clen
c_char_p, c_ulonglong, # m, mlen
c_char_p, c_ulonglong, # ad, adlen
c_char_p, # nsec, not used
c_char_p, c_char_p # npub, k
)
libsodium.crypto_aead_chacha20poly1305_decrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_decrypt.argtypes = (
c_void_p, c_void_p, # m, mlen
c_char_p, # nsec, not used
c_char_p, c_ulonglong, # c, clen
c_char_p, c_ulonglong, # ad, adlen
c_char_p, c_char_p # npub, k
)
# chacha20-ietf-poly1305, same api structure as above
libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_ietf_encrypt.argtypes = (
c_void_p, c_void_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p,
c_char_p, c_char_p
)
libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.restype = c_int
libsodium.crypto_aead_chacha20poly1305_ietf_decrypt.argtypes = (
c_void_p, c_void_p,
c_char_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p, c_char_p
)
# xchacha20-ietf-poly1305, same api structure as above
if hasattr(libsodium, 'crypto_aead_xchacha20poly1305_ietf_encrypt'):
libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.restype = c_int
libsodium.crypto_aead_xchacha20poly1305_ietf_encrypt.argtypes = (
c_void_p, c_void_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p,
c_char_p, c_char_p
)
libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.restype = c_int
libsodium.crypto_aead_xchacha20poly1305_ietf_decrypt.argtypes = (
c_void_p, c_void_p,
c_char_p,
c_char_p, c_ulonglong,
c_char_p, c_ulonglong,
c_char_p, c_char_p
)
libsodium.sodium_increment.restype = c_void_p
libsodium.sodium_increment.argtypes = (
c_void_p, c_int
)
buf = create_string_buffer(buf_size)
loaded = True
@ -81,6 +143,10 @@ class SodiumCrypto(object):
raise Exception('Unknown cipher')
# byte counter, not block counter
self.counter = 0
self.encrypt = self.update
self.decrypt = self.update
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data):
global buf_size, buf
@ -103,10 +169,85 @@ class SodiumCrypto(object):
return buf.raw[padding:padding + l]
class SodiumAeadCrypto(AeadCryptoBase):
def __init__(self, cipher_name, key, iv, op):
if not loaded:
load_libsodium()
AeadCryptoBase.__init__(self, cipher_name, key, iv, op)
if cipher_name == 'chacha20-poly1305':
self.encryptor = libsodium.crypto_aead_chacha20poly1305_encrypt
self.decryptor = libsodium.crypto_aead_chacha20poly1305_decrypt
elif cipher_name == 'chacha20-ietf-poly1305':
self.encryptor = libsodium.\
crypto_aead_chacha20poly1305_ietf_encrypt
self.decryptor = libsodium.\
crypto_aead_chacha20poly1305_ietf_decrypt
elif cipher_name == 'xchacha20-ietf-poly1305':
if hasattr(libsodium,
'crypto_aead_xchacha20poly1305_ietf_encrypt'):
self.encryptor = libsodium.\
crypto_aead_xchacha20poly1305_ietf_encrypt
self.decryptor = libsodium.\
crypto_aead_xchacha20poly1305_ietf_decrypt
else:
raise Exception('Unknown cipher')
else:
raise Exception('Unknown cipher')
def cipher_ctx_init(self):
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
# print("".join("%02x" % ord(b) for b in self._nonce))
def aead_encrypt(self, data):
global buf, buf_size
plen = len(data)
if buf_size < plen + self._tlen:
buf_size = (plen + self._tlen) * 2
buf = create_string_buffer(buf_size)
cipher_out_len = c_ulonglong(0)
self.encryptor(
byref(buf), byref(cipher_out_len),
c_char_p(data), c_ulonglong(plen),
None, c_ulonglong(0), None,
c_char_p(self._nonce.raw), c_char_p(self._skey)
)
if cipher_out_len.value != plen + self._tlen:
raise Exception("Encrypt failed")
return buf.raw[:cipher_out_len.value]
def aead_decrypt(self, data):
global buf, buf_size
clen = len(data)
if buf_size < clen:
buf_size = clen * 2
buf = create_string_buffer(buf_size)
cipher_out_len = c_ulonglong(0)
r = self.decryptor(
byref(buf), byref(cipher_out_len),
None,
c_char_p(data), c_ulonglong(clen),
None, c_ulonglong(0),
c_char_p(self._nonce.raw), c_char_p(self._skey)
)
if r != 0:
raise Exception("Decrypt failed")
if cipher_out_len.value != clen - self._tlen:
raise Exception("Encrypt failed")
return buf.raw[:cipher_out_len.value]
ciphers = {
'salsa20': (32, 8, SodiumCrypto),
'chacha20': (32, 8, SodiumCrypto),
'chacha20-ietf': (32, 12, SodiumCrypto),
'chacha20-poly1305': (32, 32, SodiumAeadCrypto),
'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
}
@ -133,7 +274,31 @@ def test_chacha20_ietf():
util.run_cipher(cipher, decipher)
def test_chacha20_poly1305():
print("Test chacha20-poly1305")
cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
def test_chacha20_ietf_poly1305():
print("Test chacha20-ietf-poly1305")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
if __name__ == '__main__':
test_chacha20()
test_salsa20()
test_chacha20_ietf()
test_chacha20_poly1305()
test_chacha20_ietf_poly1305()

View File

@ -58,6 +58,10 @@ class TableCipher(object):
def __init__(self, cipher_name, key, iv, op):
self._encrypt_table, self._decrypt_table = init_table(key)
self._op = op
self.encrypt = self.update
self.decrypt = self.update
self.encrypt_once = self.update
self.decrypt_once = self.update
def update(self, data):
if self._op:

View File

@ -33,7 +33,7 @@ def find_library_nt(name):
results.append(fname)
if fname.lower().endswith(".dll"):
continue
fname = fname + ".dll"
fname += ".dll"
if os.path.isfile(fname):
results.append(fname)
return results
@ -92,14 +92,27 @@ def find_library(possible_lib_names, search_symbol, library_name):
return None
def parse_mode(cipher_nme):
"""
Parse the cipher mode from cipher name
e.g. aes-128-gcm, the mode is gcm
:param cipher_nme: str cipher name, aes-128-cfb, aes-128-gcm ...
:return: str/None The mode, cfb, gcm ...
"""
hyphen = cipher_nme.rfind('-')
if hyphen > 0:
return cipher_nme[hyphen:]
return None
def run_cipher(cipher, decipher):
from os import urandom
import random
import time
BLOCK_SIZE = 16384
block_size = 16384
rounds = 1 * 1024
plain = urandom(BLOCK_SIZE * rounds)
plain = urandom(block_size * rounds)
results = []
pos = 0
@ -107,18 +120,18 @@ def run_cipher(cipher, decipher):
start = time.time()
while pos < len(plain):
l = random.randint(100, 32768)
c = cipher.update(plain[pos:pos + l])
c = cipher.encrypt(plain[pos:pos + l])
results.append(c)
pos += l
pos = 0
c = b''.join(results)
results = []
while pos < len(plain):
while pos < len(c):
l = random.randint(100, 32768)
results.append(decipher.update(c[pos:pos + l]))
results.append(decipher.decrypt(c[pos:pos + l]))
pos += l
end = time.time()
print('speed: %d bytes/s' % (BLOCK_SIZE * rounds / (end - start)))
print('speed: %d bytes/s' % (block_size * rounds / (end - start)))
assert b''.join(results) == plain

235
shadowsocks/cryptor.py Normal file
View File

@ -0,0 +1,235 @@
#!/usr/bin/env python
#
# Copyright 2012-2015 clowwindy
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import, division, print_function, \
with_statement
import os
import sys
import hashlib
import logging
from shadowsocks import common
from shadowsocks.crypto import rc4_md5, openssl, sodium, table
CIPHER_ENC_ENCRYPTION = 1
CIPHER_ENC_DECRYPTION = 0
METHOD_INFO_KEY_LEN = 0
METHOD_INFO_IV_LEN = 1
METHOD_INFO_CRYPTO = 2
method_supported = {}
method_supported.update(rc4_md5.ciphers)
method_supported.update(openssl.ciphers)
method_supported.update(sodium.ciphers)
method_supported.update(table.ciphers)
def random_string(length):
return os.urandom(length)
cached_keys = {}
def try_cipher(key, method=None):
Cryptor(key, method)
def EVP_BytesToKey(password, key_len, iv_len):
# equivalent to OpenSSL's EVP_BytesToKey() with count 1
# so that we make the same key and iv as nodejs version
cached_key = '%s-%d-%d' % (password, key_len, iv_len)
r = cached_keys.get(cached_key, None)
if r:
return r
m = []
i = 0
while len(b''.join(m)) < (key_len + iv_len):
md5 = hashlib.md5()
data = password
if i > 0:
data = m[i - 1] + password
md5.update(data)
m.append(md5.digest())
i += 1
ms = b''.join(m)
key = ms[:key_len]
iv = ms[key_len:key_len + iv_len]
cached_keys[cached_key] = (key, iv)
return key, iv
class Cryptor(object):
def __init__(self, password, method):
self.password = password
self.key = None
self.method = method
self.iv_sent = False
self.cipher_iv = b''
self.decipher = None
self.decipher_iv = None
method = method.lower()
self._method_info = Cryptor.get_method_info(method)
if self._method_info:
self.cipher = self.get_cipher(
password, method, CIPHER_ENC_ENCRYPTION,
random_string(self._method_info[METHOD_INFO_IV_LEN])
)
else:
logging.error('method %s not supported' % method)
sys.exit(1)
@staticmethod
def get_method_info(method):
method = method.lower()
m = method_supported.get(method)
return m
def iv_len(self):
return len(self.cipher_iv)
def get_cipher(self, password, method, op, iv):
password = common.to_bytes(password)
m = self._method_info
if m[METHOD_INFO_KEY_LEN] > 0:
key, _ = EVP_BytesToKey(password,
m[METHOD_INFO_KEY_LEN],
m[METHOD_INFO_IV_LEN])
else:
# key_length == 0 indicates we should use the key directly
key, iv = password, b''
self.key = key
iv = iv[:m[METHOD_INFO_IV_LEN]]
if op == CIPHER_ENC_ENCRYPTION:
# this iv is for cipher not decipher
self.cipher_iv = iv
return m[METHOD_INFO_CRYPTO](method, key, iv, op)
def encrypt(self, buf):
if len(buf) == 0:
return buf
if self.iv_sent:
return self.cipher.encrypt(buf)
else:
self.iv_sent = True
return self.cipher_iv + self.cipher.encrypt(buf)
def decrypt(self, buf):
if len(buf) == 0:
return buf
if self.decipher is None:
decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN]
decipher_iv = buf[:decipher_iv_len]
self.decipher_iv = decipher_iv
self.decipher = self.get_cipher(
self.password, self.method,
CIPHER_ENC_DECRYPTION,
iv=decipher_iv
)
buf = buf[decipher_iv_len:]
if len(buf) == 0:
return buf
return self.decipher.decrypt(buf)
def gen_key_iv(password, method):
method = method.lower()
(key_len, iv_len, m) = method_supported[method]
if key_len > 0:
key, _ = EVP_BytesToKey(password, key_len, iv_len)
else:
key = password
iv = random_string(iv_len)
return key, iv, m
def encrypt_all_m(key, iv, m, method, data):
result = [iv]
cipher = m(method, key, iv, 1)
result.append(cipher.encrypt_once(data))
return b''.join(result)
def decrypt_all(password, method, data):
result = []
method = method.lower()
(key, iv, m) = gen_key_iv(password, method)
iv = data[:len(iv)]
data = data[len(iv):]
cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION)
result.append(cipher.decrypt_once(data))
return b''.join(result), key, iv
def encrypt_all(password, method, data):
result = []
method = method.lower()
(key, iv, m) = gen_key_iv(password, method)
result.append(iv)
cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION)
result.append(cipher.encrypt_once(data))
return b''.join(result)
CIPHERS_TO_TEST = [
'aes-128-cfb',
'aes-256-cfb',
'rc4-md5',
'salsa20',
'chacha20',
'table',
]
def test_encryptor():
from os import urandom
plain = urandom(10240)
for method in CIPHERS_TO_TEST:
logging.warn(method)
encryptor = Cryptor(b'key', method)
decryptor = Cryptor(b'key', method)
cipher = encryptor.encrypt(plain)
plain2 = decryptor.decrypt(cipher)
assert plain == plain2
def test_encrypt_all():
from os import urandom
plain = urandom(10240)
for method in CIPHERS_TO_TEST:
logging.warn(method)
cipher = encrypt_all(b'key', method, plain)
plain2, key, iv = decrypt_all(b'key', method, cipher)
assert plain == plain2
def test_encrypt_all_m():
from os import urandom
plain = urandom(10240)
for method in CIPHERS_TO_TEST:
logging.warn(method)
key, iv, m = gen_key_iv(b'key', method)
cipher = encrypt_all_m(key, iv, m, method, plain)
plain2, key, iv = decrypt_all(b'key', method, cipher)
assert plain == plain2
if __name__ == '__main__':
test_encrypt_all()
test_encryptor()
test_encrypt_all_m()

View File

@ -153,7 +153,7 @@ def encrypt_all_m(key, iv, m, method, data):
return b''.join(result)
def decrypt_all(password, method, data):
def dencrypt_all(password, method, data):
result = []
method = method.lower()
(key_len, iv_len, m) = method_supported[method]
@ -228,7 +228,7 @@ def test_encrypt_all_m():
logging.warn(method)
key, iv, m = gen_key_iv(b'key', method)
cipher = encrypt_all_m(key, iv, m, method, plain)
plain2, key, iv = decrypt_all(b'key', method, cipher)
plain2, key, iv = dencrypt_all(b'key', method, cipher)
assert plain == plain2

View File

@ -202,7 +202,7 @@ def test():
import time
import threading
import struct
from shadowsocks import encrypt
from shadowsocks import cryptor
logging.basicConfig(level=5,
format='%(asctime)s %(levelname)-8s %(message)s',
@ -252,7 +252,7 @@ def test():
# test statistics for TCP
header = common.pack_addr(b'google.com') + struct.pack('>H', 80)
data = encrypt.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb', 1,
data = cryptor.encrypt_all(b'asdfadsfasdf', 'aes-256-cfb',
header + b'GET /\r\n\r\n')
tcp_cli = socket.socket()
tcp_cli.connect(('127.0.0.1', 7001))
@ -270,7 +270,7 @@ def test():
# test statistics for UDP
header = common.pack_addr(b'127.0.0.1') + struct.pack('>H', 80)
data = encrypt.encrypt_all(b'foobar2', 'aes-256-cfb', 1,
data = cryptor.encrypt_all(b'foobar2', 'aes-256-cfb',
header + b'test')
udp_cli = socket.socket(type=socket.SOCK_DGRAM)
udp_cli.sendto(data, ('127.0.0.1', 8382))

View File

@ -28,7 +28,7 @@ import traceback
from functools import wraps
from shadowsocks.common import to_bytes, to_str, IPNetwork
from shadowsocks import encrypt
from shadowsocks import cryptor
VERBOSE_LEVEL = 5
@ -200,7 +200,7 @@ def check_config(config, is_local):
if config.get('dns_server', None) is not None:
logging.info('Specified DNS server: %s' % config['dns_server'])
encrypt.try_cipher(config['password'], config['method'])
cryptor.try_cipher(config['password'], config['method'])
def get_config(is_local):
@ -362,6 +362,18 @@ Proxy options:
-l LOCAL_PORT local port, default: 1080
-k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb
supported method:
chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305,
aes-128-gcm, aes-192-gcm, aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
es-128-ctr, aes-192-ctr, aes-256-ctr,
camellia-128-cfb, camellia-192-cfb,
camellia-256-cfb,
salsa20, chacha20, chacha20-ietf,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
-t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+
@ -392,6 +404,18 @@ Proxy options:
-p SERVER_PORT server port, default: 8388
-k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb
supported method:
chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305,
aes-128-gcm, aes-192-gcm, aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
es-128-ctr, aes-192-ctr, aes-256-ctr,
camellia-128-cfb, camellia-192-cfb,
camellia-256-cfb,
salsa20, chacha20, chacha20-ietf,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
-t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+

View File

@ -26,7 +26,7 @@ import logging
import traceback
import random
from shadowsocks import encrypt, eventloop, shell, common
from shadowsocks import cryptor, eventloop, shell, common
from shadowsocks.common import parse_header, onetimeauth_verify, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ONETIMEAUTH_CHUNK_BYTES, \
ONETIMEAUTH_CHUNK_DATA_LEN, ADDRTYPE_AUTH
@ -94,9 +94,9 @@ WAIT_STATUS_READWRITING = WAIT_STATUS_READING | WAIT_STATUS_WRITING
BUF_SIZE = 32 * 1024
# helper exceptions for TCPRelayHandler
class BadSocksHeader(Exception):
pass
@ -125,8 +125,8 @@ class TCPRelayHandler(object):
# if is_local, this is sslocal
self._is_local = is_local
self._stage = STAGE_INIT
self._encryptor = encrypt.Encryptor(config['password'],
config['method'])
self._cryptor = cryptor.Cryptor(config['password'],
config['method'])
self._ota_enable = config.get('one_time_auth', False)
self._ota_enable_session = self._ota_enable
self._ota_buff_head = b''
@ -257,7 +257,7 @@ class TCPRelayHandler(object):
return
if self._ota_enable_session:
data = self._ota_chunk_data_gen(data)
data = self._encryptor.encrypt(data)
data = self._cryptor.encrypt(data)
self._data_to_write_to_remote.append(data)
if self._config['fast_open'] and not self._fastopen_connected:
@ -347,7 +347,7 @@ class TCPRelayHandler(object):
offset = header_length + ONETIMEAUTH_BYTES
_hash = data[header_length: offset]
_data = data[:header_length]
key = self._encryptor.decipher_iv + self._encryptor.key
key = self._cryptor.decipher_iv + self._cryptor.key
if onetimeauth_verify(_hash, _data, key) is False:
logging.warn('one time auth fail')
self.destroy()
@ -368,11 +368,11 @@ class TCPRelayHandler(object):
# ATYP & 0x10 == 0x10, then OTA is enabled.
if self._ota_enable_session:
data = common.chr(addrtype | ADDRTYPE_AUTH) + data[1:]
key = self._encryptor.cipher_iv + self._encryptor.key
key = self._cryptor.cipher_iv + self._cryptor.key
_header = data[:header_length]
sha110 = onetimeauth_gen(data, key)
data = _header + sha110 + data[header_length:]
data_to_send = self._encryptor.encrypt(data)
data_to_send = self._cryptor.encrypt(data)
self._data_to_write_to_remote.append(data_to_send)
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(self._chosen_server[0],
@ -475,7 +475,7 @@ class TCPRelayHandler(object):
_hash = self._ota_buff_head[ONETIMEAUTH_CHUNK_DATA_LEN:]
_data = self._ota_buff_data
index = struct.pack('>I', self._ota_chunk_idx)
key = self._encryptor.decipher_iv + index
key = self._cryptor.decipher_iv + index
if onetimeauth_verify(_hash, _data, key) is False:
logging.warn('one time auth fail, drop chunk !')
else:
@ -490,7 +490,7 @@ class TCPRelayHandler(object):
def _ota_chunk_data_gen(self, data):
data_len = struct.pack(">H", len(data))
index = struct.pack('>I', self._ota_chunk_idx)
key = self._encryptor.cipher_iv + index
key = self._cryptor.cipher_iv + index
sha110 = onetimeauth_gen(data, key)
self._ota_chunk_idx += 1
return data_len + sha110 + data
@ -499,7 +499,7 @@ class TCPRelayHandler(object):
if self._is_local:
if self._ota_enable_session:
data = self._ota_chunk_data_gen(data)
data = self._encryptor.encrypt(data)
data = self._cryptor.encrypt(data)
self._write_to_sock(data, self._remote_sock)
else:
if self._ota_enable_session:
@ -564,7 +564,7 @@ class TCPRelayHandler(object):
return
self._update_activity(len(data))
if not is_local:
data = self._encryptor.decrypt(data)
data = self._cryptor.decrypt(data)
if not data:
return
if self._stage == STAGE_STREAM:
@ -598,9 +598,9 @@ class TCPRelayHandler(object):
return
self._update_activity(len(data))
if self._is_local:
data = self._encryptor.decrypt(data)
data = self._cryptor.decrypt(data)
else:
data = self._encryptor.encrypt(data)
data = self._cryptor.encrypt(data)
try:
self._write_to_sock(data, self._local_sock)
except Exception as e:

View File

@ -68,7 +68,7 @@ import struct
import errno
import random
from shadowsocks import encrypt, eventloop, lru_cache, common, shell
from shadowsocks import cryptor, eventloop, lru_cache, common, shell
from shadowsocks.common import parse_header, pack_addr, onetimeauth_verify, \
onetimeauth_gen, ONETIMEAUTH_BYTES, ADDRTYPE_AUTH
@ -170,7 +170,7 @@ class UDPRelay(object):
else:
data = data[3:]
else:
data, key, iv = encrypt.decrypt_all(self._password,
data, key, iv = cryptor.decrypt_all(self._password,
self._method,
data)
# decrypt data
@ -234,11 +234,11 @@ class UDPRelay(object):
self._eventloop.add(client, eventloop.POLL_IN, self)
if self._is_local:
key, iv, m = encrypt.gen_key_iv(self._password, self._method)
key, iv, m = cryptor.gen_key_iv(self._password, self._method)
# spec https://shadowsocks.org/en/spec/one-time-auth.html
if self._ota_enable_session:
data = self._ota_chunk_data_gen(key, iv, data)
data = encrypt.encrypt_all_m(key, iv, m, self._method, data)
data = cryptor.encrypt_all_m(key, iv, m, self._method, data)
if not data:
return
else:
@ -267,13 +267,12 @@ class UDPRelay(object):
# drop
return
data = pack_addr(r_addr[0]) + struct.pack('>H', r_addr[1]) + data
response = encrypt.encrypt_all(self._password, self._method, 1,
data)
response = cryptor.encrypt_all(self._password, self._method, data)
if not response:
return
else:
data = encrypt.encrypt_all(self._password, self._method, 0,
data)
data, key, iv = cryptor.decrypt_all(self._password,
self._method, data)
if not data:
return
header_result = parse_header(data)