ChaCha20-Poly1305 is an authenticated cipher with associated data (AEAD). It works with a 32 byte secret key and a nonce which must never be reused across encryptions performed under the same key. The cipher produces a 16 byte tag that the receiver must use to validate the message.
There are three variants of the algorithm, defined by the length of the nonce:
Nonce length | Description | Max plaintext | If random nonce and same key |
---|---|---|---|
8 bytes | Based on Bernstein's original ChaCha20. | No limitations | Max 200 000 messages |
12 bytes (default) | Version used in TLS and specified in RFC7539. | 256 GB | Max 13 billion messages |
24 bytes | XChaCha20-Poly1305, still in draft stage. | 256 GB | No limitations |
The API of the cipher and its finite state machine are the same as for the modern modes of operation of block ciphers <modern>
.
You create a new cipher by calling Crypto.Cipher.ChaCha20_Poly1305.new
.
This is an example of how ChaCha20-Poly1305 (TLS version) can encrypt and authenticate data:
>>> import json
>>> from base64 import b64encode
>>> from Crypto.Cipher import ChaCha20_Poly1305
>>> from Crypto.Random import get_random_bytes
>>>
>>> header = b"header"
>>> plaintext = b'Attack at dawn'
>>> key = get_random_bytes(32)
>>> cipher = ChaCha20_Poly1305.new(key=key)
>>> cipher.update(header)
>>> ciphertext, tag = cipher.encrypt_and_digest(plaintext)
>>>
>>> jk = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = [ b64encode(x).decode('utf-8') for x in (cipher.nonce, header, ciphertext, tag) ]
>>> result = json.dumps(dict(zip(jk, jv)))
>>> print(result)
{"nonce": "4EE/9uqhoZ3mQXmm", "header": "aGVhZGVy", "ciphertext": "Wmmo4Vzn+eS3tUPv2a8=", "tag": "/FgVbM8qhzssPRY80T0iVA=="}
In the example above, a 96 bit (12 byte) nonce is automatically created. It can be accessed as the nonce
member in the cipher
object.
This is how you decrypt the data and check its authenticity:
>>> import json
>>> from base64 import b64decode
>>> from Crypto.Cipher import ChaCha20_Poly1305
>>>
>>> # We assume that the key was securely shared beforehand
>>> try:
>>> b64 = json.loads(json_input)
>>> jk = [ 'nonce', 'header', 'ciphertext', 'tag' ]
>>> jv = {k:b64decode(b64[k]) for k in jk}
>>>
>>> cipher = ChaCha20_Poly1305.new(key=key, nonce=jv['nonce'])
>>> cipher.update(jv['header'])
>>> plaintext = cipher.decrypt_and_verify(jv['ciphertext'], jv['tag'])
>>> print("The message was: " + plaintext)
>>> except (ValueError, KeyError):
>>> print("Incorrect decryption")
Crypto.Cipher.ChaCha20_Poly1305