age Encryption

Modern file encryption with age — simple, secure, and composable.

Key Generation

Generate a new age identity (private key) and extract the public key
age-keygen -o ~/.age/identities/personal.key

Output includes the public key as a comment: # public key: age1…​. Extract it:

Extract public key from identity file
age-keygen -y ~/.age/identities/personal.key
Create a recipients file — one public key per line
age-keygen -y ~/.age/identities/personal.key > ~/.age/recipients/self.txt

Encrypt & Decrypt

Encrypt a file for a single recipient
age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p -o secret.age secret.txt
Encrypt using a recipients file — supports multiple recipients
age -e -R ~/.age/recipients/self.txt -o secret.age secret.txt
Decrypt with an identity file
age -d -i ~/.age/identities/personal.key secret.age > secret.txt
Decrypt with multiple identity files (age tries each)
age -d -i ~/.age/identities/personal.key -i ~/.age/identities/work.key secret.age

Multiple Recipients

Encrypt for multiple recipients — each can decrypt independently
age -r age1abc...recipient1 -r age1def...recipient2 -o shared.age document.txt
Combine recipients files — team encryption
cat ~/.age/recipients/self.txt ~/.age/recipients/team.txt > /tmp/all-recipients.txt
age -e -R /tmp/all-recipients.txt -o shared.age document.txt

Stdin Pipes — Streaming Encryption

Encrypt from stdin — pipe sensitive output directly
gopass show infra/vault-token | age -e -R ~/.age/recipients/self.txt -o token.age
Decrypt to stdout — pipe into consumer without touching disk
age -d -i ~/.age/identities/personal.key token.age | vault login -
Encrypt a tarball on the fly — never write unencrypted archive
tar czf - ~/sensitive-dir/ | age -e -R ~/.age/recipients/self.txt -o backup.tar.gz.age
Decrypt and extract in one pipeline
age -d -i ~/.age/identities/personal.key backup.tar.gz.age | tar xzf - -C /tmp/restore/

SSH Config Encryption Pattern

The dotfiles workflow: plaintext is gitignored, only .age is tracked.

Re-encrypt after editing SSH config
age -e -R ~/.age/recipients/self.txt -o ssh/.ssh/config.age ssh/.ssh/config
Decrypt on a new machine
age -d -i ~/.age/identities/personal.key ssh/.ssh/config.age > ssh/.ssh/config
chmod 600 ssh/.ssh/config

Script Integration

Conditional decrypt — only if identity exists
if [[ -f ~/.age/identities/personal.key ]]; then
    age -d -i ~/.age/identities/personal.key secrets.age > /tmp/secrets.env
    source /tmp/secrets.env
    rm /tmp/secrets.env
fi
Batch encrypt all files in a directory
for f in ~/secrets/*.txt; do
    age -e -R ~/.age/recipients/self.txt -o "${f%.txt}.age" "$f"
done
Verify an encrypted file can be decrypted (test without writing output)
age -d -i ~/.age/identities/personal.key secret.age > /dev/null && echo "OK" || echo "FAIL"

Passphrase Encryption (No Keys)

Encrypt with a passphrase — interactive prompt
age -p -o secret.age secret.txt
Decrypt passphrase-encrypted file
age -d secret.age > secret.txt
Passphrase mode uses scrypt. Slower than public key mode by design. Use key-based encryption for automation; passphrase mode for one-off human-accessible secrets.

Directory Layout Convention

~/.age/
├── identities/         # Private keys — NEVER commit, chmod 600
│   ├── personal.key
│   └── work.key
└── recipients/         # Public keys — safe to commit, share
    ├── self.txt
    └── team.txt