ChaCha20 and XChaCha20

ChaCha20 is a stream cipher designed by Daniel J. Bernstein. The secret key is 256 bits long (32 bytes). The cipher requires a nonce, which must not be reused across encryptions performed with the same key.

There are three variants, defined by the length of the nonce:

Nonce length

Description

Max data

If random nonce and same key

8 bytes (default)

The original ChaCha20 designed by Bernstein.

No limitations

Max 200 000 messages

12 bytes

The TLS ChaCha20 as defined in RFC7539.

256 GB

Max 13 billions messages

24 bytes

XChaCha20, still in draft stage.

256 GB

No limitations

This is an example of how ChaCha20 (Bernstein’s version) can encrypt data:

>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import ChaCha20
>>> from Crypto.Random import get_random_bytes
>>>
>>> plaintext = b'Attack at dawn'
>>> key = get_random_bytes(32)
>>> cipher = ChaCha20.new(key=key)
>>> ciphertext = cipher.encrypt(plaintext)
>>>
>>> nonce = b64encode(cipher.nonce).decode('utf-8')
>>> ct = b64encode(ciphertext).decode('utf-8')
>>> result = json.dumps({'nonce':nonce, 'ciphertext':ct})
>>> print(result)
{"nonce": "IZScZh28fDo=", "ciphertext": "ZatgU1f30WDHriaN8ts="}

And this is how you decrypt it:

>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import ChaCha20
>>>
>>> # We assume that the key was somehow securely shared
>>> try:
>>>     b64 = json.loads(json_input)
>>>     nonce = b64decode(b64['nonce'])
>>>     ciphertext = b64decode(b64['ciphertext'])
>>>     cipher = ChaCha20.new(key=key, nonce=nonce)
>>>     plaintext = cipher.decrypt(ciphertext)
>>>     print("The message was " + plaintext)
>>> except (ValueError, KeyError):
>>>     print("Incorrect decryption")

In order to have a RFC7539-compliant ChaCha20 cipher, you need to explicitly generate and pass a 96 bit (12 byte) nonce parameter to new():

nonce_rfc7539 = get_random_bytes(12)
cipher = ChaCha20.new(key=key, nonce=nonce_rfc7539)

Warning

ChaCha20 does not guarantee authenticity of the data you decrypt! In other words, an attacker may manipulate the data in transit. In order to prevent that, you must also use a Message Authentication Code (such as HMAC) to authenticate the ciphertext (encrypt-then-mac). Alternatively, you can use ChaCha20_Poly1305.

class Crypto.Cipher.ChaCha20.ChaCha20Cipher(key, nonce)

ChaCha20 (or XChaCha20) cipher object. Do not create it directly. Use new() instead.

Variables:

nonce (bytes) – The nonce with length 8, 12 or 24 bytes

decrypt(ciphertext, output=None)

Decrypt a piece of data.

Parameters:

ciphertext (bytes/bytearray/memoryview) – The data to decrypt, of any size.

Keyword Arguments:

output (bytes/bytearray/memoryview) – The location where the plaintext is written to. If None, the plaintext is returned.

Returns:

If output is None, the plaintext is returned as bytes. Otherwise, None.

encrypt(plaintext, output=None)

Encrypt a piece of data.

Parameters:

plaintext (bytes/bytearray/memoryview) – The data to encrypt, of any size.

Keyword Arguments:

output (bytes/bytearray/memoryview) – The location where the ciphertext is written to. If None, the ciphertext is returned.

Returns:

If output is None, the ciphertext is returned as bytes. Otherwise, None.

seek(position)

Seek to a certain position in the key stream.

Parameters:

position (integer) – The absolute position within the key stream, in bytes.

Crypto.Cipher.ChaCha20.new(**kwargs)

Create a new ChaCha20 or XChaCha20 cipher

Keyword Arguments:
  • 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.

    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).

Return:

a Crypto.Cipher.ChaCha20.ChaCha20Cipher object