Bei age vs. GPG geht es um die Wahl zwischen einem kleinen Verschlüsselungstool mit genau einem Zweck und einer vollständigen OpenPGP-Implementierung. age (Format age-encryption.org/v1, spezifiziert bei C2SP) verschlüsselt eine Datei für einen oder mehrere Empfänger – jeder davon ein kurzer String wie age1…, ein öffentlicher SSH-Schlüssel oder ein Post-Quanten-Schlüssel age1pq1… – und nutzt dafür eine Schlüsselkapselung mit X25519 oder hybrid mit ML-KEM sowie ChaCha20-Poly1305 für die Payload. GnuPG implementiert OpenPGP: Verschlüsselung, Signaturen, Zertifizierung, Schlüsselbunde, Schlüsselserver, Smartcards und E-Mail-Integration, mit jahrzehntelanger Kompatibilität im Rücken.
Auf einen Blick
| age | GnuPG (OpenPGP) | |
|---|---|---|
| Umfang | Nur Dateiverschlüsselung | Verschlüsselung, Signieren, Zertifizierung, Schlüsselmanagement |
| Spezifikation | age v1 (C2SP), ein Format, keine Versionsaushandlung | RFC 9580 (2024) und RFC 9980 (2026); GnuPG folgt LibrePGP |
| Öffentlicher Schlüssel | 62 Zeichen (age1…); Post-Quanten-Schlüssel: 1.959 Zeichen (age1pq1…) | Binärer Schlüssel mit User-IDs und Selbstsignaturen; 473 Byte für Ed25519 plus Cv25519 (gemessen) |
| Algorithmen | X25519, ML-KEM-768 + X25519 (hybrid), scrypt für Passphrasen, ChaCha20-Poly1305, HKDF und HMAC-SHA-256 | Viele: RSA, ECC, AES, Camellia, veraltete Verschlüsselungsverfahren für alte Daten; ML-KEM-768 + X25519 in GnuPG 2.5 |
| Konfiguration | Keine | Präferenzen, Vertrauensmodelle, Einstellungen für Algorithmen und Kompression |
| Gemessenes Chiffrat für eine 26-Byte-Datei | 226 Byte (X25519); 1.685 Byte (Post-Quanten-Empfänger) | 203 Byte (Cv25519); 1.288 Byte (Unterschlüssel mit ML-KEM-768 + X25519) |
| SSH-Schlüssel als Empfänger | Ja (ssh-ed25519, ssh-rsa) | Nein |
| Hardwareschlüssel | Über Plugins: YubiKey PIV, TPM, Secure Enclave | OpenPGP-Smartcards und Sicherheitsschlüssel |
| Signaturen | Bewusst keine | Ja |
| Schlüsselsuche | Keine; Empfänger werden direkt ausgetauscht | Schlüsselserver, WKD, Schlüsselbunde |
| Große Dateien | Streaming in authentifizierten Chunks von 64 KiB; seit 1.3 eine API für Entschlüsselung mit wahlfreiem Zugriff | Streaming; ein AEAD-Paket mit Chunks |
Dieselbe Datei auf beide Arten verschlüsseln
Das folgende Skript verschlüsselt ein 26-Byte-Secret für einen age-Empfänger mit X25519, einen age-Empfänger mit Post-Quanten-Verfahren, einen Cv25519-Unterschlüssel von GnuPG und einen GnuPG-Unterschlüssel mit ML-KEM-768 + X25519, entschlüsselt jede Datei wieder und vergleicht die Größen (age 1.3.2, GnuPG 2.5.21). Alles läuft in einem temporären Verzeichnis ab.
#!/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
Beide fügen pro Empfänger einen festen Header und einen kleinen Authentifizierungs-Overhead hinzu, bei echten Dateien ist der Unterschied also vernachlässigbar. Der Post-Quanten-Overhead ist das ML-KEM-768-Chiffrat (1.088 Byte), das in jeder Empfänger-Stanza steckt; bei age fällt er vor allem deshalb größer aus, weil der Header aus Text besteht und die Binärwerte darin in base64 stehen. Das Skript verwendet --trust-model always nur, weil der Schlüssel eine Sekunde vorher erzeugt wurde; im echten Einsatz würdest du den Fingerabdruck des Empfängers prüfen.
Wichtige Details zur Interoperabilität
gpg --list-packets zeigt für die Dateien oben zwei wissenswerte Dinge. Für den Cv25519-Schlüssel schrieb GnuPG 2.5 ein OCB-Paket „AEAD encrypted packet“ im LibrePGP-Format statt des Pakets für verschlüsselte Daten in Version 2, das RFC 9580 definiert, weil die eigenen Schlüssel von GnuPG die Unterstützung dafür ankündigen; Implementierungen, die nur RFC 9580 folgen, können solche Dateien womöglich nicht lesen. Für den Post-Quanten-Unterschlüssel verwendete GnuPG den Public-Key-Algorithmus 8, sein zusammengesetztes Kyber-Verfahren aus LibrePGP, während RFC 9980 dem Hybrid ML-KEM-768 + X25519 die Nummer 35 zuweist; Post-Quanten-Schlüssel von GnuPG und Schlüssel nach RFC 9980 sind daher nicht interoperabel. age hat ein einziges Format und eine einzige Post-Quanten-Konstruktion (HPKE mit dem Hybrid aus ML-KEM-768 und X25519, auch bekannt als X-Wing), mit veröffentlichten Testvektoren.
Warum age bewusst klein ist
age lässt alles weg, was es schwer gemacht hat, OpenPGP sicher zu benutzen: Es gibt keine Aushandlung von Algorithmen, keine Kompression (die Informationen über den Klartext preisgeben kann), keinen nicht authentifizierten Modus, keinen Schlüsselserver und kein Web of Trust. Ein Empfänger ist ein String, den du in eine Konfigurationsdatei einfügen kannst, und eine Datei, die für mehrere Empfänger verschlüsselt ist, enthält pro Empfänger eine Stanza. Der Preis ist der Umfang: age kann nicht signieren, beweist also nicht, wer eine Datei verschlüsselt hat, und es verwaltet oder verteilt keine Schlüssel. Andere Tools schließen diese Lücken: SOPS verschlüsselt YAML- und JSON-Werte für age-Empfänger, und Plugins legen Identitäten (private Schlüssel) auf YubiKeys, in TPMs oder in der Secure Enclave von macOS ab.
Die Breite von GnuPG ist dort ein Vorteil, wo OpenPGP die gemeinsame Sprache ist: signierte und verschlüsselte E-Mails mit bestehenden Kommunikationspartnern, Paket-Repositorys, Release-Signaturen, die nachgelagerte Verifizierer bereits prüfen, und OpenPGP-Smartcards, die Signatur-, Verschlüsselungs- und Authentifizierungsschlüssel gemeinsam aufbewahren.
Wann welches Tool passt
- Secrets in Git (SOPS), CI-Variablen, Backups, Dotfiles: age, mit einem Empfänger pro Person oder Maschine und einem Wiederherstellungsempfänger, der auf Hardware liegt oder offline aufbewahrt wird.
- Verschlüsseln für den SSH-Schlüssel einer Person, den du bereits hast: age.
- Langfristig vertrauliche Archive: age mit Post-Quanten-Empfängern
age1pq1, als Schutz gegen harvest now, decrypt later (jetzt speichern, später entschlüsseln). - Verschlüsselte E-Mail: OpenPGP (oder S/MIME, siehe S/MIME vs. OpenPGP).
- Verschlüsseln und Signieren in einem Schritt für einen Empfänger, der OpenPGP nutzt: GnuPG.
- Release- und Paketsignaturen: weder age noch GPG als Verschlüsselungstool; siehe Sigstore vs. GPG.
Hinweise zur Migration
- Erzeuge age-Identitäten (private Schlüssel) pro Person oder Maschine (
age-keygen, oderage-keygen -pq, sobald alle, die ver- oder entschlüsseln, age 1.3 oder neuer verwenden), und bewahre eine Wiederherstellungsidentität offline auf. - Verschlüssle neu, statt zu konvertieren: Entschlüssle jede GPG-Datei mit dem alten Schlüssel und verschlüssle sie für die neuen Empfänger; eine Formatkonvertierung gibt es nicht. Füge bei SOPS age-Empfänger zu den Creation Rules in
.sops.yamlhinzu und führe für jede Dateisops updatekeysaus; wenn du später die PGP-Schlüssel entfernst, führe anschließendsops rotateaus, damit sich auch der Datenschlüssel ändert. - Behalte den GPG-Schlüssel, bis jede alte Datei und jedes Backup neu verschlüsselt oder abgelaufen ist.
- Ersetze GPG-Signaturen separat durch SSH-Signaturen, minisign oder Sigstore, da age keine bietet.
- Prüfe Größen und Tools mit dem age-Verschlüsselungstool und untersuche alte Dateien mit dem OpenPGP-Inspektor, um herauszufinden, welche Schlüssel und Algorithmen sie verwenden.
Quellen
- age-Spezifikation (C2SP), einschließlich des Empfängertyps ML-KEM-768 + X25519
- Release-Notes zu age v1.3.0
- RFC 9580, OpenPGP
- RFC 9980, Post-Quanten-Kryptografie in OpenPGP
- RFC 9180, hybride Public-Key-Verschlüsselung
- FIPS 203, ML-KEM
- SOPS-Dokumentation: age-Identitäten (private Schlüssel) und Schlüsselmanagement