Support for XChaCha20 and XChaCha20-Poly1305

This commit is contained in:
Helder Eijs 2019-06-04 15:30:13 +02:00
parent 14528bd87a
commit c09ed08d76
9 changed files with 271 additions and 67 deletions

View file

@ -8,6 +8,7 @@ New features
------------ ------------
* Add support for loading PEM files encrypted with AES256-CBC. * Add support for loading PEM files encrypted with AES256-CBC.
* Add support for XChaCha20 and XChaCha20-Poly1305.
3.8.2 (30 May 2019) 3.8.2 (30 May 2019)
+++++++++++++++++++++++ +++++++++++++++++++++++

View file

@ -49,9 +49,9 @@ with respect to the last official version of PyCrypto (2.6.1):
automatic generation of random nonces and IVs, simplified CTR cipher mode, automatic generation of random nonces and IVs, simplified CTR cipher mode,
and more) and more)
* SHA-3 (including SHAKE XOFs), truncated SHA-512 and BLAKE2 hash algorithms * SHA-3 (including SHAKE XOFs), truncated SHA-512 and BLAKE2 hash algorithms
* Salsa20 and ChaCha20 stream ciphers * Salsa20 and ChaCha20/XChaCha20 stream ciphers
* Poly1305 MAC * Poly1305 MAC
* ChaCha20-Poly1305 authenticated cipher * ChaCha20-Poly1305 and XChaCha20-Poly1305 authenticated ciphers
* scrypt and HKDF * scrypt and HKDF
* Deterministic (EC)DSA * Deterministic (EC)DSA
* Password-protected PKCS#8 key containers * Password-protected PKCS#8 key containers

View file

@ -57,9 +57,28 @@ _raw_chacha20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._chacha20",
unsigned long block_high, unsigned long block_high,
unsigned long block_low, unsigned long block_low,
unsigned offset); unsigned offset);
int hchacha20( const uint8_t key[32],
const uint8_t nonce16[16],
uint8_t subkey[32]);
""") """)
def _HChaCha20(key, nonce):
assert(len(key) == 32)
assert(len(nonce) == 16)
subkey = bytearray(32)
result = _raw_chacha20_lib.hchacha20(
c_uint8_ptr(key),
c_uint8_ptr(nonce),
c_uint8_ptr(subkey))
if result:
raise ValueError("Error %d when deriving subkey with HChaCha20" % result)
return subkey
class ChaCha20Cipher(object): class ChaCha20Cipher(object):
"""ChaCha20 cipher object. Do not create it directly. Use :py:func:`new` instead. """ChaCha20 cipher object. Do not create it directly. Use :py:func:`new` instead.
@ -70,13 +89,23 @@ class ChaCha20Cipher(object):
block_size = 1 block_size = 1
def __init__(self, key, nonce): def __init__(self, key, nonce):
"""Initialize a ChaCha20 cipher object """Initialize a ChaCha20/XChaCha20 cipher object
See also `new()` at the module level.""" See also `new()` at the module level."""
# XChaCha20 requires a key derivation with HChaCha20
# See 2.3 in https://tools.ietf.org/html/draft-arciszewski-xchacha-03
if len(nonce) == 24:
key = _HChaCha20(key, nonce[:16])
nonce = b'\x00' * 4 + nonce[16:]
self._name = "XChaCha20"
else:
self._name = "ChaCha20"
self.nonce = _copy_bytes(None, None, nonce) self.nonce = _copy_bytes(None, None, nonce)
self._next = ( self.encrypt, self.decrypt ) self._next = ( self.encrypt, self.decrypt )
self._state = VoidPointer() self._state = VoidPointer()
result = _raw_chacha20_lib.chacha20_init( result = _raw_chacha20_lib.chacha20_init(
self._state.address_of(), self._state.address_of(),
@ -85,7 +114,8 @@ class ChaCha20Cipher(object):
self.nonce, self.nonce,
c_size_t(len(nonce))) c_size_t(len(nonce)))
if result: if result:
raise ValueError("Error %d instantiating a ChaCha20 cipher") raise ValueError("Error %d instantiating a %s cipher" % (result,
self._name))
self._state = SmartPointer(self._state.get(), self._state = SmartPointer(self._state.get(),
_raw_chacha20_lib.chacha20_destroy) _raw_chacha20_lib.chacha20_destroy)
@ -109,15 +139,15 @@ class ChaCha20Cipher(object):
def _encrypt(self, plaintext, output): def _encrypt(self, plaintext, output):
"""Encrypt without FSM checks""" """Encrypt without FSM checks"""
if output is None: if output is None:
ciphertext = create_string_buffer(len(plaintext)) ciphertext = create_string_buffer(len(plaintext))
else: else:
ciphertext = output ciphertext = output
if not is_writeable_buffer(output): if not is_writeable_buffer(output):
raise TypeError("output must be a bytearray or a writeable memoryview") raise TypeError("output must be a bytearray or a writeable memoryview")
if len(plaintext) != len(output): if len(plaintext) != len(output):
raise ValueError("output must have the same length as the input" raise ValueError("output must have the same length as the input"
" (%d bytes)" % len(plaintext)) " (%d bytes)" % len(plaintext))
@ -128,8 +158,8 @@ class ChaCha20Cipher(object):
c_uint8_ptr(ciphertext), c_uint8_ptr(ciphertext),
c_size_t(len(plaintext))) c_size_t(len(plaintext)))
if result: if result:
raise ValueError("Error %d while encrypting with ChaCha20" % result) raise ValueError("Error %d while encrypting with %s" % (result, self._name))
if output is None: if output is None:
return get_raw_buffer(ciphertext) return get_raw_buffer(ciphertext)
else: else:
@ -137,7 +167,7 @@ class ChaCha20Cipher(object):
def decrypt(self, ciphertext, output=None): def decrypt(self, ciphertext, output=None):
"""Decrypt a piece of data. """Decrypt a piece of data.
Args: Args:
ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size. ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
Keyword Args: Keyword Args:
@ -176,12 +206,12 @@ class ChaCha20Cipher(object):
offset offset
) )
if result: if result:
raise ValueError("Error %d while seeking with ChaCha20" % result) raise ValueError("Error %d while seeking with %s" % (result, self._name))
def _derive_Poly1305_key_pair(key, nonce): def _derive_Poly1305_key_pair(key, nonce):
"""Derive a tuple (r, s, nonce) for a Poly1305 MAC. """Derive a tuple (r, s, nonce) for a Poly1305 MAC.
If nonce is ``None``, a new 12-byte nonce is generated. If nonce is ``None``, a new 12-byte nonce is generated.
""" """
@ -209,14 +239,16 @@ def _derive_Poly1305_key_pair(key, nonce):
def new(**kwargs): def new(**kwargs):
"""Create a new ChaCha20 cipher """Create a new ChaCha20 or XChaCha20 cipher
Keyword Args: Keyword Args:
key (bytes/bytearray/memoryview): The secret key to use. key (bytes/bytearray/memoryview): The secret key to use.
It must be 32 bytes long. It must be 32 bytes long.
nonce (bytes/bytearray/memoryview): A mandatory value that nonce (bytes/bytearray/memoryview): A mandatory value that
must never be reused for any other encryption must never be reused for any other encryption
done with this key. It must be 8 or 12 bytes long. done with this key.
For ChaCha20, it must be 8 or 12 bytes long.
For XChaCha20, it must be 24 bytes long.
If not provided, 8 bytes will be randomly generated If not provided, 8 bytes will be randomly generated
(you can find them back in the ``nonce`` attribute). (you can find them back in the ``nonce`` attribute).
@ -234,9 +266,10 @@ def new(**kwargs):
nonce = get_random_bytes(8) nonce = get_random_bytes(8)
if len(key) != 32: if len(key) != 32:
raise ValueError("ChaCha20 key must be 32 bytes long") raise ValueError("ChaCha20/XChaCha20 key must be 32 bytes long")
if len(nonce) not in (8, 12):
raise ValueError("ChaCha20 nonce must be 8 or 12 bytes long") if len(nonce) not in (8, 12, 24):
raise ValueError("Nonce must be 8/12 bytes(ChaCha20) or 24 bytes (XChaCha20)")
if kwargs: if kwargs:
raise TypeError("Unknown parameters: " + str(kwargs)) raise TypeError("Unknown parameters: " + str(kwargs))

View file

@ -2,6 +2,8 @@ from typing import Union, overload
Buffer = Union[bytes, bytearray, memoryview] Buffer = Union[bytes, bytearray, memoryview]
def _HChaCha20(key: Buffer, nonce: Buffer) -> bytearray: ...
class ChaCha20Cipher: class ChaCha20Cipher:
block_size: int block_size: int
nonce: bytes nonce: bytes

View file

@ -31,6 +31,7 @@
from binascii import unhexlify from binascii import unhexlify
from Crypto.Cipher import ChaCha20 from Crypto.Cipher import ChaCha20
from Crypto.Cipher.ChaCha20 import _HChaCha20
from Crypto.Hash import Poly1305, BLAKE2s from Crypto.Hash import Poly1305, BLAKE2s
from Crypto.Random import get_random_bytes from Crypto.Random import get_random_bytes
@ -68,7 +69,7 @@ class ChaCha20Poly1305Cipher(object):
self.verify) self.verify)
self._authenticator = Poly1305.new(key=key, nonce=nonce, cipher=ChaCha20) self._authenticator = Poly1305.new(key=key, nonce=nonce, cipher=ChaCha20)
self._cipher = ChaCha20.new(key=key, nonce=nonce) self._cipher = ChaCha20.new(key=key, nonce=nonce)
self._cipher.seek(64) # Block counter starts at 1 self._cipher.seek(64) # Block counter starts at 1
@ -137,7 +138,7 @@ class ChaCha20Poly1305Cipher(object):
def decrypt(self, ciphertext, output=None): def decrypt(self, ciphertext, output=None):
"""Decrypt a piece of data. """Decrypt a piece of data.
Args: Args:
ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size. ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
Keyword Args: Keyword Args:
@ -147,10 +148,10 @@ class ChaCha20Poly1305Cipher(object):
If ``output`` is ``None``, the plaintext is returned as ``bytes``. If ``output`` is ``None``, the plaintext is returned as ``bytes``.
Otherwise, ``None``. Otherwise, ``None``.
""" """
if self.decrypt not in self._next: if self.decrypt not in self._next:
raise TypeError("decrypt() method cannot be called") raise TypeError("decrypt() method cannot be called")
if self._status == _CipherStatus.PROCESSING_AUTH_DATA: if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
self._pad_aad() self._pad_aad()
@ -159,7 +160,7 @@ class ChaCha20Poly1305Cipher(object):
self._len_ct += len(ciphertext) self._len_ct += len(ciphertext)
self._authenticator.update(ciphertext) self._authenticator.update(ciphertext)
return self._cipher.decrypt(ciphertext, output=output) return self._cipher.decrypt(ciphertext, output=output)
def _compute_mac(self): def _compute_mac(self):
"""Finalize the cipher (if not done already) and return the MAC.""" """Finalize the cipher (if not done already) and return the MAC."""
@ -168,32 +169,32 @@ class ChaCha20Poly1305Cipher(object):
return self._mac_tag return self._mac_tag
assert(self._status != _CipherStatus.PROCESSING_DONE) assert(self._status != _CipherStatus.PROCESSING_DONE)
if self._status == _CipherStatus.PROCESSING_AUTH_DATA: if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
self._pad_aad() self._pad_aad()
if self._len_ct & 0x0F: if self._len_ct & 0x0F:
self._authenticator.update(b'\x00' * (16 - (self._len_ct & 0x0F))) self._authenticator.update(b'\x00' * (16 - (self._len_ct & 0x0F)))
self._status = _CipherStatus.PROCESSING_DONE self._status = _CipherStatus.PROCESSING_DONE
self._authenticator.update(long_to_bytes(self._len_aad, 8)[::-1]) self._authenticator.update(long_to_bytes(self._len_aad, 8)[::-1])
self._authenticator.update(long_to_bytes(self._len_ct, 8)[::-1]) self._authenticator.update(long_to_bytes(self._len_ct, 8)[::-1])
self._mac_tag = self._authenticator.digest() self._mac_tag = self._authenticator.digest()
return self._mac_tag return self._mac_tag
def digest(self): def digest(self):
"""Compute the *binary* authentication tag (MAC). """Compute the *binary* authentication tag (MAC).
:Return: the MAC tag, as 16 ``bytes``. :Return: the MAC tag, as 16 ``bytes``.
""" """
if self.digest not in self._next: if self.digest not in self._next:
raise TypeError("digest() method cannot be called") raise TypeError("digest() method cannot be called")
self._next = (self.digest,) self._next = (self.digest,)
return self._compute_mac() return self._compute_mac()
def hexdigest(self): def hexdigest(self):
"""Compute the *printable* authentication tag (MAC). """Compute the *printable* authentication tag (MAC).
@ -280,14 +281,17 @@ class ChaCha20Poly1305Cipher(object):
def new(**kwargs): def new(**kwargs):
"""Create a new ChaCha20-Poly1305 AEAD cipher. """Create a new ChaCha20-Poly1305 or XChaCha20-Poly1305 AEAD cipher.
:keyword key: The secret key to use. It must be 32 bytes long. :keyword key: The secret key to use. It must be 32 bytes long.
:type key: byte string :type key: byte string
:keyword nonce: :keyword nonce:
A value that must never be reused for any other encryption A value that must never be reused for any other encryption
done with this key. It must be 8 or 12 bytes long. done with this key.
For ChaCha20-Poly1305, it must be 8 or 12 bytes long.
For XChaCha20-Poly1305, it must be 24 bytes long.
If not provided, 12 ``bytes`` will be generated randomly If not provided, 12 ``bytes`` will be generated randomly
(you can find them back in the ``nonce`` attribute). (you can find them back in the ``nonce`` attribute).
@ -302,7 +306,7 @@ def new(**kwargs):
raise TypeError("Missing parameter %s" % e) raise TypeError("Missing parameter %s" % e)
self._len_ct += len(plaintext) self._len_ct += len(plaintext)
if len(key) != 32: if len(key) != 32:
raise ValueError("Key must be 32 bytes long") raise ValueError("Key must be 32 bytes long")
@ -310,8 +314,13 @@ def new(**kwargs):
if nonce is None: if nonce is None:
nonce = get_random_bytes(12) nonce = get_random_bytes(12)
if len(nonce) not in (8, 12): if len(nonce) in (8, 12):
raise ValueError("Nonce must be 8 or 12 bytes long") pass
elif len(nonce) == 24:
key = _HChaCha20(key, nonce[:16])
nonce = b'\x00\x00\x00\x00' + nonce[16:]
else:
raise ValueError("Nonce must be 8, 12 or 24 bytes long")
if not is_buffer(nonce): if not is_buffer(nonce):
raise TypeError("nonce must be bytes, bytearray or memoryview") raise TypeError("nonce must be bytes, bytearray or memoryview")

View file

@ -56,7 +56,7 @@ class Poly1305_MAC(object):
def __init__(self, r, s, data): def __init__(self, r, s, data):
if len(r) != 16: if len(r) != 16:
raise ValueError("Paramater r is not 16 bytes long") raise ValueError("Parameter r is not 16 bytes long")
if len(s) != 16: if len(s) != 16:
raise ValueError("Parameter s is not 16 bytes long") raise ValueError("Parameter s is not 16 bytes long")

View file

@ -243,6 +243,66 @@ class ChaCha20Test(unittest.TestCase):
assert(ct == ct_expect) assert(ct == ct_expect)
class XChaCha20Test(unittest.TestCase):
# From https://tools.ietf.org/html/draft-arciszewski-xchacha-03
def test_hchacha20(self):
# Section 2.2.1
from Crypto.Cipher.ChaCha20 import _HChaCha20
key = b"00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"
key = unhexlify(key.replace(b":", b""))
nonce = b"00:00:00:09:00:00:00:4a:00:00:00:00:31:41:59:27"
nonce = unhexlify(nonce.replace(b":", b""))
subkey = _HChaCha20(key, nonce)
expected = b"82413b42 27b27bfe d30e4250 8a877d73 a0f9e4d5 8a74a853 c12ec413 26d3ecdc"
expected = unhexlify(expected.replace(b" ", b""))
self.assertEqual(subkey, expected)
def test_encrypt(self):
# Section A.3.2
pt = b"""
5468652064686f6c65202870726f6e6f756e6365642022646f6c652229206973
20616c736f206b6e6f776e2061732074686520417369617469632077696c6420
646f672c2072656420646f672c20616e642077686973746c696e6720646f672e
2049742069732061626f7574207468652073697a65206f662061204765726d61
6e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061
206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c
757369766520616e6420736b696c6c6564206a756d70657220697320636c6173
736966696564207769746820776f6c7665732c20636f796f7465732c206a6163
6b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d6963
2066616d696c792043616e696461652e"""
pt = unhexlify(pt.replace(b"\n", b"").replace(b" ", b""))
key = unhexlify(b"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
iv = unhexlify(b"404142434445464748494a4b4c4d4e4f5051525354555658")
ct = b"""
7d0a2e6b7f7c65a236542630294e063b7ab9b555a5d5149aa21e4ae1e4fbce87
ecc8e08a8b5e350abe622b2ffa617b202cfad72032a3037e76ffdcdc4376ee05
3a190d7e46ca1de04144850381b9cb29f051915386b8a710b8ac4d027b8b050f
7cba5854e028d564e453b8a968824173fc16488b8970cac828f11ae53cabd201
12f87107df24ee6183d2274fe4c8b1485534ef2c5fbc1ec24bfc3663efaa08bc
047d29d25043532db8391a8a3d776bf4372a6955827ccb0cdd4af403a7ce4c63
d595c75a43e045f0cce1f29c8b93bd65afc5974922f214a40b7c402cdb91ae73
c0b63615cdad0480680f16515a7ace9d39236464328a37743ffc28f4ddb324f4
d0f5bbdc270c65b1749a6efff1fbaa09536175ccd29fb9e6057b307320d31683
8a9c71f70b5b5907a66f7ea49aadc409"""
ct = unhexlify(ct.replace(b"\n", b"").replace(b" ", b""))
cipher = ChaCha20.new(key=key, nonce=iv)
cipher.seek(64) # Counter = 1
ct_test = cipher.encrypt(pt)
self.assertEqual(ct, ct_test)
class ByteArrayTest(unittest.TestCase): class ByteArrayTest(unittest.TestCase):
"""Verify we can encrypt or decrypt bytearrays""" """Verify we can encrypt or decrypt bytearrays"""
@ -439,6 +499,7 @@ class TestOutput(unittest.TestCase):
def get_tests(config={}): def get_tests(config={}):
tests = [] tests = []
tests += list_test_cases(ChaCha20Test) tests += list_test_cases(ChaCha20Test)
tests += list_test_cases(XChaCha20Test)
tests.append(ChaCha20_AGL_NIR()) tests.append(ChaCha20_AGL_NIR())
tests.append(ByteArrayTest()) tests.append(ByteArrayTest())

View file

@ -328,6 +328,44 @@ class ChaCha20Poly1305Tests(unittest.TestCase):
del test_memoryview del test_memoryview
class XChaCha20Poly1305Tests(unittest.TestCase):
def test_encrypt(self):
# From https://tools.ietf.org/html/draft-arciszewski-xchacha-03
# Section A.3.1
pt = b"""
4c616469657320616e642047656e746c656d656e206f662074686520636c6173
73206f66202739393a204966204920636f756c64206f6666657220796f75206f
6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73
637265656e20776f756c642062652069742e"""
pt = unhexlify(pt.replace(b"\n", b"").replace(b" ", b""))
aad = unhexlify(b"50515253c0c1c2c3c4c5c6c7")
key = unhexlify(b"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
iv = unhexlify(b"404142434445464748494a4b4c4d4e4f5051525354555657")
ct = b"""
bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb
731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452
2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9
21f9664c97637da9768812f615c68b13b52e"""
ct = unhexlify(ct.replace(b"\n", b"").replace(b" ", b""))
tag = unhexlify(b"c0875924c1c7987947deafd8780acf49")
cipher = ChaCha20_Poly1305.new(key=key, nonce=iv)
cipher.update(aad)
ct_test, tag_test = cipher.encrypt_and_digest(pt)
self.assertEqual(ct, ct_test)
self.assertEqual(tag, tag_test)
cipher = ChaCha20_Poly1305.new(key=key, nonce=iv)
cipher.update(aad)
cipher.decrypt_and_verify(ct, tag)
class ChaCha20Poly1305FSMTests(unittest.TestCase): class ChaCha20Poly1305FSMTests(unittest.TestCase):
key_256 = get_tag_random("key_256", 32) key_256 = get_tag_random("key_256", 32)
@ -726,6 +764,7 @@ def get_tests(config={}):
tests = [] tests = []
tests += list_test_cases(ChaCha20Poly1305Tests) tests += list_test_cases(ChaCha20Poly1305Tests)
tests += list_test_cases(XChaCha20Poly1305Tests)
tests += list_test_cases(ChaCha20Poly1305FSMTests) tests += list_test_cases(ChaCha20Poly1305FSMTests)
tests += [TestVectorsRFC()] tests += [TestVectorsRFC()]
tests += [TestVectorsWycheproof(wycheproof_warnings)] tests += [TestVectorsWycheproof(wycheproof_warnings)]

View file

@ -73,7 +73,7 @@ EXPORT_SYM int chacha20_init(stream_state **pState,
if (NULL == key || keySize != KEY_SIZE) if (NULL == key || keySize != KEY_SIZE)
return ERR_KEY_SIZE; return ERR_KEY_SIZE;
if (nonceSize != 8 && nonceSize != 12) if (nonceSize != 8 && nonceSize != 12 && nonceSize != 16)
return ERR_NONCE_SIZE; return ERR_NONCE_SIZE;
*pState = hs = (stream_state*) calloc(1, sizeof(stream_state)); *pState = hs = (stream_state*) calloc(1, sizeof(stream_state));
@ -84,20 +84,35 @@ EXPORT_SYM int chacha20_init(stream_state **pState,
hs->h[1] = 0x3320646e; hs->h[1] = 0x3320646e;
hs->h[2] = 0x79622d32; hs->h[2] = 0x79622d32;
hs->h[3] = 0x6b206574; hs->h[3] = 0x6b206574;
/** Move 256-bit/32-byte key into h[4..11] **/ /** Move 256-bit/32-byte key into h[4..11] **/
for (i=0; i<32/4; i++) { for (i=0; i<32/4; i++) {
hs->h[4+i] = LOAD_U32_LITTLE(key + 4*i); hs->h[4+i] = LOAD_U32_LITTLE(key + 4*i);
} }
/** h[12] remains 0 (offset) **/
if (nonceSize == 8) {
/** h[13] remains 0 (offset) **/ switch (nonceSize) {
hs->h[14] = LOAD_U32_LITTLE(nonce + 0); case 8: {
hs->h[15] = LOAD_U32_LITTLE(nonce + 4); /** h[12] remains 0 (offset) **/
} else { /** h[13] remains 0 (offset) **/
hs->h[13] = LOAD_U32_LITTLE(nonce + 0); hs->h[14] = LOAD_U32_LITTLE(nonce + 0);
hs->h[14] = LOAD_U32_LITTLE(nonce + 4); hs->h[15] = LOAD_U32_LITTLE(nonce + 4);
hs->h[15] = LOAD_U32_LITTLE(nonce + 8); break;
}
case 12: {
/** h[12] remains 0 (offset) **/
hs->h[13] = LOAD_U32_LITTLE(nonce + 0);
hs->h[14] = LOAD_U32_LITTLE(nonce + 4);
hs->h[15] = LOAD_U32_LITTLE(nonce + 8);
break;
}
case 16: {
hs->h[12] = LOAD_U32_LITTLE(nonce + 0);
hs->h[13] = LOAD_U32_LITTLE(nonce + 4);
hs->h[14] = LOAD_U32_LITTLE(nonce + 8);
hs->h[15] = LOAD_U32_LITTLE(nonce + 12);
break;
}
} }
hs->nonceSize = nonceSize; hs->nonceSize = nonceSize;
@ -114,12 +129,11 @@ EXPORT_SYM int chacha20_destroy(stream_state *state)
return 0; return 0;
} }
static int chacha20_core(stream_state *state) static int chacha20_core(stream_state *state, uint32_t h[16])
{ {
unsigned i; unsigned i;
uint32_t h[16];
memcpy(h, state->h, sizeof h); memcpy(h, state->h, sizeof state->h);
for (i=0; i<10; i++) { for (i=0; i<10; i++) {
/** Column round **/ /** Column round **/
@ -134,27 +148,36 @@ static int chacha20_core(stream_state *state)
QR(h[3], h[4], h[ 9], h[14]); QR(h[3], h[4], h[ 9], h[14]);
} }
for (i=0; i<16; i++)
h[i] += state->h[i];
for (i=0; i<16; i++) { for (i=0; i<16; i++) {
STORE_U32_LITTLE(state->keyStream + 4*i, h[i]); uint32_t sum;
sum = h[i] + state->h[i];
STORE_U32_LITTLE(state->keyStream + 4*i, sum);
} }
state->usedKeyStream = 0; state->usedKeyStream = 0;
if (state->nonceSize == 8) { switch (state->nonceSize) {
/** Nonce is 64 bits, counter is two words **/ case 8: {
if (++state->h[12] == 0) { /** Nonce is 64 bits, counter is two words **/
if (++state->h[13] == 0) { if (++state->h[12] == 0) {
return ERR_MAX_DATA; if (++state->h[13] == 0) {
return ERR_MAX_DATA;
}
}
break;
}
case 12: {
/** Nonce is 96 bits, counter is one word **/
if (++state->h[12] == 0) {
return ERR_MAX_DATA;
}
break;
}
case 16: {
/** Nonce is 128 bits, there is no counter (HChaCha20) **/
break;
} }
}
} else {
/** Nonce is 96 bits, counter is one word **/
if (++state->h[12] == 0) {
return ERR_MAX_DATA;
}
} }
return 0; return 0;
@ -171,11 +194,12 @@ EXPORT_SYM int chacha20_encrypt(stream_state *state,
while (len>0) { while (len>0) {
unsigned keyStreamToUse; unsigned keyStreamToUse;
unsigned i; unsigned i;
uint32_t h[16];
if (state->usedKeyStream == sizeof state->keyStream) { if (state->usedKeyStream == sizeof state->keyStream) {
int result; int result;
result = chacha20_core(state); result = chacha20_core(state, h);
if (result) if (result)
return result; return result;
} }
@ -197,6 +221,7 @@ EXPORT_SYM int chacha20_seek(stream_state *state,
unsigned offset) unsigned offset)
{ {
int result; int result;
uint32_t h[16];
if (NULL == state) if (NULL == state)
return ERR_NULL; return ERR_NULL;
@ -216,14 +241,48 @@ EXPORT_SYM int chacha20_seek(stream_state *state,
state->h[12] = (uint32_t)block_low; state->h[12] = (uint32_t)block_low;
} }
result = chacha20_core(state); result = chacha20_core(state, h);
if (result) if (result)
return result; return result;
state->usedKeyStream = offset; state->usedKeyStream = offset;
return 0; return 0;
} }
/*
* Based on https://tools.ietf.org/html/draft-arciszewski-xchacha-03
*/
EXPORT_SYM int hchacha20(const uint8_t key[KEY_SIZE],
const uint8_t nonce16[16], /* First 16 bytes of the 24 byte nonce */
uint8_t subkey[KEY_SIZE])
{
stream_state *pState;
uint32_t h[16];
if (NULL == key || NULL == nonce16 || NULL == subkey) {
return ERR_NULL;
}
chacha20_init(&pState, key, KEY_SIZE, nonce16, 16);
if (NULL == pState)
return ERR_MEMORY;
chacha20_core(pState, h);
/* We only keep first and last row from the new state */
STORE_U32_LITTLE(subkey + 0, h[0]);
STORE_U32_LITTLE(subkey + 4, h[1]);
STORE_U32_LITTLE(subkey + 8, h[2]);
STORE_U32_LITTLE(subkey + 12, h[3]);
STORE_U32_LITTLE(subkey + 16, h[12]);
STORE_U32_LITTLE(subkey + 20, h[13]);
STORE_U32_LITTLE(subkey + 24, h[14]);
STORE_U32_LITTLE(subkey + 28, h[15]);
chacha20_destroy(pState);
return 0;
}
#ifdef PROFILE #ifdef PROFILE
int main(void) int main(void)
{ {
@ -242,7 +301,7 @@ int main(void)
for (int i=0; i<1024; i++) for (int i=0; i<1024; i++)
chacha20_encrypt(state, data, data, data_size); chacha20_encrypt(state, data, data, data_size);
chacha20_destroy(state); chacha20_destroy(state);
free(data); free(data);
} }