Zum Inhalt springen
ecdsa.com

age vs. GPG: Dateien und Secrets im Jahr 2026 verschlüsseln

age macht genau eine Sache: Dateien für kurze öffentliche Schlüssel oder mit einer Passphrase verschlüsseln. GnuPG implementiert OpenPGP vollständig, samt Signaturen, Schlüsselmanagement und E-Mail – mit der Komplexität, die das mit sich bringt.

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

ageGnuPG (OpenPGP)
UmfangNur DateiverschlüsselungVerschlüsselung, Signieren, Zertifizierung, Schlüsselmanagement
Spezifikationage v1 (C2SP), ein Format, keine VersionsaushandlungRFC 9580 (2024) und RFC 9980 (2026); GnuPG folgt LibrePGP
Öffentlicher Schlüssel62 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)
AlgorithmenX25519, ML-KEM-768 + X25519 (hybrid), scrypt für Passphrasen, ChaCha20-Poly1305, HKDF und HMAC-SHA-256Viele: RSA, ECC, AES, Camellia, veraltete Verschlüsselungsverfahren für alte Daten; ML-KEM-768 + X25519 in GnuPG 2.5
KonfigurationKeinePräferenzen, Vertrauensmodelle, Einstellungen für Algorithmen und Kompression
Gemessenes Chiffrat für eine 26-Byte-Datei226 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ängerJa (ssh-ed25519, ssh-rsa)Nein
HardwareschlüsselÜber Plugins: YubiKey PIV, TPM, Secure EnclaveOpenPGP-Smartcards und Sicherheitsschlüssel
SignaturenBewusst keineJa
SchlüsselsucheKeine; Empfänger werden direkt ausgetauschtSchlüsselserver, WKD, Schlüsselbunde
Große DateienStreaming in authentifizierten Chunks von 64 KiB; seit 1.3 eine API für Entschlüsselung mit wahlfreiem ZugriffStreaming; 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.

age und GnuPG, klassisch und mit Post-Quanten-Verfahren
#!/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"
Ausgabe
== 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

  1. Erzeuge age-Identitäten (private Schlüssel) pro Person oder Maschine (age-keygen, oder age-keygen -pq, sobald alle, die ver- oder entschlüsseln, age 1.3 oder neuer verwenden), und bewahre eine Wiederherstellungsidentität offline auf.
  2. 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.yaml hinzu und führe für jede Datei sops updatekeys aus; wenn du später die PGP-Schlüssel entfernst, führe anschließend sops rotate aus, damit sich auch der Datenschlüssel ändert.
  3. Behalte den GPG-Schlüssel, bis jede alte Datei und jedes Backup neu verschlüsselt oder abgelaufen ist.
  4. Ersetze GPG-Signaturen separat durch SSH-Signaturen, minisign oder Sigstore, da age keine bietet.
  5. 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

Alle Vergleiche