Zum Inhalt springen
ecdsa.com

SSH vs. GPG: welcher Schlüssel deine Git-Commits signiert

Beide legen im Commit-Objekt eine Signatur ab, die Git und Code-Hosting-Plattformen prüfen können. SSH-Signaturen nutzen Schlüssel, die ohnehin schon vorhanden sind; OpenPGP bringt Ablaufdaten, Widerruf und ein eigenes Vertrauensmodell mit.

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 SSHSignieren mit OpenPGP (GPG)
Unterstützung in GitGit 2.34 (November 2021), gpg.format=sshgit commit -S seit Git 1.7.9 (2012); signierte Tags schon viel früher
Signaturtoolssh-keygen -Y sign, OpenSSH 8.1 oder neuer (8.7 meiden)gpg oder eine andere OpenPGP-Implementierung
SignaturformatSSHSIG mit Namespace git, in ASCII-Armor BEGIN SSH SIGNATUREOpenPGP-Signaturpaket, in ASCII-Armor BEGIN PGP SIGNATURE
Größe im Commit-Objekt (Ed25519, gemessen)306 Byte279 Byte
Wem vertraut wirdSchlüssel, die in gpg.ssh.allowedSignersFile aufgeführt sindSchlüssel im Schlüsselbund mit ausreichendem Besitzervertrauen (Owner Trust)
Ablauf und Rotationvalid-after und valid-before pro Schlüssel in der Allowed-Signers-Datei (OpenSSH 8.8+)Ablaufdatum im Schlüssel, verlängerbar; Unterschlüssel
Widerrufgpg.ssh.revocationFile (Schlüsselliste oder KRL)Widerrufszertifikat, das mit dem Schlüssel veröffentlicht wird
HardwareschlüsselFIDO2-Schlüssel (ed25519-sk, ecdsa-sk), Agenten auf Basis einer Secure EnclaveOpenPGP-Smartcards und Sicherheitsschlüssel mit OpenPGP-Applet
ZertifizierungsstellenSSH-Zertifikate über Zeilen mit cert-authorityKeine; Identitäten werden von anderen Schlüsseln zertifiziert
„Verified“ bei GitHubSeit dem 23. August 2022Seit April 2016
„Verified“ bei GitLabSeit 15.7 (Dezember 2022), allgemein verfügbar ab 15.8Ja

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.

Ein Repository, ein mit SSH und ein mit OpenPGP signierter Commit
#!/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"
Ausgabe
== 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-sk auf einem Sicherheitsschlüssel, sodass jedes Signieren eine Berührung erfordert.
  • Teams und Unternehmen: SSH mit einer Datei allowed_signers im 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=x509 mit einem S/MIME-Signaturtool, unterstützt seit Git 2.19.

Hinweise zur Migration

  1. SSH-Signaturen konfigurieren: git config --global gpg.format ssh, user.signingkey auf deine Datei mit dem öffentlichen Schlüssel gesetzt (oder key:: mit dem Schlüssel) sowie commit.gpgsign true und tag.gpgsign true.
  2. 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.
  3. Allowed Signers im Repository veröffentlichen (zum Beispiel als .gitsigners), gpg.ssh.allowedSignersFile in CI darauf zeigen lassen und bei Merges git verify-commit ausführen; ergänze valid-after, wenn du einen Schlüssel hinzufügst, und valid-before, wenn du einen ausmusterst.
  4. 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.
  5. Signaturpflicht durchsetzen: Verlange signierte Commits auf geschützten Branches (Rulesets bei GitHub, Push-Regeln bei GitLab), sobald alle Committer umgestiegen sind.

Quellen

Alle Vergleiche