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, \
with_statement
from ctypes import create_string_buffer
from ctypes import c_int, create_string_buffer, byref, c_void_p
import hashlib
from struct import pack, unpack
from shadowsocks.crypto import util
from shadowsocks.crypto import hkdf
from shadowsocks.common import ord, chr
@ -42,22 +43,58 @@ CIPHER_NONCE_LEN = {
'aes-128-gcm': 12,
'aes-192-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-ietf-poly1305': 12,
'xchacha20-ietf-poly1305': 24,
'sodium:aes-256-gcm': 12,
}
CIPHER_TAG_LEN = {
'aes-128-gcm': 16,
'aes-192-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-ietf-poly1305': 16,
'xchacha20-ietf-poly1305': 16,
'sodium:aes-256-gcm': 16,
}
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):
"""
@ -119,12 +156,28 @@ class AeadCryptoBase(object):
self.encrypt_once = self.aead_encrypt
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):
"""
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))
def aead_encrypt(self, data):
@ -132,7 +185,7 @@ class AeadCryptoBase(object):
Encrypt data with authenticate tag
:param data: plain text
:return: cipher text with tag
:return: str [payload][tag] cipher text with tag
"""
raise Exception("Must implement aead_encrypt method")
@ -141,23 +194,21 @@ class AeadCryptoBase(object):
Encrypt a chunk for TCP chunks
:param data: str
:return: (str, int)
:return: str [len][tag][payload][tag]
"""
plen = len(data)
l = AEAD_CHUNK_SIZE_LEN + plen + self._tlen * 2
# 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:
ctext = [self.aead_encrypt(pack("!H", plen & AEAD_CHUNK_SIZE_MASK))]
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")
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
return b''.join(ctext)
def encrypt(self, data):
"""
@ -169,25 +220,24 @@ class AeadCryptoBase(object):
"""
plen = len(data)
if plen <= AEAD_CHUNK_SIZE_MASK:
ctext, _ = self.encrypt_chunk(data)
ctext = self.encrypt_chunk(data)
return ctext
ctext, clen = b"", 0
ctext = []
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
c = self.encrypt_chunk(data[:mlen])
ctext.append(c)
data = data[mlen:]
plen -= mlen
return ctext
return b''.join(ctext)
def aead_decrypt(self, data):
"""
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
"""
raise Exception("Must implement aead_decrypt method")
@ -196,7 +246,7 @@ class AeadCryptoBase(object):
"""
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
"""
if self._chunk['mlen'] > 0:
@ -213,7 +263,6 @@ class AeadCryptoBase(object):
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):
@ -221,7 +270,7 @@ class AeadCryptoBase(object):
Decrypted encrypted msg payload
: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
"""
data = self._chunk['data'] + data
@ -237,15 +286,13 @@ class AeadCryptoBase(object):
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
:param data: str [len][tag][payload][tag][[len][tag]...] encrypted msg
:return: (str, str) decrypted msg and remaining encrypted data
"""
plen, data = self.decrypt_chunk_size(data)
@ -261,11 +308,13 @@ class AeadCryptoBase(object):
:param data: str
:return: str
"""
ptext, left = self.decrypt_chunk(data)
ptext = []
pnext, left = self.decrypt_chunk(data)
ptext.append(pnext)
while len(left) > 0:
pnext, left = self.decrypt_chunk(left)
ptext += pnext
return ptext
ptext.append(pnext)
return b''.join(ptext)
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.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
EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG
__all__ = ['ciphers']
libcrypto = None
loaded = False
libsodium = None
buf = None
buf_size = 2048
ctx_cleanup = None
CIPHER_ENC_UNCHANGED = -1
def load_openssl():
global loaded, libcrypto, buf, ctx_cleanup
global loaded, libcrypto, libsodium, buf, ctx_cleanup
libcrypto = util.find_library(('crypto', 'eay32'),
'EVP_get_cipherbyname',
@ -140,10 +144,13 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
super(OpenSSLAeadCrypto, self).__init__(cipher_name)
AeadCryptoBase.__init__(self, cipher_name, key, iv, op)
key_ptr = c_char_p(self._skey)
r = libcrypto.EVP_CipherInit_ex(
self._ctx,
self._cipher, None,
None, None, c_int(op)
self._cipher,
None,
key_ptr, None,
c_int(op)
)
if not r:
self.clean()
@ -165,21 +172,19 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
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,
None,
None,
None, 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))
AeadCryptoBase.nonce_increment(self)
def set_tag(self, tag):
"""
@ -225,7 +230,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
)
if not r:
# 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]
def aead_encrypt(self, data):
@ -236,6 +241,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
:return: cipher text with tag
"""
ctext = self.update(data) + self.final() + self.get_tag()
self.cipher_ctx_init()
return ctext
def aead_decrypt(self, data):
@ -251,6 +257,7 @@ class OpenSSLAeadCrypto(OpenSSLCryptoBase, AeadCryptoBase):
self.set_tag(data[clen - self._tlen:])
plaintext = self.update(data[:clen - self._tlen]) + self.final()
self.cipher_ctx_init()
return plaintext
@ -305,6 +312,7 @@ ciphers = {
def run_method(method):
print(method, ': [stream]', 32)
cipher = OpenSSLStreamCrypto(method, b'k' * 32, b'i' * 16, 1)
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):
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)
cipher = OpenSSLAeadCrypto(method, b'k' * key_len, b'i' * key_len, 1)
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)
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():
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_ocb(bits=128):
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():
@ -360,7 +403,16 @@ def test_rc4():
if __name__ == '__main__':
test_aes_128_cfb()
test_aes_256_cfb()
test_aes_gcm(128)
test_aes_gcm(192)
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
from shadowsocks.crypto import util
from shadowsocks.crypto import aead
from shadowsocks.crypto.aead import AeadCryptoBase
__all__ = ['ciphers']
@ -28,6 +29,7 @@ __all__ = ['ciphers']
libsodium = None
loaded = False
buf = None
buf_size = 2048
# for salsa20 and chacha20 and chacha20-ietf
@ -37,10 +39,20 @@ BLOCK_SIZE = 64
def load_libsodium():
global loaded, libsodium, buf
libsodium = util.find_library('sodium', 'crypto_stream_salsa20_xor_ic',
'libsodium')
if libsodium is None:
raise Exception('libsodium not found')
if not aead.sodium_loaded:
aead.load_sodium()
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.argtypes = (c_void_p, c_char_p,
@ -116,10 +128,26 @@ def load_libsodium():
c_char_p, c_char_p
)
libsodium.sodium_increment.restype = c_void_p
libsodium.sodium_increment.argtypes = (
c_void_p, c_int
)
# aes-256-gcm, same api structure as above
libsodium.crypto_aead_aes256gcm_is_available.restype = 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)
loaded = True
@ -192,11 +220,15 @@ class SodiumAeadCrypto(AeadCryptoBase):
crypto_aead_xchacha20poly1305_ietf_decrypt
else:
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:
raise Exception('Unknown cipher')
def cipher_ctx_init(self):
global libsodium
libsodium.sodium_increment(byref(self._nonce), c_int(self._nlen))
# 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:
raise Exception("Encrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value]
def aead_decrypt(self, data):
@ -238,6 +271,7 @@ class SodiumAeadCrypto(AeadCryptoBase):
if cipher_out_len.value != clen - self._tlen:
raise Exception("Encrypt failed")
self.cipher_ctx_init()
return buf.raw[:cipher_out_len.value]
@ -248,10 +282,13 @@ ciphers = {
'chacha20-poly1305': (32, 32, SodiumAeadCrypto),
'chacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'xchacha20-ietf-poly1305': (32, 32, SodiumAeadCrypto),
'sodium:aes-256-gcm': (32, 32, SodiumAeadCrypto),
}
def test_salsa20():
print("Test salsa20")
cipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('salsa20', b'k' * 32, b'i' * 16, 0)
@ -260,6 +297,7 @@ def test_salsa20():
def test_chacha20():
print("Test chacha20")
cipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20', b'k' * 32, b'i' * 16, 0)
@ -268,6 +306,7 @@ def test_chacha20():
def test_chacha20_ietf():
print("Test chacha20-ietf")
cipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 1)
decipher = SodiumCrypto('chacha20-ietf', b'k' * 32, b'i' * 16, 0)
@ -276,7 +315,7 @@ def test_chacha20_ietf():
def test_chacha20_poly1305():
print("Test chacha20-poly1305")
print("Test chacha20-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-poly1305',
@ -285,9 +324,23 @@ def test_chacha20_poly1305():
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():
print("Test chacha20-ietf-poly1305")
print("Test chacha20-ietf-poly1305 [payload][tag]")
cipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
b'k' * 32, b'i' * 32, 1)
decipher = SodiumAeadCrypto('chacha20-ietf-poly1305',
@ -296,9 +349,52 @@ def test_chacha20_ietf_poly1305():
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__':
test_chacha20()
test_salsa20()
test_chacha20_ietf()
test_chacha20_poly1305()
test_chacha20_poly1305_chunk()
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
plain = urandom(block_size * rounds)
results = []
cipher_results = []
pos = 0
print('test start')
start = time.time()
while pos < len(plain):
l = random.randint(100, 32768)
c = cipher.encrypt(plain[pos:pos + l])
results.append(c)
# print(pos, l)
c = cipher.encrypt_once(plain[pos:pos + l])
cipher_results.append(c)
pos += l
pos = 0
c = b''.join(results)
results = []
while pos < len(c):
l = random.randint(100, 32768)
results.append(decipher.decrypt(c[pos:pos + l]))
# c = b''.join(cipher_results)
plain_results = []
for c in cipher_results:
# l = random.randint(100, 32768)
l = len(c)
plain_results.append(decipher.decrypt_once(c))
pos += l
end = time.time()
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():

View file

@ -362,18 +362,21 @@ 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.
Sodium:
chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305,
sodium:aes-256-gcm,
salsa20, chacha20, chacha20-ietf.
OpenSSL:(* v1.1)
*aes-128-ocb, *aes-192-ocb, *aes-256-ocb,
aes-128-gcm, aes-192-gcm, aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
aes-128-ctr, aes-192-ctr, aes-256-ctr,
camellia-128-cfb, camellia-192-cfb,
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
-a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+
@ -404,18 +407,21 @@ 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.
Sodium:
chacha20-poly1305, chacha20-ietf-poly1305,
*xchacha20-ietf-poly1305,
sodium:aes-256-gcm,
salsa20, chacha20, chacha20-ietf.
OpenSSL:(* v1.1)
*aes-128-ocb, *aes-192-ocb, *aes-256-ocb,
aes-128-gcm, aes-192-gcm, aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
aes-128-ctr, aes-192-ctr, aes-256-ctr,
camellia-128-cfb, camellia-192-cfb,
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
-a ONE_TIME_AUTH one time auth
--fast-open use TCP_FASTOPEN, requires Linux 3.7+