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
isNone
, the plaintext is returned asbytes
. 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
isNone
, the ciphertext is returned asbytes
. 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: