ChaCha20-Poly1305 is an authenticated cipher with associated data (AEAD). It works with a 32 bytes 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 billions 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 :doc:`modern modes of operation of block ciphers <modern>`.
You create a new cipher by calling :meth:`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")
.. automodule:: Crypto.Cipher.ChaCha20_Poly1305 :members: