Install age with brew install age (macOS) or go install filippo.io/age@latest, generate a key pair with age-keygen, then encrypt any file with age -r <public-key> -o output.age input.txt. Age is a modern, minimal alternative to PGP that handles file encryption with far less complexity – no key servers, no web of trust, no configuration files. This tutorial covers command-line usage, passphrase-based encryption, Go library integration, SSH key interoperability, and CI/CD pipeline automation.
Installing Age
Age supports multiple platforms including macOS, Linux, and Windows. Install it using Homebrew on macOS:
brew install age
On Linux, download the appropriate binary from the GitHub releases page:
wget https://github.com/FiloSottile/age/releases/download/v1.2.1/age-v1.2.1-linux-amd64.tar.gz
tar -xzf age-v1.2.1-linux-amd64.tar.gz
sudo mv age/age* /usr/local/bin/
For Go developers, install directly via go install:
go install filippo.io/age@latest
Verify the installation:
age --version
age v1.2.1
Understanding Age’s Key Pairs
Age uses two types of keys: identity keys (private keys) and recipient keys (public keys). Generate a new identity key pair:
# Generate identity (private key)
age-keygen
# Output example:
# # created: 2026-03-15T12:00:00Z
# # public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrjwfkg5vwveyq6mk9l
# AGE-SECRET-KEY-1XK7Q6Z9J8V4Y2W5N3M1L6P0R8T3U9V7W4X1Y2Z5A3B6C9D0E2F4G7H8J
Save this output securely. The public key (age1ql3z...) allows others to encrypt files for you without accessing your private key.
Encrypting Files
Encrypt a file for yourself using your public key:
age -p -o encrypted.tar.gz.age secret.tar.gz
The -p flag prompts for a passphrase to add an additional layer of encryption (symmetric encryption layered on top of recipient encryption). The -o flag specifies the output file.
Alternatively, encrypt directly with a recipient’s public key:
age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrjwfkg5vwveyq6mk9l \
-o document.pdf.age document.pdf
For multiple recipients, specify each public key with a separate -r flag:
age -r age1recipient1... -r age1recipient2... -o shared.tar.gz.age secret.tar.gz
Decrypting Files
Decrypt a file using your identity key:
age -d -o decrypted.tar.gz encrypted.tar.gz.age
If the file was encrypted with a passphrase only (using -p without -r):
age -d encrypted.tar.gz.age
This prompts for the passphrase. For automated workflows, pipe the passphrase:
echo "your-passphrase" | age -d -i ~/age-key.txt encrypted.tar.gz.age -o decrypted.tar.gz
Using Passphrases Only
For quick symmetric encryption without key pairs, use passphrase-based encryption:
age -p secret.txt > secret.txt.age
This creates a file encrypted with a passphrase. Decrypt using the same approach:
age -d secret.txt.age > restored_secret.txt
Programmatic Integration
Integrate age into your Go applications using the age package:
package main
import (
"bytes"
"fmt"
"filippo.io/age"
"filippo.io/age/armor"
"io/ioutil"
)
func main() {
// Generate a new identity
identity, err := age.GenerateX25519Identity()
if err != nil {
panic(err)
}
fmt.Println("Public Key:", identity.PublicKey().String())
// Encrypt data
plaintext := []byte("Sensitive data to encrypt")
// Create recipient from public key
recipient, err := age.ParseX25519Recipient(identity.PublicKey().String())
if err != nil {
panic(err)
}
var buf bytes.Buffer
w, err := age.Encrypt(&buf, recipient)
if err != nil {
panic(err)
}
if _, err := w.Write(plaintext); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
// Decrypt data
r, err := age.Decrypt(bytes.NewReader(buf.Bytes()), identity)
if err != nil {
panic(err)
}
decrypted, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println("Decrypted:", string(decrypted))
}
Install the package:
go get filippo.io/age
Integration with SSH Keys
Age can derive recipient keys from existing SSH keys, making migration easier:
# Generate age key from SSH key
ssh-to-age < ~/.ssh/id_rsa.pub
Use the resulting age public key for encryption. This approach uses your existing SSH infrastructure.
Shell Script Automation
Create a reusable encryption script:
#!/bin/bash
# encrypt.sh
RECIPIENT="$1"
INPUT_FILE="$2"
OUTPUT_FILE="${INPUT_FILE}.age"
if [ -z "$RECIPIENT" ] || [ -z "$INPUT_FILE" ]; then
echo "Usage: $0 <recipient-public-key> <file-to-encrypt>"
exit 1
fi
age -r "$RECIPIENT" -o "$OUTPUT_FILE" "$INPUT_FILE"
echo "Encrypted: $OUTPUT_FILE"
Create a corresponding decryption script:
#!/bin/bash
# decrypt.sh
INPUT_FILE="$1"
OUTPUT_FILE="${INPUT_FILE%.age}"
if [ -z "$INPUT_FILE" ]; then
echo "Usage: $0 <file-to-decrypt>"
exit 1
}
age -d -o "$OUTPUT_FILE" "$INPUT_FILE"
echo "Decrypted: $OUTPUT_FILE"
Make them executable and use them:
chmod +x encrypt.sh decrypt.sh
./encrypt.sh age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrjwfkg5vwveyq6mk9l backup.tar.gz
./decrypt.sh backup.tar.gz.age
CI/CD Pipeline Integration
Use age in automated deployment pipelines. Generate keys specifically for CI environments:
# In your CI pipeline
age-keygen > /tmp/ci-age-key.txt
# Encrypt secrets for the pipeline
age -r "$(grep 'public key:' /tmp/ci-age-key.txt | awk '{print $3}')" \
-o secrets.age .env
# Decrypt during build
age -d -i /tmp/ci-age-key.txt -o .env secrets.age
Store the CI identity key as a secret in your CI platform’s secrets management.
Security Considerations
When using age in production environments, follow these practices:
Store identity keys separately from encrypted data. Use hardware security modules or secure key management services for production secrets.
Periodically rotate keys and re-encrypt sensitive data. Age makes this straightforward—decrypt with the old key and re-encrypt with a new one.
When using -p, choose passphrases that meet modern complexity requirements. Combine with public-key encryption for defense in depth.
Log encryption and decryption operations in sensitive environments. Consider adding metadata to encrypted files for organizational purposes.
Troubleshooting
Common issues and solutions:
If you get a permission denied error, ensure your identity key file has restrictive permissions:
chmod 600 ~/.age/identity
If you see an invalid key format error, verify the public key format—age public keys start with age1. Check for copy errors when sharing keys.
A passphrase mismatch occurs when decrypting files encrypted with -p using the wrong passphrase. There is no recovery mechanism for lost passphrases.
Age encrypts files in streaming fashion, handling large files efficiently. For very large files, consider splitting them first with split before encryption.
Conclusion
Age provides a clean, modern alternative to PGP for developers who value simplicity and transparency. Its small attack surface, clear design, and Go-based implementation make it suitable for security-conscious projects. Start with basic command-line usage, then integrate it into your applications using the Go library or shell scripts for automation.
Related Reading
- Signal Disappearing Messages Best Practices: Security.
- Best Hardware Security Key for Developers: A Practical Guide
- Bitwarden Vault Export Backup Guide: Complete Technical.
Built by theluckystrike — More at zovo.one