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:
parent
d6b40efa5d
commit
74f8f8cb85
5 changed files with 295 additions and 90 deletions
|
@ -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():
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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+
|
||||||
|
|
Loading…
Add table
Reference in a new issue