The first post made an encrypted file called secret.txt.age. Its header is readable text. The header gives age the information necessary to start decryption.
The payload starts after the header. It contains a random value and the encrypted file contents. The header and the payload have different jobs.
Read the header
In the example directory, show the first four lines:
head -n 4 secret.txt.age
The output from the initial example is:
age-encryption.org/v1
-> scrypt pzJgb0zs5vH+TDRw2LwY6w 18
uGTID9B5wTysoKAj16JdCCNB82QMOVTCS7e0OFjgN5s
--- q0qn6iAS9XKtkTt94HV6beIv5DIVMOqDCUYKuBtY7bA
Your random values will be different. The four line layout applies to this passphrase example. Other recipient types can make longer headers.
| Line | Name | Meaning |
|---|---|---|
| 1 | Version line | The file uses age format version 1 |
| 2 | Recipient type and parameters | age uses scrypt with the displayed salt and work factor |
| 3 | Stanza body | An encrypted file key and its authentication tag |
| 4 | Header authentication code | A value that age uses to detect changes to the header |
A recipient is a destination for encryption. A recipient stanza is a group of header lines that shows one method for decryption of the file key. Lines 2 and 3 form the scrypt stanza in this example.
The salt and work factor
The first stanza line contains the name scrypt, a salt, and the number 18.
A salt is a random value that age uses with the passphrase to derive a key. It does not have to be secret. age stores the salt so that decryption can repeat the same calculation.
The salt contains 16 bytes. The header displays those bytes as base64 text. Base64 is a way to represent bytes with readable characters. Base64 does not encrypt those bytes.
The number 18 is the work factor stored in the header. It sets the scrypt cost parameter to 2 raised to the power 18, or 262,144. The scrypt post shows how this value changes time and memory use.
The keys have different jobs
The passphrase does not directly encrypt the file contents. age uses several keys in this process:
| Value | Size | Job |
|---|---|---|
| Passphrase | Variable | The secret that the user supplies |
| Wrapping key | 32 bytes | Encrypts and decrypts the file key |
| File key | 16 bytes | Random secret from which age derives other keys |
| Payload key | 32 bytes | Encrypts and decrypts the file contents |
In passphrase mode, scrypt derives the wrapping key from the passphrase and salt. age also generates a new random file key for each file. It encrypts that file key with the wrapping key.
The stanza body contains 16 bytes of encrypted file key and a 16 byte authentication tag. Thus, the decoded stanza body has 32 bytes. It is not a separate random key of 32 bytes.
An authentication tag lets age detect an incorrect key or a change to the encrypted value. With an incorrect passphrase, the tag check fails when age tries to decrypt the file key.
After age gets the file key, it derives a separate payload key. The payload post shows that step.
Why age encrypts the file key separately
Separate keys let age encrypt the same file key for multiple public key recipients. Each recipient gets a stanza. Any recipient with a matching secret key can decrypt the file key.
The payload still contains only one encrypted copy of the initial data. The key pair post gives an example with two recipients.
Passphrase mode has a different restriction. An age file with a scrypt stanza must contain only that stanza. It cannot also contain public key recipients.
The header authentication code
The last header line starts with ---. The remaining text represents a message authentication code, or MAC. This MAC lets age detect a header change by someone who does not know the file key.
age derives a header authentication key from the file key. It uses that key with the HMAC-SHA-256 algorithm. The calculation covers the header through the --- marker, without the space after the marker.
During decryption, age calculates the MAC again and compares the result with the stored value. If the values differ, age rejects the file. Some changes, such as an invalid salt, can cause an earlier error.
A person who knows the file key can calculate a new MAC. Thus, the MAC does not prove who sent the file. The age format specification defines the header and its authentication calculation.
Text armor and random values
The -a option makes an armored file. Armor is a base64 text representation of the complete encrypted file. It helps when a system accepts text but cannot accept binary data.
To make an armored example, run:
age -p -a -o secret.txt.age.txt secret.txt
Enter your example passphrase at the prompts. The first line of the output file is:
-----BEGIN AGE ENCRYPTED FILE-----
The armor adds no encryption. After base64 decoding, the file has the same age format as a binary age file.
This command performs a new encryption. age generates a new salt, file key, and payload nonce. A nonce is a value used once in an encryption context.
Even with the same input and passphrase, a new encryption makes different bytes with overwhelming probability. The salt prevents useful reuse of a precomputed passphrase table across files with different salts.
The complete layout
graph TD
A["Version line"] --> B["scrypt, salt, and work factor"]
B --> C["Encrypted file key and its tag"]
C --> D["Header MAC"]
D --> E["Payload nonce: 16 bytes"]
E --> F["Encrypted content and chunk tags"]
The first four boxes belong to the header. The last two belong to the payload. The next post shows how scrypt uses the header parameters.

Loading comments...