Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Applied Cryptography for Developers  Module 7 – Symmetric encryption and AEAD

Encrypting files, streams and passphrase data

Authenticated chunks that refuse cut and shuffled files, keys from passphrases with their settings in the header, no early plaintext, and age.

  • Advanced
  • 15 minutes
  • Examples run with Node.js 24.21.0, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free intro, Pro: 4 of 15 minutes free
Free: Files and streams are sealed in chunks that know their place. With a pass: the other 7 sections and 3 interview answers, about 11 minutes.

What you will learn

  • Encrypt large data in authenticated chunks that refuse truncation, reordering and added chunks
  • Derive file keys from passphrases and store the KDF and its parameters in the header
  • Avoid releasing plaintext before the final authentication check, in files and in streams
  • Trace how a reader opens an age file, from its header to its last chunk

Before you start

What you’ll learn · Before you start

What you will learn

  • Encrypt large data in authenticated chunks that refuse truncation, reordering and added chunks
  • Derive file keys from passphrases and store the KDF and its parameters in the header
  • Avoid releasing plaintext before the final authentication check, in files and in streams
  • Trace how a reader opens an age file, from its header to its last chunk

Before you start

On this page

crypto.subtle.encrypt takes a whole message at once, and Web Crypto has no streaming AEAD, so a 2 GB video cannot be one call on a phone. Cut it into 64 KiB chunks and seal each one, and a new problem appears: a file whose chunks are sealed independently still decrypts after its last chunk is dropped, two chunks are swapped or a chunk is added.

Encrypting files safely takes a chunk layout that refuses all three, keys derived from passphrases with their settings kept in the header, and no unverified plaintext: Decrypted bytes handed out before the authentication tag has been checked. They may belong to a changed message, so they must not be written, shown or acted on until the check succeeds. in the output. This lesson builds each of them and then opens real age test files with Node.js’s standard library.

Files and streams are sealed in chunks that know their place

Encrypting files of any size therefore means encrypting them piece by piece, so that neither side holds the whole file in memory. The age format, a C2SP specification, shows the layout: a random file key: A random key made for one file and used for nothing else. It protects the file's contents, and is itself wrapped in the file's header, by a passphrase or by each recipient's key. for this file alone, a 16-byte stream nonce, a payload key from HKDF: The HMAC-based key derivation function of RFC 5869. HKDF-Extract turns input keying material and a salt into a pseudorandom key; HKDF-Expand turns that key and an info label into as many bytes as asked for, up to 255 hash blocks. More in “Deriving keys with HKDF” over both, and chunks of 64 KiB sealed with ChaCha20-Poly1305: The AEAD of RFC 8439: the ChaCha20 stream cipher with the Poly1305 authenticator, a 256-bit key, a 96-bit nonce and a 128-bit tag. It runs fast in plain software, without AES instructions. More in “Authenticated encryption: AES-GCM and ChaCha20”. Each chunk’s 12-byte nonce is an 11-byte chunk counter and a flag that is 1 only for the last chunk. The counter stops reordering; the flag stops a file from being cut short or extended. The age specification calls its payload a variant of STREAM construction: A way to encrypt a long message in chunks with an AEAD in which each chunk's nonce holds its position and a flag for the last chunk, so that a reader detects chunks that are dropped, reordered, repeated or added. The age format uses a variant., a construction from the research literature, and libsodium’s encrypted streams make the same promise with a final tag. Step through a 200,000-byte file:

Interactive · step by step A 200,000-byte file in four authenticated chunks Step through how one file key and one stream nonce give every chunk a key and a nonce that name its place.
A 200,000-byte file in four authenticated chunks, step 6 of 6. Showing File key, Stream nonce, HKDF-SHA-256, Chunk 0, Chunk 1, Chunk 2, Chunk 3, the last, A cut or shuffled file. In focus: A cut or shuffled file.File key16 random bytes, one fileStream nonce16 random bytes, storedHKDF-SHA-256info "payload"Chunk 064 KiB, nonce …00 00Chunk 164 KiB, nonce …01 00Chunk 264 KiB, nonce …02 00Chunk 3, the last3,392 bytes, nonce …03 01A cut or shuffled filefails a tag, so refused

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A key for one file. Every file gets its own random 16-byte file key, used for nothing else. A passphrase or a recipient key protects it in the header.
  2. One payload key. A fresh 16-byte stream nonce, stored at the front of the payload, and HKDF over the file key give the key that seals every chunk.
  3. Chunk 0. The first 64 KiB are sealed with ChaCha20-Poly1305 under a nonce that is the chunk counter, 0, and a flag byte of 0: not the last chunk.
  4. Chunks 1 and 2. Each next chunk raises the counter by one. A chunk moved to another place would be opened with another counter, and its tag would not check.
  5. The last chunk. The last 3,392 bytes are sealed with counter 3 and the flag set to 1. Only a chunk sealed as the last can end the file.
  6. Cut or shuffled. If the last chunk is dropped, chunk 2 must now open as the last, with flag 1, and fails. Swapped and added chunks fail their tags too, under the wrong counter or the wrong flag, so the reader refuses the file.

The program computes the layout for three sizes, with every chunk’s nonce:

Chunks, nonces and overhead for three file sizes Python · layout-py/chunk_layout.py
# How a file is cut into authenticated chunks in the payload layout of the age format: a 16-byte random nonce in front,
# chunks of 64 KiB each sealed with a 16-byte tag, and a 12-byte nonce per chunk made of an 11-byte big-endian chunk
# counter and a last-chunk flag. A 200,000-byte file, an empty one and one of exactly two chunks.
CHUNK = 64 * 1024
TAG = 16
NONCE = 16


def chunk_nonce(counter, last):
    return counter.to_bytes(11, "big") + (b"\x01" if last else b"\x00")


def layout(size):
    """(plaintext bytes, chunk nonce) for every chunk; an empty file is one empty final chunk."""
    count = max(1, -(-size // CHUNK))
    return [(min(CHUNK, size - i * CHUNK), chunk_nonce(i, i == count - 1)) for i in range(count)]


for size in (200_000, 0, 131_072):
    chunks = layout(size)
    total = NONCE + sum(length + TAG for length, _ in chunks)
    print(f"{size:,} bytes: {len(chunks)} chunk{'s' if len(chunks) > 1 else ''}, {total:,} bytes stored, {total - size} of them overhead")
    for length, nonce in chunks:
        print(f"  {length:>6,} bytes   nonce {nonce.hex()}")

Output

200,000 bytes: 4 chunks, 200,080 bytes stored, 80 of them overhead
  65,536 bytes   nonce 000000000000000000000000
  65,536 bytes   nonce 000000000000000000000100
  65,536 bytes   nonce 000000000000000000000200
   3,392 bytes   nonce 000000000000000000000301
0 bytes: 1 chunk, 32 bytes stored, 32 of them overhead
       0 bytes   nonce 000000000000000000000001
131,072 bytes: 2 chunks, 131,120 bytes stored, 48 of them overhead
  65,536 bytes   nonce 000000000000000000000000
  65,536 bytes   nonce 000000000000000000000101

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chunk_layout.py

The 200,000-byte file becomes three full chunks and one of 3,392 bytes, 80 bytes larger in all: the 16-byte stream nonce and a 16-byte tag per chunk. An empty file is still one empty final chunk, so its end is checked too, and a file of exactly two chunks ends with a full final chunk, never an empty extra one.

The same layout serves streams that never sit on a disk, such as an upload or a backup sent over the network, and those streams raise one more question: when to let plaintext out. Four other choices complete a design. Sealing chunks independently with random nonces protects each chunk but not their order or their number. Passphrase data needs a slow key-derivation function, such as scrypt or PBKDF2 at 600,000 iterations, before any key exists. The salt and cost of that function, such as an age file’s scrypt work factor: The cost setting of a password-based key derivation, such as scrypt's log₂ N in the age format, stored with the encrypted data so that each file opens with the cost it was made with., belong in the header of the passphrase data, where a reader finds them. And a streaming reader must not write or show plaintext until its tag, and for the whole file the final chunk, has checked.

Checkpoint

Check: Chunks that carry their position

  1. Question 1 of 2 In the age payload layout, how many chunks does a file of 300,000 bytes take?

    Type a number.

    A hint

    Each chunk holds 65,536 bytes, and only the last may be shorter.

    Show the answer: Question 1 of 2

    Answer: 5 chunks

    Four full chunks hold 262,144 bytes, and the remaining 37,856 bytes make a fifth, the final one, sealed with the last-chunk flag set.

  2. Question 2 of 2 In a chunk nonce of an 11-byte counter and a 1-byte flag, what does the flag do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It marks the last chunk, so that a file cut short or extended does not end with a valid final chunk

    Counter for position, flag for the end: together they make each chunk valid only in its own place.

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Why is sealing each chunk of a file independently not enough?

    Web Crypto has no streaming AEAD, so a large file is cut into 64 KiB chunks. Chunks sealed independently with random nonces are each protected, but not their order or their number: the file still decrypts after its last chunk is dropped, two chunks are swapped or a chunk is added.

  2. How does the age file format keep its chunks in place?

    A random file key for this file alone, a 16-byte stream nonce, a payload key from HKDF over both, and 64 KiB chunks sealed with ChaCha20-Poly1305. Each chunk's 12-byte nonce is an 11-byte chunk counter and a flag that is 1 only for the last chunk. The counter stops reordering; the flag stops a file from being cut short or extended. The payload is a variant of STREAM.

  3. How much larger is a file sealed in age-style chunks, and what about empty files?

    A 200,000-byte file seals into three full 64 KiB chunks and a last one of 3,392 bytes, and grows by 80 bytes: a 16-byte stream nonce plus a 16-byte tag for each of four chunks. An empty file still has one empty final chunk, so that its end is checked, and a file of exactly two chunks ends in a full final chunk, never an extra empty one.

  4. What does passphrase-protected data need before any key exists?

    A slow key-derivation function, such as scrypt or PBKDF2 at 600,000 iterations. Its salt and cost, such as an age file's scrypt work factor, belong in the header of the passphrase data, where a reader finds them.

  5. When may a streaming reader write or show plaintext?

    Not until the chunk's tag has checked, and for the whole file not until the final chunk has checked. A reader that releases plaintext earlier hands out bytes that nobody has verified.

Chunks that refuse truncation, reordering and additions Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Keys from passphrases, with the parameters in the header
  • No plaintext before the final check
  • The age format, opened with Node.js’s standard library
  • Practise: a stream that refuses cut files
    • Chunked encryption that refuses cut and shuffled streams
  • Interview questions
    • Why are large files encrypted in chunks, and what must each chunk's nonce carry?
    • Your backup tool encrypts each 1 MiB block of a file with AES-256-GCM under one key and a random nonce, and stores the blocks in order. Review it: what can go wrong without anyone noticing, and how do you change the format?
    • A desktop app protects each user's vault with a key from PBKDF2-HMAC-SHA-256 at 100,000 iterations, a count hard-coded in the app and stored nowhere. Plan the move to stronger settings without locking anyone out.
    • Your service decrypts encrypted 10 GB downloads on the fly with AES-GCM, piping the decipher's output into the HTTP response. Explain what is wrong and redesign the storage format and the streaming code.
  • Key takeaways

1 diagram · 4 runnable examples (JavaScript) · 1 exercise with sample tests · 3 interview answers · about 30 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

file key
A random key made for one file and used for nothing else. It protects the file's contents, and is itself wrapped in the file's header, by a passphrase or by each recipient's key.
STREAM construction
A way to encrypt a long message in chunks with an AEAD in which each chunk's nonce holds its position and a flag for the last chunk, so that a reader detects chunks that are dropped, reordered, repeated or added. The age format uses a variant.
unverified plaintext
Decrypted bytes handed out before the authentication tag has been checked. They may belong to a changed message, so they must not be written, shown or acted on until the check succeeds.
work factor
The cost setting of a password-based key derivation, such as scrypt's log₂ N in the age format, stored with the encrypted data so that each file opens with the cost it was made with.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.