mirror of
https://github.com/Legrandin/pycryptodome.git
synced 2025-12-08 05:19:46 +00:00
Support for XChaCha20 and XChaCha20-Poly1305
This commit is contained in:
parent
14528bd87a
commit
c09ed08d76
9 changed files with 271 additions and 67 deletions
|
|
@ -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)
|
||||||
+++++++++++++++++++++++
|
+++++++++++++++++++++++
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
123
src/chacha20.c
123
src/chacha20.c
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue