Install
# Debian / Ubuntu
sudo apt install age
# macOS
brew install age
# Arch / Fedora similar packages
# Or via mise (see /tutorials/mise-polyglot-runtime-versions.html)
mise use -g age@latest
age --version
Generate a keypair
age-keygen -o ~/.config/age/key.txt
# Output:
# # public key: age1abc...xyz
# AGE-SECRET-KEY-1...
cat ~/.config/age/key.txt
# The full file contains the private key + the public key in a comment
# chmod 600 it
The public key is what others use to encrypt to you. The private key is what you use to decrypt; protect it.
Encrypt to a public key
# File output
age -r age1abc...xyz -o secret.age plaintext.txt
# Or via stdin/stdout
cat plaintext.txt | age -r age1abc...xyz > secret.age
# Decrypt (with your private key)
age -d -i ~/.config/age/key.txt -o plaintext.txt secret.age
Encrypt with a passphrase
# Encrypt
age -p -o secret.age plaintext.txt
# Prompts for passphrase
# Decrypt
age -d -o plaintext.txt secret.age
Passphrase mode uses scrypt; expensive to brute-force but slower than public-key mode. Use a real passphrase — the encryption is only as strong as the secret.
Encrypt to an SSH key
The killer feature for collaboration: encrypt a file for someone using their existing SSH public key:
# Get a colleague's SSH public key from GitHub
curl https://github.com/octocat.keys
# Pick an ed25519 key (RSA also works but is larger)
echo "ssh-ed25519 AAAAC3... octocat" > octocat.pub
# Encrypt for them
age -R octocat.pub -o file-for-octocat.age confidential.pdf
# They decrypt with their SSH private key
age -d -i ~/.ssh/id_ed25519 -o confidential.pdf file-for-octocat.age
This eliminates the "exchange public keys first" step that PGP traditionally required — GitHub already exposes everyone's SSH keys; use them.
Encrypt to multiple recipients
# Anyone whose key is listed can decrypt
age -r age1aaa...000 -r age1bbb...111 -r age1ccc...222 -o team.age secret.txt
# Or with a recipients file
cat > recipients.txt <<'EOF'
age1aaa...000
ssh-ed25519 AAAA... alice
ssh-ed25519 AAAA... bob
EOF
age -R recipients.txt -o team.age secret.txt
Each recipient can decrypt independently with their own key.
Encrypt a directory: pipe through tar
# Encrypt a whole directory
tar -czf - my-secret-folder | age -r age1abc...xyz > folder.tar.gz.age
# Decrypt
age -d -i ~/.config/age/key.txt folder.tar.gz.age | tar -xzf -
age is a streaming encryption tool; it doesn't know about directories. Combine with tar / zip for tree encryption.
age in CI / scripts: secrets encrypted in Git
The "encrypted secrets in your repo" pattern:
# Encrypt a secrets file for the deploy key
age -r age1deploy...key -o secrets/prod.env.age secrets/prod.env
# Commit the .age file; .gitignore the plaintext
# In CI, decrypt using a deploy private key from a secret
echo "$AGE_KEY" > /tmp/key.txt
age -d -i /tmp/key.txt -o /tmp/prod.env secrets/prod.env.age
source /tmp/prod.env
rm /tmp/key.txt /tmp/prod.env
For more complete in-Git secrets management with editor integration, see SOPS (next section) — it can use age as the encryption backend.
SOPS + age: structured-file secrets
SOPS is Mozilla's secrets-management tool: it encrypts only the values of YAML/JSON/INI/.env files, leaving keys + structure visible. Combined with age, it's the cleanest "secrets in Git" workflow:
sudo apt install sops # or brew install sops
# .sops.yaml
creation_rules:
- path_regex: secrets/.*\.yaml
age: age1abc...xyz,age1def...uvw
# Edit a secrets file (sops opens $EDITOR with decrypted contents)
sops secrets/prod.yaml
# View
sops -d secrets/prod.yaml
# CI consumes via
SOPS_AGE_KEY_FILE=/tmp/key.txt sops -d secrets/prod.yaml
The encrypted YAML in Git looks like:
database:
host: db.prod.example.com
port: 5432
password: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
user: ENC[AES256_GCM,...]
Reviewers see structure + non-secret keys; diff tools show which secrets changed without revealing values.
chezmoi + age
For dotfiles with secrets (see chezmoi tutorial), age is the recommended encryption backend:
# chezmoi config
chezmoi edit-config
# Add:
[age]
identity = "~/.config/age/key.txt"
recipient = "age1abc...xyz"
# Now files with .age suffix are encrypted in the source dir
chezmoi add --encrypt ~/.aws/credentials
age vs PGP / GPG
- PGP/GPG — the elder. Long-lived; massive feature surface; many key types; sub-keys; web of trust; key-server protocols. Crypto is mostly fine but the UX has always been hostile.
- age — modern primitives, one binary, no keyring concept, no sub-keys, no signatures (just encryption). Smaller scope; safer defaults.
For signing artifacts (release tarballs, git commits), use minisign / signify / GPG. For encrypting files to specific recipients, age.
What age doesn't do
- Signing — age is encrypt-only. Use minisign or GPG for signatures.
- Web of trust — no key-server protocol, no signed-by chains. Recipients are trusted via out-of-band key exchange.
- Streaming + random access — age files are encrypted as a single stream; you can't decrypt arbitrary byte ranges.
- Compression — pipe through gzip / zstd before age.
For "I have a secret and want to give it to a specific person, encrypted, with no setup ceremony," age is the right tool. For everything beyond that, layer on SOPS, minisign, or a full PKI like step-ca (see that tutorial).