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.

Built by theluckystrike — More at zovo.one