Elegir entre age y GPG es elegir entre una herramienta de cifrado pequeña, de un solo propósito, y una implementación completa de OpenPGP. age (formato age-encryption.org/v1, especificado en C2SP) cifra un archivo para uno o varios destinatarios, cada uno de ellos una cadena corta como age1…, una clave pública SSH o una clave poscuántica age1pq1…, mediante X25519 o la encapsulación de claves híbrida con ML-KEM, y ChaCha20-Poly1305 para la carga útil. GnuPG implementa OpenPGP: cifrado, firmas, certificación, anillos de claves, servidores de claves, tarjetas inteligentes e integración con el correo electrónico, con décadas de compatibilidad a sus espaldas.
De un vistazo
| age | GnuPG (OpenPGP) | |
|---|---|---|
| Alcance | Solo cifrado de archivos | Cifrado, firma, certificación, gestión de claves |
| Especificación | age v1 (C2SP), un único formato, sin versiones que negociar | RFC 9580 (2024) y RFC 9980 (2026); GnuPG sigue LibrePGP |
| Clave pública | 62 caracteres (age1…); poscuántica, 1959 caracteres (age1pq1…) | Clave binaria con ID de usuario y autofirmas; 473 bytes para Ed25519 más Cv25519 (medido) |
| Algoritmos | X25519, híbrido ML-KEM-768 + X25519, scrypt para frases de contraseña, ChaCha20-Poly1305, HKDF y HMAC-SHA-256 | Muchos: RSA, ECC, AES, Camellia, algoritmos de cifrado heredados para datos antiguos; ML-KEM-768 + X25519 en GnuPG 2.5 |
| Configuración | Ninguna | Preferencias, modelos de confianza, configuración de algoritmos y de compresión |
| Texto cifrado medido para un archivo de 26 bytes | 226 bytes (X25519); 1685 bytes (poscuántico) | 203 bytes (Cv25519); 1288 bytes (subclave ML-KEM-768 + X25519) |
| Claves SSH como destinatarios | Sí (ssh-ed25519, ssh-rsa) | No |
| Claves de hardware | Mediante plugins: YubiKey PIV, TPM, Secure Enclave | Tarjetas inteligentes OpenPGP y llaves de seguridad |
| Firmas | Ninguna, por diseño | Sí |
| Descubrimiento de claves | Ninguno; los destinatarios se intercambian directamente | Servidores de claves, WKD, anillos de claves |
| Archivos grandes | Funciona en streaming, en fragmentos autenticados de 64 KiB; API de descifrado con acceso aleatorio desde la 1.3 | Funciona en streaming; un único paquete AEAD fragmentado |
El mismo archivo, cifrado de las dos formas
El script siguiente cifra un secreto de 26 bytes para un destinatario X25519 de age, un destinatario poscuántico de age, una subclave Cv25519 de GnuPG y una subclave ML-KEM-768 + X25519 de GnuPG, descifra cada resultado y compara los tamaños (age 1.3.2, GnuPG 2.5.21). Todo ocurre en un directorio temporal.
#!/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
Ambos añaden un encabezado fijo por destinatario más una pequeña sobrecarga de autenticación, así que con archivos reales la diferencia es despreciable. La sobrecarga poscuántica es el texto cifrado de ML-KEM-768 (1088 bytes) que va en cada bloque de destinatario (stanza); la de age es mayor sobre todo porque su encabezado es texto, con los valores binarios en base64. El script usa --trust-model always solo porque la clave se creó un segundo antes; en un uso real verificarías la huella digital del destinatario.
Detalles de interoperabilidad que importan
gpg --list-packets sobre los archivos anteriores muestra dos cosas que conviene saber. Para su clave Cv25519, GnuPG 2.5 escribió un «AEAD encrypted packet» OCB, el formato de LibrePGP, en lugar del paquete de datos cifrados de versión 2 que define el RFC 9580, porque las propias claves de GnuPG anuncian que lo admiten; puede que las implementaciones que solo siguen el RFC 9580 no lean esos archivos. Para la subclave poscuántica, GnuPG usó el algoritmo de clave pública 8, su compuesto Kyber de LibrePGP, mientras que el RFC 9980 asigna el 35 a ML-KEM-768 + X25519, así que las claves poscuánticas de GnuPG y las del RFC 9980 no son interoperables. age tiene un único formato y una única construcción poscuántica (HPKE con el híbrido de ML-KEM-768 y X25519, también conocido como X-Wing), con vectores de prueba publicados.
Por qué age es pequeño a propósito
age deja fuera todo lo que hacía difícil usar OpenPGP de forma segura: no hay negociación de algoritmos, ni compresión (que puede filtrar información sobre el texto en claro), ni modo sin autenticar, ni servidor de claves, ni red de confianza. Un destinatario es una cadena que puedes pegar en un archivo de configuración, y un archivo cifrado para varios destinatarios tiene un bloque por destinatario. El precio es el alcance: age no puede firmar, así que no demuestra quién cifró un archivo, y tampoco gestiona ni distribuye claves. Otras herramientas cubren esos huecos a su alrededor: SOPS cifra valores YAML y JSON para destinatarios de age, y los plugins permiten que las claves privadas (identities, en la terminología de age) residan en YubiKeys, en un TPM o en el Secure Enclave de macOS.
La amplitud de GnuPG es su valor allí donde OpenPGP es el lenguaje común: correo firmado y cifrado con los corresponsales que ya tienes, repositorios de paquetes, firmas de releases que los verificadores aguas abajo ya comprueban y tarjetas inteligentes OpenPGP que guardan juntas las claves de firma, de cifrado y de autenticación.
Cuándo elegir cada uno
- Secretos en Git (SOPS), variables de CI, copias de seguridad, dotfiles: age, con un destinatario por persona o máquina y un destinatario de recuperación en hardware o sin conexión.
- Cifrar para alguien cuya clave SSH ya tienes: age.
- Archivado confidencial a largo plazo: age con destinatarios poscuánticos
age1pq1, para resistir la estrategia «cosechar ahora, descifrar después» (harvest now, decrypt later). - Correo electrónico cifrado: OpenPGP (o S/MIME; consulta S/MIME frente a OpenPGP).
- Cifrar y firmar en un solo paso para un destinatario que usa OpenPGP: GnuPG.
- Firmas de releases y de paquetes: ninguno de los dos como herramienta de cifrado; consulta Sigstore frente a GPG.
Notas de migración
- Genera claves privadas de age por persona o máquina (
age-keygen, oage-keygen -pqcuando todos los que cifran o descifran usen age 1.3 o posterior), y guarda sin conexión una clave privada de recuperación. - Vuelve a cifrar en lugar de convertir: descifra cada archivo GPG con la clave antigua y cífralo para los nuevos destinatarios; no existe conversión de formato. En SOPS, añade los destinatarios de age a las reglas de creación de
.sops.yamly ejecutasops updatekeysen cada archivo; cuando más adelante quites las claves PGP, ejecuta a continuaciónsops rotatepara que también cambie la clave de datos. - Conserva la clave GPG hasta que todos los archivos y copias de seguridad antiguos se hayan vuelto a cifrar o hayan caducado.
- Sustituye aparte las firmas GPG por firmas SSH, minisign o Sigstore, ya que age no ofrece ninguna.
- Comprueba tamaños y herramientas con la herramienta de cifrado con age e inspecciona los archivos antiguos con el inspector de OpenPGP para saber qué claves y algoritmos usan.
Referencias
- Especificación de age (C2SP), incluido el tipo de destinatario ML-KEM-768 + X25519
- Notas de la versión v1.3.0 de age
- RFC 9580, OpenPGP
- RFC 9980, criptografía poscuántica en OpenPGP
- RFC 9180, cifrado híbrido de clave pública
- FIPS 203, ML-KEM
- Documentación de SOPS: claves privadas de age y gestión de claves