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 XChaCha20 and XChaCha20-Poly1305.
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,
and more)
* 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
* ChaCha20-Poly1305 authenticated cipher
* ChaCha20-Poly1305 and XChaCha20-Poly1305 authenticated ciphers
* scrypt and HKDF
* Deterministic (EC)DSA
* 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_low,
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):
"""ChaCha20 cipher object. Do not create it directly. Use :py:func:`new` instead.
@ -70,13 +89,23 @@ class ChaCha20Cipher(object):
block_size = 1
def __init__(self, key, nonce):
"""Initialize a ChaCha20 cipher object
"""Initialize a ChaCha20/XChaCha20 cipher object
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._next = ( self.encrypt, self.decrypt )
self._state = VoidPointer()
result = _raw_chacha20_lib.chacha20_init(
self._state.address_of(),
@ -85,7 +114,8 @@ class ChaCha20Cipher(object):
self.nonce,
c_size_t(len(nonce)))
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(),
_raw_chacha20_lib.chacha20_destroy)
@ -128,7 +158,7 @@ class ChaCha20Cipher(object):
c_uint8_ptr(ciphertext),
c_size_t(len(plaintext)))
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:
return get_raw_buffer(ciphertext)
@ -176,7 +206,7 @@ class ChaCha20Cipher(object):
offset
)
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):
@ -209,14 +239,16 @@ def _derive_Poly1305_key_pair(key, nonce):
def new(**kwargs):
"""Create a new ChaCha20 cipher
"""Create a new ChaCha20 or XChaCha20 cipher
Keyword Args:
key (bytes/bytearray/memoryview): The secret key to use.
It must be 32 bytes long.
nonce (bytes/bytearray/memoryview): A mandatory 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, 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
(you can find them back in the ``nonce`` attribute).
@ -234,9 +266,10 @@ def new(**kwargs):
nonce = get_random_bytes(8)
if len(key) != 32:
raise ValueError("ChaCha20 key must be 32 bytes long")
if len(nonce) not in (8, 12):
raise ValueError("ChaCha20 nonce must be 8 or 12 bytes long")
raise ValueError("ChaCha20/XChaCha20 key must be 32 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:
raise TypeError("Unknown parameters: " + str(kwargs))

View file

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

View file

@ -31,6 +31,7 @@
from binascii import unhexlify
from Crypto.Cipher import ChaCha20
from Crypto.Cipher.ChaCha20 import _HChaCha20
from Crypto.Hash import Poly1305, BLAKE2s
from Crypto.Random import get_random_bytes
@ -280,14 +281,17 @@ class ChaCha20Poly1305Cipher(object):
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.
:type key: byte string
:keyword nonce:
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
(you can find them back in the ``nonce`` attribute).
@ -310,8 +314,13 @@ def new(**kwargs):
if nonce is None:
nonce = get_random_bytes(12)
if len(nonce) not in (8, 12):
raise ValueError("Nonce must be 8 or 12 bytes long")
if len(nonce) in (8, 12):
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):
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):
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:
raise ValueError("Parameter s is not 16 bytes long")

View file

@ -243,6 +243,66 @@ class ChaCha20Test(unittest.TestCase):
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):
"""Verify we can encrypt or decrypt bytearrays"""
@ -439,6 +499,7 @@ class TestOutput(unittest.TestCase):
def get_tests(config={}):
tests = []
tests += list_test_cases(ChaCha20Test)
tests += list_test_cases(XChaCha20Test)
tests.append(ChaCha20_AGL_NIR())
tests.append(ByteArrayTest())

View file

@ -328,6 +328,44 @@ class ChaCha20Poly1305Tests(unittest.TestCase):
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):
key_256 = get_tag_random("key_256", 32)
@ -726,6 +764,7 @@ def get_tests(config={}):
tests = []
tests += list_test_cases(ChaCha20Poly1305Tests)
tests += list_test_cases(XChaCha20Poly1305Tests)
tests += list_test_cases(ChaCha20Poly1305FSMTests)
tests += [TestVectorsRFC()]
tests += [TestVectorsWycheproof(wycheproof_warnings)]

View file

@ -73,7 +73,7 @@ EXPORT_SYM int chacha20_init(stream_state **pState,
if (NULL == key || keySize != KEY_SIZE)
return ERR_KEY_SIZE;
if (nonceSize != 8 && nonceSize != 12)
if (nonceSize != 8 && nonceSize != 12 && nonceSize != 16)
return ERR_NONCE_SIZE;
*pState = hs = (stream_state*) calloc(1, sizeof(stream_state));
@ -89,15 +89,30 @@ EXPORT_SYM int chacha20_init(stream_state **pState,
for (i=0; i<32/4; i++) {
hs->h[4+i] = LOAD_U32_LITTLE(key + 4*i);
}
switch (nonceSize) {
case 8: {
/** h[12] remains 0 (offset) **/
if (nonceSize == 8) {
/** h[13] remains 0 (offset) **/
hs->h[14] = LOAD_U32_LITTLE(nonce + 0);
hs->h[15] = LOAD_U32_LITTLE(nonce + 4);
} else {
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;
@ -114,12 +129,11 @@ EXPORT_SYM int chacha20_destroy(stream_state *state)
return 0;
}
static int chacha20_core(stream_state *state)
static int chacha20_core(stream_state *state, uint32_t h[16])
{
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++) {
/** Column round **/
@ -134,27 +148,36 @@ static int chacha20_core(stream_state *state)
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++) {
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;
if (state->nonceSize == 8) {
switch (state->nonceSize) {
case 8: {
/** Nonce is 64 bits, counter is two words **/
if (++state->h[12] == 0) {
if (++state->h[13] == 0) {
return ERR_MAX_DATA;
}
}
} else {
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;
}
}
return 0;
@ -171,11 +194,12 @@ EXPORT_SYM int chacha20_encrypt(stream_state *state,
while (len>0) {
unsigned keyStreamToUse;
unsigned i;
uint32_t h[16];
if (state->usedKeyStream == sizeof state->keyStream) {
int result;
result = chacha20_core(state);
result = chacha20_core(state, h);
if (result)
return result;
}
@ -197,6 +221,7 @@ EXPORT_SYM int chacha20_seek(stream_state *state,
unsigned offset)
{
int result;
uint32_t h[16];
if (NULL == state)
return ERR_NULL;
@ -216,14 +241,48 @@ EXPORT_SYM int chacha20_seek(stream_state *state,
state->h[12] = (uint32_t)block_low;
}
result = chacha20_core(state);
result = chacha20_core(state, h);
if (result)
return result;
state->usedKeyStream = offset;
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
int main(void)
{