Beim Signieren von Commits mit SSH vs. GPG geht es um die Frage, welchen Schlüssel Git verwendet, wenn du git commit -S oder git tag -s ausführst. Mit OpenPGP (der Standard, meist GnuPG) legt Git eine OpenPGP-Signatur im Header gpgsig des Commits ab. Mit SSH (gpg.format=ssh, ab Git 2.34) ruft Git ssh-keygen -Y sign auf und legt an derselben Stelle eine SSHSIG-Signatur ab. In beiden Fällen deckt die Signatur das Commit-Objekt ab, das den Tree, die Eltern-Commits, den Autor und die Nachricht nennt – eine einzige Signatur authentifiziert also die gesamte Historie bis zu diesem Commit.
Auf einen Blick
| Signieren mit SSH | Signieren mit OpenPGP (GPG) | |
|---|---|---|
| Unterstützung in Git | Git 2.34 (November 2021), gpg.format=ssh | git commit -S seit Git 1.7.9 (2012); signierte Tags schon viel früher |
| Signaturtool | ssh-keygen -Y sign, OpenSSH 8.1 oder neuer (8.7 meiden) | gpg oder eine andere OpenPGP-Implementierung |
| Signaturformat | SSHSIG mit Namespace git, in ASCII-Armor BEGIN SSH SIGNATURE | OpenPGP-Signaturpaket, in ASCII-Armor BEGIN PGP SIGNATURE |
| Größe im Commit-Objekt (Ed25519, gemessen) | 306 Byte | 279 Byte |
| Wem vertraut wird | Schlüssel, die in gpg.ssh.allowedSignersFile aufgeführt sind | Schlüssel im Schlüsselbund mit ausreichendem Besitzervertrauen (Owner Trust) |
| Ablauf und Rotation | valid-after und valid-before pro Schlüssel in der Allowed-Signers-Datei (OpenSSH 8.8+) | Ablaufdatum im Schlüssel, verlängerbar; Unterschlüssel |
| Widerruf | gpg.ssh.revocationFile (Schlüsselliste oder KRL) | Widerrufszertifikat, das mit dem Schlüssel veröffentlicht wird |
| Hardwareschlüssel | FIDO2-Schlüssel (ed25519-sk, ecdsa-sk), Agenten auf Basis einer Secure Enclave | OpenPGP-Smartcards und Sicherheitsschlüssel mit OpenPGP-Applet |
| Zertifizierungsstellen | SSH-Zertifikate über Zeilen mit cert-authority | Keine; Identitäten werden von anderen Schlüsseln zertifiziert |
| „Verified“ bei GitHub | Seit dem 23. August 2022 | Seit April 2016 |
| „Verified“ bei GitLab | Seit 15.7 (Dezember 2022), allgemein verfügbar ab 15.8 | Ja |
Auf beide Arten signieren und prüfen
Das folgende Skript erzeugt in einem temporären Verzeichnis einen SSH-Schlüssel und einen OpenPGP-Schlüssel für dieselbe Identität, signiert mit jedem davon einen Commit und prüft beide (Git 2.54, OpenSSH 10.3, GnuPG 2.5.21). Die globale Git-Konfiguration ist deaktiviert, sodass sich auf deinem Rechner nichts ändert.
#!/bin/sh
base=$(pwd)
dir=$(mktemp -d "$base/commitdemo.XXXXXX")
cd "$dir" || exit 1
GNUPGHOME=$(mktemp -d /tmp/gpg.XXXXXX) # short path: gpg-agent sockets need one
export GNUPGHOME GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1
export GIT_AUTHOR_NAME=Alice GIT_AUTHOR_EMAIL=alice@example.org
export GIT_COMMITTER_NAME=Alice GIT_COMMITTER_EMAIL=alice@example.org
# One SSH key with an allowed_signers entry, one OpenPGP key
ssh-keygen -q -t ed25519 -N '' -C alice@example.org -f alice
echo "alice@example.org namespaces=\"git\" $(cat alice.pub)" > allowed_signers
gpg --batch --quiet --passphrase '' --quick-gen-key "Alice <alice@example.org>" ed25519 sign 1y
fpr=$(gpg --list-keys --with-colons alice@example.org 2>/dev/null | awk -F: '/^fpr/ { print $10; exit }')
# Two commits: the first signed with SSH, the second with OpenPGP
git init -q repo && cd repo || exit 1
git config gpg.ssh.allowedSignersFile ../allowed_signers
echo one > file && git add file
git -c gpg.format=ssh -c user.signingkey=../alice.pub commit -q -S -m "signed with SSH"
echo two >> file
git commit -q -a -S"$fpr" -m "signed with OpenPGP" 2>/dev/null
for c in HEAD~1 HEAD; do
echo "== $(git log -1 --format=%s "$c")"
git verify-commit "$c" 2>&1 | grep -E "Good|using"
git cat-file commit "$c" | awk '/^gpgsig/,/END/' > sig.txt
echo "$(head -1 sig.txt | cut -c8-) ($(wc -c < sig.txt | tr -d ' ') bytes in the commit object)"
done
gpgconf --kill gpg-agent
cd "$base" && rm -rf "$dir" "$GNUPGHOME"
== signed with SSH
Good "git" signature for alice@example.org with ED25519 key SHA256:7z1vKkx5UJmp2Qrc5ucSDqImz8+r8pfG21roc32f/2A
-----BEGIN SSH SIGNATURE----- (306 bytes in the commit object)
== signed with OpenPGP
gpg: using EDDSA key 6A4E1CC8308072568C9E240CDDBB1B4BE59BFC00
gpg: Good signature from "Alice <alice@example.org>" [ultimate]
-----BEGIN PGP SIGNATURE----- (279 bytes in the commit object)
Die SSH-Prüfung nennt den Principal aus allowed_signers und den Fingerabdruck des Schlüssels; die OpenPGP-Prüfung nennt die User-ID und meldet das Vertrauen laut Schlüsselbund. Die Größen sind ähnlich, weil beide eine Ed25519-Signatur enthalten; SSHSIG bettet zusätzlich den vollständigen öffentlichen Schlüssel ein, OpenPGP nur dessen Fingerabdruck. Der SSH-Signatur-Verifizierer und der OpenPGP-Inspektor dekodieren beide Blöcke aus git cat-file commit.
Festlegen, wessen Signaturen zählen
Eine gültige Signatur nützt nur etwas, wenn der Verifizierer weiß, welche Schlüssel zu wem gehören. Bei SSH ist diese Zuordnung die Datei allowed_signers: eine Zeile pro Principal und Schlüssel, optional auf den Namespace git und auf ein Gültigkeitsfenster beschränkt. Git prüft valid-after und valid-before gegen den Zeitpunkt, zu dem die Signatur erstellt wurde. Ein ausgemusterter Schlüssel kann also mit einem Enddatum in der Datei bleiben, und alte Commits lassen sich weiterhin prüfen. Eine Zeile mit cert-authority vertraut jedem Schlüssel, den die Zertifizierungsstelle einer Organisation für SSH-Zertifikate zertifiziert hat – passend für Unternehmen, die bereits kurzlebige SSH-Zertifikate ausstellen. Die Datei kann im Repository liegen, sodass Änderungen an ihr selbst ein Review durchlaufen und signiert werden.
Bei OpenPGP liegt die Zuordnung im Schlüsselbund und in der Vertrauensdatenbank jedes einzelnen Verifizierers – deshalb importieren CI-Systeme und Plattformen Schlüssel explizit. Das Web of Trust kann Fremde miteinander verbinden, wird aber außerhalb langjähriger Communitys selten genutzt. Schlüssel tragen eigene Ablaufdaten und Widerrufszertifikate, und Unterschlüssel erlauben es, den Hauptschlüssel offline zu halten und Unterschlüssel zum Signieren zu rotieren, ohne die Identität zu wechseln.
Die Plattformen fügen eine eigene Ebene hinzu: GitHub und GitLab zeigen „Verified“, wenn der Signaturschlüssel im Konto registriert ist, zu dem die bestätigte E-Mail-Adresse des Committers gehört. Dieses Badge hängt von den Daten der Plattform ab; die persistente Verifizierung von GitHub, seit November 2024 in der Vorschau, speichert das Ergebnis zum Zeitpunkt des Pushs, sodass ein späteres Entfernen des Schlüssels nichts mehr daran ändert.
Wann welches Verfahren passt
- Einzelne Entwickler auf GitHub oder GitLab: SSH, idealerweise mit einem Schlüssel vom Typ
ed25519-skauf einem Sicherheitsschlüssel, sodass jedes Signieren eine Berührung erfordert. - Teams und Unternehmen: SSH mit einer Datei
allowed_signersim Repository oder mit SSH-Zertifikaten deiner bestehenden CA. - Projekte, die Upstream-Tags mit OpenPGP prüfen (Linux-Kernel, viele Distributionen, Release-Manager mit veröffentlichten Schlüsseln): OpenPGP.
- Commits aus CI oder von Bots: keiner der beiden langlebigen Schlüssel; nutze Keyless Signing mit kurzlebigen Zertifikaten (zum Beispiel gitsign von Sigstore, siehe Sigstore vs. GPG beim Signieren) oder die eigenen Bot-Signaturen der Plattform.
- Regulierte Umgebungen mit X.509-Smartcards:
gpg.format=x509mit einem S/MIME-Signaturtool, unterstützt seit Git 2.19.
Hinweise zur Migration
- SSH-Signaturen konfigurieren:
git config --global gpg.format ssh,user.signingkeyauf deine Datei mit dem öffentlichen Schlüssel gesetzt (oderkey::mit dem Schlüssel) sowiecommit.gpgsign trueundtag.gpgsign true. - Den Schlüssel auf deiner Plattform als Signaturschlüssel registrieren: GitHub und GitLab akzeptieren denselben Schlüssel für Authentifizierung und Signieren, aber ein separater Signaturschlüssel lässt sich leichter rotieren.
- Allowed Signers im Repository veröffentlichen (zum Beispiel als
.gitsigners),gpg.ssh.allowedSignersFilein CI darauf zeigen lassen und bei Mergesgit verify-commitausführen; ergänzevalid-after, wenn du einen Schlüssel hinzufügst, undvalid-before, wenn du einen ausmusterst. - Alte öffentliche OpenPGP-Schlüssel behalten: Halte sie für Verifizierer und in den Konten auf der Plattform verfügbar, damit Commits, die vor dem Wechsel signiert wurden, weiterhin erfolgreich geprüft werden.
- Signaturpflicht durchsetzen: Verlange signierte Commits auf geschützten Branches (Rulesets bei GitHub, Push-Regeln bei GitLab), sobald alle Committer umgestiegen sind.
Quellen
- Git-Dokumentation: git-config gpg.format, gpg.ssh.allowedSignersFile, gpg.ssh.revocationFile
- Versionshinweise zu Git 2.34
- Versionshinweise zu OpenSSH 8.1 und Handbuch zu ssh-keygen, Abschnitt ALLOWED SIGNERS
- OpenSSH PROTOCOL.sshsig
- RFC 9580, OpenPGP
- GitHub-Changelog: Prüfung von SSH-Commit-Signaturen
- GitLab: Commits und Tags mit SSH-Schlüsseln signieren