Aller au contenu
ecdsa.com

SSH vs GPG : quelle clé doit signer vos commits Git

Les deux placent dans l’objet commit une signature que Git et les forges de code peuvent vérifier. La signature SSH réutilise des clés que les développeurs possèdent déjà ; OpenPGP apporte l’expiration, la révocation et un modèle de confiance qui lui est propre.

SSH vs GPG pour signer les commits, c’est le choix de la clé qu’utilise Git lorsque vous exécutez git commit -S ou git tag -s. Avec OpenPGP (le choix par défaut, généralement GnuPG), Git stocke une signature OpenPGP dans l’en-tête gpgsig du commit. Avec SSH (gpg.format=ssh, Git 2.34 et versions ultérieures), Git appelle ssh-keygen -Y sign et stocke une signature SSHSIG au même endroit. Dans les deux cas, la signature couvre l’objet commit, qui désigne l’arbre, les parents, l’auteur et le message : une seule signature authentifie donc tout l’historique jusqu’à ce commit.

Vue d’ensemble

Signature SSHSignature OpenPGP (GPG)
Prise en charge par GitGit 2.34 (novembre 2021), gpg.format=sshgit commit -S depuis Git 1.7.9 (2012) ; tags signés bien avant
Outil de signaturessh-keygen -Y sign, OpenSSH 8.1 ou ultérieur (éviter la 8.7)gpg ou une autre implémentation d’OpenPGP
Format de signatureSSHSIG avec l’espace de noms git, en armure ASCII BEGIN SSH SIGNATUREPaquet de signature OpenPGP, en armure ASCII BEGIN PGP SIGNATURE
Taille dans l’objet commit (Ed25519, mesurée)306 octets279 octets
À qui l’on fait confianceClés listées dans gpg.ssh.allowedSignersFileClés du trousseau dotées d’un niveau de confiance (owner trust) suffisant
Expiration et rotationvalid-after et valid-before par clé dans le fichier allowed signers (OpenSSH 8.8+)Date d’expiration inscrite dans la clé, prolongeable ; sous-clés
Révocationgpg.ssh.revocationFile (liste de clés ou KRL)Certificat de révocation publié avec la clé
Clés matériellesClés FIDO2 (ed25519-sk, ecdsa-sk), agents adossés à des enclaves sécuriséesCartes à puce OpenPGP et clés de sécurité dotées d’une applet OpenPGP
Autorités de certificationCertificats SSH via des lignes cert-authorityAucune ; identités certifiées par d’autres clés
Badge « Verified » de GitHubDepuis le 23 août 2022Depuis avril 2016
Badge « Verified » de GitLabDepuis la 15.7 (décembre 2022), disponibilité générale avec la 15.8Oui

Signer et vérifier des deux façons

Le script ci-dessous crée une clé SSH et une clé OpenPGP pour la même identité dans un répertoire temporaire, signe un commit avec chacune et vérifie les deux (Git 2.54, OpenSSH 10.3, GnuPG 2.5.21). La configuration Git globale est désactivée : rien ne change sur votre machine.

Un dépôt, un commit signé avec SSH et un autre avec OpenPGP
#!/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"
Sortie
== 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)

La vérification SSH indique le principal tiré de allowed_signers et l’empreinte de la clé ; la vérification OpenPGP indique l’ID utilisateur et le niveau de confiance issu du trousseau. Les tailles sont proches, car les deux transportent une seule signature Ed25519 ; SSHSIG intègre en plus la clé publique complète, OpenPGP seulement son empreinte. Le vérificateur de signatures SSH et l’inspecteur OpenPGP décodent l’un ou l’autre bloc extrait par git cat-file commit.

Décider quelles signatures font foi

Une signature valide n’a de valeur que si le vérificateur sait quelles clés appartiennent à qui. Avec SSH, cette correspondance est le fichier allowed_signers : une ligne par principal et par clé, éventuellement restreinte à l’espace de noms git et à une fenêtre de validité. Git compare valid-after et valid-before à l’heure à laquelle la signature a été faite : une clé retirée peut donc rester dans le fichier avec une date de fin, et les anciens commits restent vérifiables. Une ligne cert-authority fait confiance à toute clé certifiée par l’autorité de certification de certificats SSH d’une organisation, ce qui convient aux entreprises qui délivrent déjà des certificats SSH de courte durée. Le fichier peut résider dans le dépôt : ses modifications sont alors elles-mêmes relues et signées.

Avec OpenPGP, la correspondance réside dans le trousseau et la base de confiance de chaque vérificateur, c’est pourquoi les systèmes de CI et les forges importent les clés explicitement. La toile de confiance peut relier des inconnus, mais elle est rarement utilisée en dehors de communautés établies de longue date. Les clés portent leurs propres dates d’expiration et certificats de révocation, et les sous-clés permettent à un développeur de garder la clé principale hors ligne et de renouveler les sous-clés de signature sans changer d’identité.

Les forges ajoutent leur propre couche : GitHub et GitLab affichent « Verified » lorsque la clé de signature est enregistrée sur le compte associé à l’adresse e-mail vérifiée du committer. Ce badge dépend des données de la forge ; la vérification persistante de GitHub, en préversion depuis novembre 2024, enregistre le résultat au moment du push, si bien qu’une suppression ultérieure de la clé ne le modifie pas.

Quand choisir l’un ou l’autre

  • Développeurs individuels sur GitHub ou GitLab : SSH, idéalement avec une clé ed25519-sk sur une clé de sécurité, pour que chaque signature exige de toucher la clé.
  • Équipes et entreprises : SSH avec un fichier allowed_signers dans le dépôt, ou avec des certificats SSH délivrés par votre AC existante.
  • Projets qui vérifient les tags amont avec OpenPGP (noyau Linux, nombreuses distributions, responsables de versions dont les clés sont publiées) : OpenPGP.
  • Commits créés en CI ou par des bots : aucune des deux clés de longue durée ; utilisez la signature sans clé avec des certificats de courte durée (par exemple gitsign de Sigstore, voir Sigstore vs signature GPG) ou les signatures propres aux bots de la forge.
  • Environnements réglementés avec cartes à puce X.509 : gpg.format=x509 avec un outil de signature S/MIME, pris en charge depuis Git 2.19.

Notes de migration

  1. Configurez la signature SSH : git config --global gpg.format ssh, user.signingkey pointant vers le fichier de votre clé publique (ou key:: suivi de la clé), ainsi que commit.gpgsign true et tag.gpgsign true.
  2. Enregistrez la clé comme clé de signature sur votre forge ; GitHub et GitLab acceptent la même clé pour l’authentification et la signature, mais une clé de signature distincte est plus facile à renouveler.
  3. Publiez la liste des signataires autorisés dans le dépôt (par exemple .gitsigners), faites pointer gpg.ssh.allowedSignersFile vers elle en CI et exécutez git verify-commit sur les merges ; ajoutez valid-after lorsque vous ajoutez une clé et valid-before lorsque vous en retirez une.
  4. Gardez les anciennes clés publiques OpenPGP à la disposition des vérificateurs et sur les comptes de la forge, afin que les commits signés avant le changement restent vérifiables.
  5. Imposez les commits signés sur les branches protégées (ensembles de règles GitHub, règles de push GitLab) une fois que tous les committers ont migré.

Références

Tous les comparatifs