Back to the journalNOTES BY FAJAR
Cryptography5 min read

[Crypto: age] ChaCha20-Poly1305, Sealing the Body

Learn how age encrypts file contents, what an authentication tag checks, and what happens when one byte changes.

PART 4 OF 6Crypto: age
  1. 01[Crypto: age] From Password to Ciphertext
  2. 02[Crypto: age] Reading the Header
  3. 03[Crypto: age] scrypt, From Password to Key
  4. 04[Crypto: age] ChaCha20-Poly1305, Sealing the BodyYou are here
  5. 05[Crypto: age] Passphrase or Key Pair
  6. 06[Crypto: age] How Long Does a Passphrase Attack Take?
In this article 6 sections

age uses ChaCha20-Poly1305 to encrypt data and detect changes to that data. This algorithm has two jobs in the passphrase example. It encrypts the file key in the header. It also encrypts the file contents in the payload.

The scrypt post introduced the wrapping key. That key decrypts the file key. age then derives a separate payload key for the file contents.

Encryption and authentication

A cipher is an algorithm for encryption and decryption. ChaCha20-Poly1305 combines a cipher with an authentication algorithm:

PartJob
ChaCha20Converts plaintext into ciphertext with a key and a nonce
Poly1305Makes authentication data for the encrypted message

Plaintext is the initial readable data. Ciphertext is the encrypted data. A nonce is a value used once with a specified key.

The combined algorithm makes ciphertext and a 16 byte authentication tag. During decryption, the tag lets age detect an incorrect key or a change to the encrypted message.

The full technical name is authenticated encryption with associated data, or AEAD. Associated data is information that the algorithm authenticates without encrypting it. The two age operations described here use empty associated data.

Authentication detects a change. It does not prevent someone from changing or deleting a file. It also does not, by itself, prove the identity of the sender.

First job: encrypt the file key

The file key is a random secret of 16 bytes. In passphrase mode, age encrypts it with the wrapping key from scrypt. This operation makes 16 bytes of ciphertext and a 16 byte tag.

Those 32 bytes form the stanza body in the header. The header post shows their base64 representation.

The nonce for this operation is twelve zero bytes. This fixed nonce depends on a new wrapping key for each encryption. In passphrase mode, the random salt supplies this separation with overwhelming probability.

An incorrect passphrase makes an incorrect wrapping key. The authentication check fails at this first operation. age reports incorrect passphrase before it decrypts any payload data.

Second job: encrypt the file contents

age derives a payload key from the file key and a new random value of 16 bytes. That random value is the payload nonce. age stores it at the start of the payload.

The derivation uses HKDF-SHA-256. HKDF is a key derivation function that makes a separate key for a specified use. Here, the label payload identifies that use.

The formula below is pseudocode:

text
payload key = HKDF-SHA-256(
    input key material = file key,
    salt = payload nonce,
    info = "payload",
    output length = 32 bytes
)

age divides the plaintext into chunks. A chunk is one section of the data. Each full chunk contains 64 KiB, or 65,536 bytes, before encryption. The last chunk can be smaller.

age encrypts each chunk separately with the payload key. Each chunk gets its own authentication tag and a nonce of 12 bytes.

Chunk nonce fieldSizeMeaning
Chunk counter11 bytesStarts at zero and increases for each chunk
Final flag1 byte0x01 for the last chunk, 0x00 for earlier chunks

The counter uses big endian order, which stores the most significant byte first. The flag identifies the correct end of the data. If the final chunk is missing, complete decryption fails.

The payload nonce and the chunk nonce are different values. The payload nonce helps derive the key. The chunk nonce identifies one encryption operation with that key. The age format specification defines both values.

Account for the 255 bytes

The first post used a plaintext file of 73 bytes. That file fits in one chunk. Its binary age file has these sizes:

PartBytes
Header150
Payload nonce16
Encrypted file contents73
Chunk authentication tag16
Total255

The payload contains 105 bytes: 16 plus 73 plus 16. The ciphertext has the same length as the plaintext. The nonce and tag account for the extra payload bytes.

Change one byte in the example

This procedure uses secret.txt.age from the first post. It makes a separate file called tampered.age. The Python code refuses to replace an existing file.

A bit is one binary digit, either 0 or 1. A byte contains eight bits. The example changes one bit in the last byte.

  1. Make the altered copy:

    bash
    python3 - <<'PYTHON'
    from pathlib import Path
    
    data = bytearray(Path("secret.txt.age").read_bytes())
    # Change one bit in the final authentication tag.
    data[-1] ^= 1
    with open("tampered.age", "xb") as output:
        output.write(data)
    PYTHON
    
  2. Try to decrypt the altered copy:

    bash
    age -d tampered.age
    
  3. Enter the correct passphrase.

The error includes:

text
failed to decrypt and authenticate payload chunk

The passphrase still decrypts the file key. The error occurs later, when age authenticates the payload chunk. In this small example, age returns no plaintext because the only chunk fails authentication.

Decryption can leave an incomplete file

age decrypts large files one chunk at a time. It can write earlier, valid chunks before it finds a damaged chunk later in the file. Thus, a decryption error can leave incomplete output.

After decryption, make sure that the command succeeded before you use the output. If age reports an error, do not use the output as a complete file. A tag authenticates each chunk. Successful decryption of the complete file also authenticates its end.

The size of the cipher key is also not the security level of the complete file. The payload key has 256 bits, but age generates the file key with 128 bits. A weak passphrase can decrease security much more.

The attack time post examines passphrase attempts. The next post shows how public and secret keys replace a shared passphrase.

FILED UNDER

NEXT IN THIS SERIES[Crypto: age] Passphrase or Key Pair

THANKS FOR READING

Did this resonate?

A reaction or a conversation is always welcome.

Loading reactions…

Pass it along

Loading comments...

KEEP EXPLORING

One thought leads to another.

All writing
Back to all writingOne note at a time.