You have got a file that cannot leak, and age turns it into unreadable noise with one command and a key you control. This walkthrough uses age 1.1.1, the version installed on this machine, and keeps the private identity well away from the encrypted data.
Allow about fifteen minutes. You need a normal shell, the age and age-keygen commands, and enough space for a second copy of the file. No step needs sudo. Everything happens in a temporary working directory, so the original input stays untouched throughout.
Do this before you rely on a flag in a script: confirm which executable runs and which version it is. It is read-only, so there is nothing to undo.
$ command -v age
/usr/bin/age
$ age --version
1.1.1
$ command -v age-keygen
/usr/bin/age-keygen
Checkpoint: the version should be the one you have tested. Upstream documentation moves faster than distro packages: it already covers key formats and features this installed release does not have. Copy an example off the internet and your local age may just reject it, so check the local manual first.
Generate a native X25519 identity file. The private identity goes to the path you name; the public recipient prints separately so you can grab it without opening the secret file.
$ umask 077
$ age-keygen -o "$HOME/age-key.txt"
Public key: age1REPLACE_THIS_WITH_THE_PRINTED_RECIPIENT
Swap the placeholder recipient in every later command for the full value your own run just printed. Never paste a real private identity into a shell history, ticket, article or chat, and back up $HOME/age-key.txt somewhere you trust. Anyone holding that identity can decrypt anything addressed to its recipient, and if you lose it, that access is gone for good.
Check the file without ever displaying its secret contents:
$ stat -c '%A %n' "$HOME/age-key.txt"
-rw------- /home/you/age-key.txt
$ age-keygen -y "$HOME/age-key.txt"
age1REPLACE_THIS_WITH_THE_PRINTED_RECIPIENT
The -y operation derives the public recipient straight from the identity, so compare it against the value you saved earlier. If the file turns out readable by other users, fix its permissions before you trust it with anything:
$ chmod 600 "$HOME/age-key.txt"
Pick a new output name. The -r option names a recipient and -o names the output file. Encryption is the default behaviour anyway, but spelling out -e makes a script easier to audit later:
$ age -e \
-r 'age1REPLACE_THIS_WITH_THE_PRINTED_RECIPIENT' \
-o report.txt.age \
report.txt
$ test -s report.txt.age && echo 'encrypted output exists'
encrypted output exists
Warning: keep the original file until decryption has been tested. If report.txt.age already exists, age just overwrites it, no prompt. Use a fresh name, or move the old output to a clearly named backup before rerunning the command. Binary age output also has no business hitting a terminal screen; redirecting it to a file with -o is what saves you from that mess.
Use the private identity with -i, and pick a different output path so a failed experiment cannot clobber the original:
$ age --decrypt \
--identity "$HOME/age-key.txt" \
--output report.txt.restored \
report.txt.age
$ cmp -- report.txt report.txt.restored
$ echo 'decryption verified'
decryption verified
A zero exit from cmp means the restored bytes match the source, byte for byte. A zero exit from age means it processed the whole input successfully. If decryption fails, read the error and never treat a partial output as trustworthy. The manpage is clear that unauthenticated output is never released, but a partial authenticated output can still land on disk.
Only once you have verified the restored file should you replace a working copy with it:
$ mv -- report.txt.restored report.txt
Recovery: that mv changes state and can replace an existing destination. Keep the original under a different name until the replacement has been checked. There is no age command that recovers a deleted identity or an overwritten plaintext, so this is the point of no return, not a rehearsal.
Each recipient gets an independent way into the same file. Repeat -r for every complete public recipient:
$ age -e \
-r 'age1RECIPIENT_FOR_ALICE' \
-r 'age1RECIPIENT_FOR_BOB' \
-o shared-notes.txt.age \
shared-notes.txt
Handy when Alice and Bob both need to read the file: they never share private identities, each just uses the one matching their own recipient. Recipient values are not secrets, but verify them over a trusted channel before encrypting anything sensitive. Get one character wrong and you have built an archive nobody in the group can open.
For a maintained list, put one recipient per line in a file. Empty lines and lines starting with # get ignored:
$ cat recipients.txt
# Alice
age1RECIPIENT_FOR_ALICE
# Bob
age1RECIPIENT_FOR_BOB
$ age -e -R recipients.txt -o shared-notes.txt.age shared-notes.txt
Review the recipient file before you use it: it decides who can decrypt the result. Lock down its permissions if it carries internal comments or operational details too, even though the public keys inside grant nothing on their own.
Passphrase mode is interactive, and it will not combine with recipient flags:
$ age --passphrase -o personal.txt.age personal.txt
Enter passphrase (leave empty to autogenerate a secure one):
$ age --decrypt -o personal.txt.restored personal.txt.age
Enter passphrase:
In age 1.1.1, an empty first prompt offers you an autogenerated passphrase instead. Save a generated or chosen passphrase in an approved password manager before you close the terminal. A passphrase-protected file is not a backup plan: lose the passphrase and the ciphertext is just noise.
-o OUTPUT or redirect deliberately. For text transport, encryption can use --armor; decryption detects armoring automatically.-o overwrites an existing path. Restore from your backup and choose a new output name for the next test.age, then compare files or verify a checksum. Do not check only that an output path exists.-i, but the manual recommends native age keys when available.Running age as root fixes none of this. A missing identity, a bad recipient or a wrong path stays broken, and now you have also created root-owned output your normal account cannot manage.
age --version reports the version you tested.age-keygen -y.cmp verified the restored bytes.