age vs GPG is the choice between a small, single-purpose encryption tool and a complete OpenPGP implementation. age (format age-encryption.org/v1, specified at C2SP) encrypts a file to one or more recipients, each a short string such as age1…, an SSH public key or a post-quantum age1pq1… key, using X25519 or hybrid ML-KEM key encapsulation and ChaCha20-Poly1305 for the payload. GnuPG implements OpenPGP: encryption, signatures, certification, keyrings, keyservers, smart cards and email integration, with decades of compatibility behind it.
At a glance
| age | GnuPG (OpenPGP) | |
|---|---|---|
| Scope | File encryption only | Encryption, signing, certification, key management |
| Specification | age v1 (C2SP), one format, no versions to negotiate | RFC 9580 (2024) and RFC 9980 (2026); GnuPG follows LibrePGP |
| Public key | 62 characters (age1…); post-quantum 1,959 characters (age1pq1…) | Binary key with user IDs and self-signatures; 473 bytes for Ed25519 plus Cv25519 (measured) |
| Algorithms | X25519, ML-KEM-768 + X25519 hybrid, scrypt for passphrases, ChaCha20-Poly1305, HKDF and HMAC-SHA-256 | Many: RSA, ECC, AES, Camellia, legacy ciphers for old data; ML-KEM-768 + X25519 in GnuPG 2.5 |
| Configuration | None | Preferences, trust models, algorithm and compression settings |
| Measured ciphertext for a 26-byte file | 226 bytes (X25519); 1,685 bytes (post-quantum) | 203 bytes (Cv25519); 1,288 bytes (ML-KEM-768 + X25519 subkey) |
| SSH keys as recipients | Yes (ssh-ed25519, ssh-rsa) | No |
| Hardware keys | Plugins: YubiKey PIV, TPM, Secure Enclave | OpenPGP smart cards and security keys |
| Signatures | None by design | Yes |
| Key discovery | None; recipients are exchanged directly | Keyservers, WKD, keyrings |
| Large files | Streams in 64 KiB authenticated chunks; seekable decryption API since 1.3 | Streams; one AEAD packet with chunking |
Encrypting the same file both ways
The script below encrypts a 26-byte secret to an age X25519 recipient, an age post-quantum recipient, a GnuPG Cv25519 subkey and a GnuPG ML-KEM-768 + X25519 subkey, decrypts each and compares sizes (age 1.3.2, GnuPG 2.5.21). Everything happens in a temporary directory.
#!/bin/sh
dir=$(mktemp -d /tmp/agegpg.XXXXXX) # short path: gpg-agent sockets need one
cd "$dir" || exit 1
export GNUPGHOME="$dir/g"
mkdir -m 700 g
printf 'db_password=correct-horse\n' > secret.env # 26 bytes
echo "== age"
age-keygen -o key.txt 2> /dev/null
recipient=$(age-keygen -y key.txt)
echo "recipient: ${#recipient} characters"
age -r "$recipient" -o secret.env.age secret.env
age -d -i key.txt secret.env.age | cmp -s - secret.env && echo "decrypted: identical"
echo "ciphertext: $(wc -c < secret.env.age | tr -d ' ') bytes"
echo "== age, post-quantum recipient"
age-keygen -pq -o pq.txt 2> /dev/null
pqrecipient=$(age-keygen -y pq.txt)
echo "recipient: ${#pqrecipient} characters"
age -r "$pqrecipient" -o secret.env.pq.age secret.env
age -d -i pq.txt secret.env.pq.age | cmp -s - secret.env && echo "decrypted: identical"
echo "ciphertext: $(wc -c < secret.env.pq.age | tr -d ' ') bytes"
echo "== GnuPG"
gpg --batch --quiet --passphrase '' --quick-gen-key "Ops <ops@example.org>" ed25519 cert 1y
fpr=$(gpg --list-keys --with-colons ops@example.org 2> /dev/null | awk -F: '/^fpr/ { print $10; exit }')
gpg --batch --quiet --passphrase '' --quick-add-key "$fpr" cv25519 encr 1y
gpg --batch --quiet --trust-model always -r ops@example.org -o secret.env.gpg -e secret.env
gpg --batch --quiet -d secret.env.gpg 2> /dev/null | cmp -s - secret.env && echo "decrypted: identical"
echo "ciphertext: $(wc -c < secret.env.gpg | tr -d ' ') bytes"
echo "public key export: $(gpg --export ops@example.org | wc -c | tr -d ' ') bytes binary"
echo "== GnuPG, ML-KEM-768 + X25519 subkey"
gpg --batch --quiet --passphrase '' --quick-gen-key "Ops PQ <ops-pq@example.org>" ed25519 cert 1y
fpr=$(gpg --list-keys --with-colons ops-pq@example.org 2> /dev/null | awk -F: '/^fpr/ { print $10; exit }')
gpg --batch --quiet --passphrase '' --quick-add-key "$fpr" ky768_cv25519 encr 1y
gpg --batch --quiet --trust-model always -r ops-pq@example.org -o secret.env.pq.gpg -e secret.env
gpg --batch --quiet -d secret.env.pq.gpg 2> /dev/null | cmp -s - secret.env && echo "decrypted: identical"
echo "ciphertext: $(wc -c < secret.env.pq.gpg | tr -d ' ') bytes"
gpgconf --kill gpg-agent
cd / && rm -rf "$dir"
== age
recipient: 62 characters
decrypted: identical
ciphertext: 226 bytes
== age, post-quantum recipient
recipient: 1959 characters
decrypted: identical
ciphertext: 1685 bytes
== GnuPG
decrypted: identical
ciphertext: 203 bytes
public key export: 473 bytes binary
== GnuPG, ML-KEM-768 + X25519 subkey
decrypted: identical
ciphertext: 1288 bytes
Both add a fixed header per recipient plus a small authentication overhead, so for real files the difference is negligible. The post-quantum overhead is the ML-KEM-768 ciphertext (1,088 bytes) carried in each recipient stanza; age's is larger mainly because its header is text, with the binary values in base64. The script uses --trust-model always only because the key was created a second earlier; in real use you would verify the recipient's fingerprint.
Interoperability details that matter
gpg --list-packets on the files above shows two things worth knowing. For its Cv25519 key, GnuPG 2.5 wrote an OCB "AEAD encrypted packet", the LibrePGP format, rather than the version 2 encrypted-data packet that RFC 9580 defines, because GnuPG's own keys advertise support for it; implementations that follow only RFC 9580 may not read such files. For the post-quantum subkey, GnuPG used public-key algorithm 8, its LibrePGP Kyber composite, whereas RFC 9980 assigns 35 to ML-KEM-768 + X25519, so GnuPG's post-quantum keys and RFC 9980 keys do not interoperate. age has a single format and a single post-quantum construction (HPKE with the ML-KEM-768 and X25519 hybrid also known as X-Wing), with published test vectors.
Why age is small on purpose
age leaves out everything that made OpenPGP hard to use safely: there is no algorithm negotiation, no compression (which can leak information about the plaintext), no unauthenticated mode, no keyserver and no web of trust. A recipient is a string you can paste into a config file, and a file encrypted to several recipients has one stanza per recipient. The cost is scope: age cannot sign, so it does not prove who encrypted a file, and it does not manage or distribute keys. Tools fill those gaps around it: SOPS encrypts YAML and JSON values to age recipients, and plugins let identities live on YubiKeys, TPMs or the macOS Secure Enclave.
GnuPG's breadth is its value where OpenPGP is the shared language: signed and encrypted email with existing correspondents, package repositories, release signatures that downstream verifiers already check, and OpenPGP smart cards that hold signing, encryption and authentication keys together.
When to choose which
- Secrets in Git (SOPS), CI variables, backups, dotfiles: age, with one recipient per person or machine and a hardware or offline recovery recipient.
- Encrypting to someone's SSH key you already have: age.
- Long-term confidential archives: age with post-quantum
age1pq1recipients, to resist harvest now, decrypt later. - Encrypted email: OpenPGP (or S/MIME, see S/MIME vs OpenPGP).
- Encrypt and sign in one step for a recipient who uses OpenPGP: GnuPG.
- Release and package signatures: neither for encryption; see Sigstore vs GPG.
Migration notes
- Generate age identities per person or machine (
age-keygen, orage-keygen -pqonce everyone who encrypts or decrypts runs age 1.3 or later), and keep a recovery identity offline. - Re-encrypt rather than convert: decrypt each GPG file with the old key and encrypt to the new recipients; there is no format conversion. For SOPS, add age recipients to the creation rules in
.sops.yamland runsops updatekeyson each file; when you later remove the PGP keys, follow withsops rotateso the data key changes too. - Keep the GPG key until every old file and backup has been re-encrypted or has expired.
- Replace GPG signatures separately with SSH signatures, minisign or Sigstore, since age provides none.
- Check sizes and tools with the age encryption tool and inspect old files with the OpenPGP inspector to find which keys and algorithms they use.