Add sodium:aes-256-gcm and openssl:ocb AEAD mode (#784)

* Add sodium:aes-256-gcm and openssl:ocb AEAD mode
Use sodium_increment for nonce_increment if avaiable

* Fix pep8

* Fix python3 test code
This commit is contained in:
Zou Yong 2017-03-10 17:44:42 +08:00 committed by mengskysama
parent d6b40efa5d
commit 74f8f8cb85
5 changed files with 295 additions and 90 deletions

View file

@ -14,11 +14,12 @@
from __future__ import absolute_import, division, print_function, \ from __future__ import absolute_import, division, print_function, \
with_statement with_statement
from ctypes import create_string_buffer from ctypes import c_int, create_string_buffer, byref, c_void_p
import hashlib import hashlib
from struct import pack, unpack from struct import pack, unpack
from shadowsocks.crypto import util
from shadowsocks.crypto import hkdf from shadowsocks.crypto import hkdf
from shadowsocks.common import ord, chr from shadowsocks.common import ord, chr
@ -42,22 +43,58 @@ CIPHER_NONCE_LEN = {
'aes-128-gcm': 12, 'aes-128-gcm': 12,
'aes-192-gcm': 12, 'aes-192-gcm': 12,
'aes-256-gcm': 12, 'aes-256-gcm': 12,
'aes-128-ocb': 12, # requires openssl 1.1
'aes-192-ocb': 12,
'aes-256-ocb': 12,
'chacha20-poly1305': 12, 'chacha20-poly1305': 12,
'chacha20-ietf-poly1305': 12, 'chacha20-ietf-poly1305': 12,
'xchacha20-ietf-poly1305': 24, 'xchacha20-ietf-poly1305': 24,
'sodium:aes-256-gcm': 12,
} }
CIPHER_TAG_LEN = { CIPHER_TAG_LEN = {
'aes-128-gcm': 16, 'aes-128-gcm': 16,
'aes-192-gcm': 16, 'aes-192-gcm': 16,
'aes-256-gcm': 16, 'aes-256-gcm': 16,
'aes-128-ocb': 16, # requires openssl 1.1
'aes-192-ocb': 16,
'aes-256-ocb': 16,
'chacha20-poly1305': 16, 'chacha20-poly1305': 16,
'chacha20-ietf-poly1305': 16, 'chacha20-ietf-poly1305': 16,
'xchacha20-ietf-poly1305': 16, 'xchacha20-ietf-poly1305': 16,
'sodium:aes-256-gcm': 16,
} }
SUBKEY_INFO = b"ss-subkey" SUBKEY_INFO = b"ss-subkey"
libsodium = None
sodium_loaded = False
def load_sodium():
"""
Load libsodium helpers for nonce increment
:return: None
"""
global libsodium, sodium_loaded
libsodium = util.find_library('sodium', 'sodium_increment',
'libsodium')
if libsodium is None:
return
if libsodium.sodium_init() < 0:
libsodium = None
return
libsodium.sodium_increment.restype = c_void_p
libsodium.sodium_increment.argtypes = (
c_void_p, c_int
)
sodium_loaded = True
return
def nonce_increment(nonce, nlen): def nonce_increment(nonce, nlen):
""" """
@ -119,12 +156,28 @@ class AeadCryptoBase(object):
self.encrypt_once = self.aead_encrypt self.encrypt_once = self.aead_encrypt
self.decrypt_once = self.aead_decrypt self.decrypt_once = self.aead_decrypt
# load libsodium for nonce increment
if not sodium_loaded:
load_sodium()
def nonce_increment(self):
"""
AEAD ciphers need nonce to be unique per key
TODO: cache and check unique
:return: None
"""
global libsodium, sodium_loaded
if sodium_loaded:
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
else:
nonce_increment(self._nonce, self._nlen)
def cipher_ctx_init(self): def cipher_ctx_init(self):
""" """
Increase nonce to make it unique for the same key Increase nonce to make it unique for the same key
:return: void :return: None
""" """
nonce_increment(self._nonce, self._nlen) self.nonce_increment()
# print("".join("%02x" % ord(b) for b in self._nonce)) # print("".join("%02x" % ord(b) for b in self._nonce))
def aead_encrypt(self, data): def aead_encrypt(self, data):
@ -132,7 +185,7 @@ class AeadCryptoBase(object):
Encrypt data with authenticate tag Encrypt data with authenticate tag
:param data: plain text :param data: plain text
:return: cipher text with tag :return: str [payload][tag] cipher text with tag
""" """
raise Exception("Must implement aead_encrypt method") raise Exception("Must implement aead_encrypt method")
@ -141,23 +194,21 @@ class AeadCryptoBase(object):
Encrypt a chunk for TCP chunks Encrypt a chunk for TCP chunks
:param data: str :param data: str
:return: (str, int) :return: str [len][tag][payload][tag]
""" """
plen = len(data) plen = len(data)
l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2 # l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2
# network byte order # network byte order
ctext = self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK)) ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))]
if len(ctext) != AEAD_CHUNK_SIZE_LEN + self._tlen: if len(ctext[0]) != AEAD_CHUNK_SIZE_LEN + self._tlen:
raise Exception("size length invalid")
ctext.append(self.aead_encrypt(data))
if len(ctext[1]) != plen + self._tlen:
raise Exception("data length invalid") raise Exception("data length invalid")
self.cipher_ctx_init() return b''.join(ctext)
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): def encrypt(self, data):
""" """
@ -169,25 +220,24 @@ class AeadCryptoBase(object):
""" """
plen = len(data) plen = len(data)
if plen <= AEAD_CHUNK_SIZE_MASK: if plen <= AEAD_CHUNK_SIZE_MASK:
ctext, _ = self.encrypt_chunk(data) ctext = self.encrypt_chunk(data)
return ctext return ctext
ctext, clen = b"", 0 ctext = []
while plen > 0: while plen > 0:
mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \ mlen = plen if plen < AEAD_CHUNK_SIZE_MASK \
else AEAD_CHUNK_SIZE_MASK else AEAD_CHUNK_SIZE_MASK
r, l = self.encrypt_chunk(data[:mlen]) c = self.encrypt_chunk(data[:mlen])
ctext += r ctext.append(c)
clen += l
data = data[mlen:] data = data[mlen:]
plen -= mlen plen -= mlen
return ctext return b''.join(ctext)
def aead_decrypt(self, data): def aead_decrypt(self, data):
""" """
Decrypt data and authenticate tag Decrypt data and authenticate tag
:param data: str cipher text with tag :param data: str [len][tag][payload][tag] cipher text with tag
:return: str plain text :return: str plain text
""" """
raise Exception("Must implement aead_decrypt method") raise Exception("Must implement aead_decrypt method")
@ -196,7 +246,7 @@ class AeadCryptoBase(object):
""" """
Decrypt chunk size Decrypt chunk size
:param data: str encrypted msg :param data: str [size][tag] encrypted chunk payload len
:return: (int, str) msg length and remaining encrypted data :return: (int, str) msg length and remaining encrypted data
""" """
if self._chunk['mlen'] > 0: if self._chunk['mlen'] > 0:
@ -213,7 +263,6 @@ class AeadCryptoBase(object):
if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0: if plen & AEAD_CHUNK_SIZE_MASK != plen or plen <= 0:
raise Exception('Invalid message length') raise Exception('Invalid message length')
self.cipher_ctx_init()
return plen, data[hlen:] return plen, data[hlen:]
def decrypt_chunk_payload(self, plen, data): def decrypt_chunk_payload(self, plen, data):
@ -221,7 +270,7 @@ class AeadCryptoBase(object):
Decrypted encrypted msg payload Decrypted encrypted msg payload
:param plen: int payload length :param plen: int payload length
:param data: str encrypted data :param data: str [payload][tag][[len][tag]....] encrypted data
:return: (str, str) plain text and remaining encrypted data :return: (str, str) plain text and remaining encrypted data
""" """
data = self._chunk['data'] + data data = self._chunk['data'] + data
@ -237,15 +286,13 @@ class AeadCryptoBase(object):
if len(plaintext) != plen: if len(plaintext) != plen:
raise Exception("plaintext length invalid") raise Exception("plaintext length invalid")
self.cipher_ctx_init()
return plaintext, data[plen + self._tlen:] return plaintext, data[plen + self._tlen:]
def decrypt_chunk(self, data): def decrypt_chunk(self, data):
""" """
Decrypt a TCP chunk Decrypt a TCP chunk
:param data: str encrypted msg :param data: str [len][tag][payload][tag][[len][tag]...] encrypted msg
:return: (str, str) decrypted msg and remaining encrypted data :return: (str, str) decrypted msg and remaining encrypted data
""" """
plen, data = self.decrypt_chunk_size(data) plen, data = self.decrypt_chunk_size(data)
@ -261,11 +308,13 @@ class AeadCryptoBase(object):
:param data: str :param data: str
:return: str :return: str
""" """
ptext, left = self.decrypt_chunk(data) ptext = []
pnext, left = self.decrypt_chunk(data)
ptext.append(pnext)
while len(left) > 0: while len(left) > 0:
pnext, left = self.decrypt_chunk(left) pnext, left = self.decrypt_chunk(left)
ptext += pnext ptext.append(pnext)
return ptext return b''.join(ptext)
def test_nonce_increment(): def test_nonce_increment():

View file

@ -23,20 +23,24 @@ from ctypes import c_char_p, c_int, c_long, byref,\
from shadowsocks import common from shadowsocks import common
from shadowsocks.crypto import util from shadowsocks.crypto import util
from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \ from shadowsocks.crypto.aead import AeadCryptoBase, EVP_CTRL_AEAD_SET_IVLEN, \
nonce_increment, EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG
__all__ = ['ciphers'] __all__ = ['ciphers']
libcrypto = None libcrypto = None
loaded = False loaded = False
libsodium = None
buf = None
buf_size = 2048 buf_size = 2048
ctx_cleanup = None
CIPHER_ENC_UNCHANGED = -1 CIPHER_ENC_UNCHANGED = -1
def load_openssl(): def load_openssl():
global loaded, libcrypto, buf, ctx_cleanup global loaded, libcrypto, libsodium, buf, ctx_cleanup
libcrypto = util.find_library(('crypto', 'eay32'), libcrypto = util.find_library(('crypto', 'eay32'),
'EVP_get_cipherbyname', 'EVP_get_cipherbyname',
@ -140,10 +144,13 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
super(OpenSSLAeadCrypto, self).__init__(cipher_name) super(OpenSSLAeadCrypto, self).__init__(cipher_name)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op) AeadCryptoBase.__init__(self, cipher_name, key, iv, op)
key_ptr = c_char_p(self._skey)
r = libcrypto.EVP_CipherInit_ex( r = libcrypto.EVP_CipherInit_ex(
self._ctx, self._ctx,
self._cipher, None, self._cipher,
None, None, c_int(op) None,
key_ptr, None,
c_int(op)
) )
if not r: if not r:
self.clean() self.clean()
@ -165,21 +172,19 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
Need init cipher context after EVP_CipherFinal_ex to reuse context Need init cipher context after EVP_CipherFinal_ex to reuse context
:return: void :return: void
""" """
key_ptr = c_char_p(self._skey)
iv_ptr = c_char_p(self._nonce.raw) iv_ptr = c_char_p(self._nonce.raw)
r = libcrypto.EVP_CipherInit_ex( r = libcrypto.EVP_CipherInit_ex(
self._ctx, self._ctx,
None, None, None,
key_ptr, iv_ptr, None,
None, iv_ptr,
c_int(CIPHER_ENC_UNCHANGED) c_int(CIPHER_ENC_UNCHANGED)
) )
if not r: if not r:
self.clean() self.clean()
raise Exception('can not initialize cipher context') raise Exception('can not initialize cipher context')
nonce_increment(self._nonce, self._nlen) AeadCryptoBase.nonce_increment(self)
# print("".join("%02x" % ord(b) for b in self._nonce))
def set_tag(self, tag): def set_tag(self, tag):
""" """
@ -225,7 +230,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
) )
if not r: if not r:
# print(self._nonce.raw, r, cipher_out_len) # print(self._nonce.raw, r, cipher_out_len)
raise Exception('Verify data failed') raise Exception('Finalize cipher failed')
return buf.raw[:cipher_out_len.value] return buf.raw[:cipher_out_len.value]
def aead_encrypt(self, data): def aead_encrypt(self, data):
@ -236,6 +241,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
:return: cipher text with tag :return: cipher text with tag
""" """
ctext = self.update(data) + self.final() + self.get_tag() ctext = self.update(data) + self.final() + self.get_tag()
self.cipher_ctx_init()
return ctext return ctext
def aead_decrypt(self, data): def aead_decrypt(self, data):
@ -251,6 +257,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
self.set_tag(data[clen - self._tlen:]) self.set_tag(data[clen - self._tlen:])
plaintext = self.update(data[:clen - self._tlen]) + self.final() plaintext = self.update(data[:clen - self._tlen]) + self.final()
self.cipher_ctx_init()
return plaintext return plaintext
@ -305,6 +312,7 @@ ciphers = {
def run_method(method): def run_method(method):
print(method, ': [stream]', 32)
cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1) cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1)
decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0) decipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 0)
@ -313,6 +321,13 @@ def run_method(method):
def run_aead_method(method, key_len=16): def run_aead_method(method, key_len=16):
print(method, ': [payload][tag]', key_len)
cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method))
if not cipher:
cipher = load_cipher(common.to_bytes(method))
if not cipher:
print('cipher not avaiable, please upgrade openssl')
return
key_len = int(key_len) key_len = int(key_len)
cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1) cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0) decipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 0)
@ -320,18 +335,46 @@ def run_aead_method(method, key_len=16):
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def run_aead_method_chunk(method, key_len=16):
print(method, ': chunk([size][tag][payload][tag]', key_len)
cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(method))
if not cipher:
cipher = load_cipher(common.to_bytes(method))
if not cipher:
print('cipher not avaiable, please upgrade openssl')
return
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)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_aes_128_cfb(): def test_aes_128_cfb():
run_method('aes-128-cfb') run_method('aes-128-cfb')
def test_aes_gcm(bits=128): def test_aes_gcm(bits=128):
method = "aes-{0}-gcm".format(bits) method = "aes-{0}-gcm".format(bits)
print(method, int(bits / 8))
run_aead_method(method, bits / 8) run_aead_method(method, bits / 8)
def test_aes_256_gcm(): def test_aes_ocb(bits=128):
test_aes_gcm(256) method = "aes-{0}-ocb".format(bits)
run_aead_method(method, bits / 8)
def test_aes_gcm_chunk(bits=128):
method = "aes-{0}-gcm".format(bits)
run_aead_method_chunk(method, bits / 8)
def test_aes_ocb_chunk(bits=128):
method = "aes-{0}-ocb".format(bits)
run_aead_method_chunk(method, bits / 8)
def test_aes_256_cfb(): def test_aes_256_cfb():
@ -360,7 +403,16 @@ def test_rc4():
if __name__ == '__main__': if __name__ == '__main__':
test_aes_128_cfb() test_aes_128_cfb()
test_aes_256_cfb()
test_aes_gcm(128) test_aes_gcm(128)
test_aes_gcm(192) test_aes_gcm(192)
test_aes_gcm(256) test_aes_gcm(256)
test_aes_256_gcm() test_aes_gcm_chunk(128)
test_aes_gcm_chunk(192)
test_aes_gcm_chunk(256)
test_aes_ocb(128)
test_aes_ocb(192)
test_aes_ocb(256)
test_aes_ocb_chunk(128)
test_aes_ocb_chunk(192)
test_aes_ocb_chunk(256)

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 create_string_buffer, c_void_p
from shadowsocks.crypto import util from shadowsocks.crypto import util
from shadowsocks.crypto import aead
from shadowsocks.crypto.aead import AeadCryptoBase from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers'] __all__ = ['ciphers']
@ -28,6 +29,7 @@ __all__ = ['ciphers']
libsodium = None libsodium = None
loaded = False loaded = False
buf = None
buf_size = 2048 buf_size = 2048
# for salsa20 and chacha20 and chacha20-ietf # for salsa20 and chacha20 and chacha20-ietf
@ -37,10 +39,20 @@ BLOCK_SIZE = 64
def load_libsodium(): def load_libsodium():
global loaded, libsodium, buf global loaded, libsodium, buf
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic', if not aead.sodium_loaded:
'libsodium') aead.load_sodium()
if libsodium is None:
raise Exception('libsodium not found') if aead.sodium_loaded:
libsodium = aead.libsodium
else:
print('load libsodium again')
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
'libsodium')
if libsodium is None:
raise Exception('libsodium not found')
if libsodium.sodium_init() < 0:
raise Exception('libsodium init failed')
libsodium.crypto_stream_salsa20_xor_ic.restype = c_int libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p, libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p,
@ -116,10 +128,26 @@ def load_libsodium():
c_char_p, c_char_p c_char_p, c_char_p
) )
libsodium.sodium_increment.restype = c_void_p # aes-256-gcm, same api structure as above
libsodium.sodium_increment.argtypes = ( libsodium.crypto_aead_aes256gcm_is_available.restype = c_int
c_void_p, c_int
) if libsodium.crypto_aead_aes256gcm_is_available():
libsodium.crypto_aead_aes256gcm_encrypt.restype = c_int
libsodium.crypto_aead_aes256gcm_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_aes256gcm_decrypt.restype = c_int
libsodium.crypto_aead_aes256gcm_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
)
buf = create_string_buffer(buf_size) buf = create_string_buffer(buf_size)
loaded = True loaded = True
@ -192,11 +220,15 @@ class SodiumAeadCrypto(AeadCryptoBase):
crypto_aead_xchacha20poly1305_ietf_decrypt crypto_aead_xchacha20poly1305_ietf_decrypt
else: else:
raise Exception('Unknown cipher') raise Exception('Unknown cipher')
elif cipher_name == 'sodium:aes-256-gcm':
if hasattr(libsodium, 'crypto_aead_aes256gcm_encrypt'):
self.encryptor = libsodium.crypto_aead_aes256gcm_encrypt
self.decryptor = libsodium.crypto_aead_aes256gcm_decrypt
else: else:
raise Exception('Unknown cipher') raise Exception('Unknown cipher')
def cipher_ctx_init(self): def cipher_ctx_init(self):
global libsodium
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen)) libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
# print("".join("%02x" % ord(b) for b in self._nonce)) # print("".join("%02x" % ord(b) for b in self._nonce))
@ -216,6 +248,7 @@ class SodiumAeadCrypto(AeadCryptoBase):
if cipher_out_len.value != plen + self._tlen: if cipher_out_len.value != plen + self._tlen:
raise Exception("Encrypt failed") raise Exception("Encrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value] return buf.raw[:cipher_out_len.value]
def aead_decrypt(self, data): def aead_decrypt(self, data):
@ -238,6 +271,7 @@ class SodiumAeadCrypto(AeadCryptoBase):
if cipher_out_len.value != clen - self._tlen: if cipher_out_len.value != clen - self._tlen:
raise Exception("Encrypt failed") raise Exception("Encrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value] return buf.raw[:cipher_out_len.value]
@ -248,10 +282,13 @@ ciphers = {
'chacha20-poly1305': (32, 32, SodiumAeadCrypto), 'chacha20-poly1305': (32, 32, SodiumAeadCrypto),
'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), 'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto), 'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'sodium:aes-256-gcm': (32, 32, SodiumAeadCrypto),
} }
def test_salsa20(): def test_salsa20():
print("Test salsa20")
cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1) cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0) decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0)
@ -260,6 +297,7 @@ def test_salsa20():
def test_chacha20(): def test_chacha20():
print("Test chacha20")
cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1) cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0) decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0)
@ -268,6 +306,7 @@ def test_chacha20():
def test_chacha20_ietf(): def test_chacha20_ietf():
print("Test chacha20-ietf")
cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1) cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0) decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0)
@ -276,7 +315,7 @@ def test_chacha20_ietf():
def test_chacha20_poly1305(): def test_chacha20_poly1305():
print("Test chacha20-poly1305") print("Test chacha20-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-poly1305', cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1) b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305', decipher = SodiumAeadCrypto('chacha20-poly1305',
@ -285,9 +324,23 @@ def test_chacha20_poly1305():
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def test_chacha20_poly1305_chunk():
print("Test chacha20-poly1305 chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_chacha20_ietf_poly1305(): def test_chacha20_ietf_poly1305():
print("Test chacha20-ietf-poly1305") print("Test chacha20-ietf-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305', cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1) b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305', decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
@ -296,9 +349,52 @@ def test_chacha20_ietf_poly1305():
util.run_cipher(cipher, decipher) util.run_cipher(cipher, decipher)
def test_chacha20_ietf_poly1305_chunk():
print("Test chacha20-ietf-poly1305 chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
def test_aes_256_gcm():
print("Test sodium:aes-256-gcm [payload][tag]")
cipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 0)
util.run_cipher(cipher, decipher)
def test_aes_256_gcm_chunk():
print("Test sodium:aes-256-gcm chunk [size][tag][payload][tag]")
cipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('sodium:aes-256-gcm',
b'k' * 32, b'i' * 32, 0)
cipher.encrypt_once = cipher.encrypt
decipher.decrypt_once = decipher.decrypt
util.run_cipher(cipher, decipher)
if __name__ == '__main__': if __name__ == '__main__':
test_chacha20() test_chacha20()
test_salsa20() test_salsa20()
test_chacha20_ietf() test_chacha20_ietf()
test_chacha20_poly1305() test_chacha20_poly1305()
test_chacha20_poly1305_chunk()
test_chacha20_ietf_poly1305() test_chacha20_ietf_poly1305()
test_chacha20_ietf_poly1305_chunk()
test_aes_256_gcm()
test_aes_256_gcm_chunk()

View file

@ -114,25 +114,27 @@ def run_cipher(cipher, decipher):
rounds = 1 * 1024 rounds = 1 * 1024
plain = urandom(block_size * rounds) plain = urandom(block_size * rounds)
results = [] cipher_results = []
pos = 0 pos = 0
print('test start') print('test start')
start = time.time() start = time.time()
while pos < len(plain): while pos < len(plain):
l = random.randint(100, 32768) l = random.randint(100, 32768)
c = cipher.encrypt(plain[pos:pos + l]) # print(pos, l)
results.append(c) c = cipher.encrypt_once(plain[pos:pos + l])
cipher_results.append(c)
pos += l pos += l
pos = 0 pos = 0
c = b''.join(results) # c = b''.join(cipher_results)
results = [] plain_results = []
while pos < len(c): for c in cipher_results:
l = random.randint(100, 32768) # l = random.randint(100, 32768)
results.append(decipher.decrypt(c[pos:pos + l])) l = len(c)
plain_results.append(decipher.decrypt_once(c))
pos += l pos += l
end = time.time() 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 assert b''.join(plain_results) == plain
def test_find_library(): def test_find_library():

View file

@ -362,18 +362,21 @@ Proxy options:
-l LOCAL_PORT local port, default: 1080 -l LOCAL_PORT local port, default: 1080
-k PASSWORD password -k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb -m METHOD encryption method, default: aes-256-cfb
supported method: Sodium:
chacha20-poly1305, chacha20-ietf-poly1305, chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305, *xchacha20-ietf-poly1305,
aes-128-gcm, aes-192-gcm, aes-256-gcm, sodium:aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb, salsa20, chacha20, chacha20-ietf.
es-128-ctr, aes-192-ctr, aes-256-ctr, OpenSSL:(* v1.1)
camellia-128-cfb, camellia-192-cfb, *aes-128-ocb, *aes-192-ocb, *aes-256-ocb,
camellia-256-cfb, aes-128-gcm, aes-192-gcm, aes-256-gcm,
salsa20, chacha20, chacha20-ietf, aes-128-cfb, aes-192-cfb, aes-256-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb, aes-128-ctr, aes-192-ctr, aes-256-ctr,
rc2-cfb, seed-cfb, camellia-128-cfb, camellia-192-cfb,
rc4, rc4-md5, table. camellia-256-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
-t TIMEOUT timeout in seconds, default: 300 -t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth -a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+ --fast-open use TCP_FASTOPEN, requires Linux 3.7+
@ -404,18 +407,21 @@ Proxy options:
-p SERVER_PORT server port, default: 8388 -p SERVER_PORT server port, default: 8388
-k PASSWORD password -k PASSWORD password
-m METHOD encryption method, default: aes-256-cfb -m METHOD encryption method, default: aes-256-cfb
supported method: Sodium:
chacha20-poly1305, chacha20-ietf-poly1305, chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305, *xchacha20-ietf-poly1305,
aes-128-gcm, aes-192-gcm, aes-256-gcm, sodium:aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb, salsa20, chacha20, chacha20-ietf.
es-128-ctr, aes-192-ctr, aes-256-ctr, OpenSSL:(* v1.1)
camellia-128-cfb, camellia-192-cfb, *aes-128-ocb, *aes-192-ocb, *aes-256-ocb,
camellia-256-cfb, aes-128-gcm, aes-192-gcm, aes-256-gcm,
salsa20, chacha20, chacha20-ietf, aes-128-cfb, aes-192-cfb, aes-256-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb, aes-128-ctr, aes-192-ctr, aes-256-ctr,
rc2-cfb, seed-cfb, camellia-128-cfb, camellia-192-cfb,
rc4, rc4-md5, table. camellia-256-cfb,
bf-cfb, cast5-cfb, des-cfb, idea-cfb,
rc2-cfb, seed-cfb,
rc4, rc4-md5, table.
-t TIMEOUT timeout in seconds, default: 300 -t TIMEOUT timeout in seconds, default: 300
-a ONE_TIME_AUTH one time auth -a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+ --fast-open use TCP_FASTOPEN, requires Linux 3.7+