Elegir entre SSH y GPG para firmar commits es elegir qué clave usa Git cuando ejecutas git commit -S o git tag -s. Con OpenPGP (la opción predeterminada, normalmente GnuPG), Git guarda una firma OpenPGP en el encabezado gpgsig del commit. Con SSH (gpg.format=ssh, Git 2.34 y posteriores), Git llama a ssh-keygen -Y sign y guarda una firma SSHSIG en el mismo lugar. En ambos casos la firma cubre el objeto commit, que nombra el árbol, los padres, el autor y el mensaje, de modo que una sola firma autentica todo el historial hasta ese commit.
De un vistazo
| Firma SSH | Firma OpenPGP (GPG) | |
|---|---|---|
| Compatibilidad en Git | Git 2.34 (noviembre de 2021), gpg.format=ssh | git commit -S desde Git 1.7.9 (2012); las etiquetas firmadas, mucho antes |
| Herramienta de firma | ssh-keygen -Y sign, OpenSSH 8.1 o posterior (evita la 8.7) | gpg u otra implementación de OpenPGP |
| Formato de la firma | SSHSIG con el espacio de nombres git, con armadura BEGIN SSH SIGNATURE | Paquete de firma OpenPGP, con armadura BEGIN PGP SIGNATURE |
| Tamaño en el objeto commit (Ed25519, medido) | 306 bytes | 279 bytes |
| En quién se confía | Las claves que figuran en gpg.ssh.allowedSignersFile | Las claves del anillo de claves con suficiente confianza en el propietario |
| Caducidad y rotación | valid-after y valid-before por clave en el archivo de firmantes permitidos (OpenSSH 8.8+) | Fecha de caducidad dentro de la clave, prorrogable; subclaves |
| Revocación | gpg.ssh.revocationFile (lista de claves o KRL) | Certificado de revocación publicado con la clave |
| Claves de hardware | Llaves FIDO2 (ed25519-sk, ecdsa-sk), agentes respaldados por enclaves seguros | Tarjetas inteligentes OpenPGP y llaves de seguridad con un applet OpenPGP |
| Autoridades de certificación | Certificados SSH mediante líneas cert-authority | Ninguna; las identidades las certifican otras claves |
| «Verified» en GitHub | Desde el 23 de agosto de 2022 | Desde abril de 2016 |
| «Verified» en GitLab | Desde la 15.7 (diciembre de 2022), con disponibilidad general en la 15.8 | Sí |
Firmar y verificar de las dos formas
El script siguiente crea una clave SSH y una clave OpenPGP para la misma identidad en un directorio temporal, firma un commit con cada una y verifica ambos (Git 2.54, OpenSSH 10.3, GnuPG 2.5.21). La configuración global de Git está desactivada, así que no cambia nada en tu equipo.
#!/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)
La verificación SSH nombra el principal de allowed_signers y la huella digital de la clave; la verificación OpenPGP nombra el ID de usuario e informa del nivel de confianza que indica el anillo de claves. Los tamaños son parecidos porque las dos llevan una firma Ed25519; SSHSIG además incluye la clave pública completa, y OpenPGP solo su huella digital. El verificador de firmas SSH y el inspector de OpenPGP decodifican cualquiera de los dos bloques que muestra git cat-file commit.
Cómo decidir qué firmas cuentan
Una firma válida solo importa si el verificador sabe qué claves pertenecen a quién. Con SSH, esa correspondencia es el archivo allowed_signers: una línea por principal y clave, restringida opcionalmente al espacio de nombres git y a una ventana de validez. Git compara valid-after y valid-before con el momento en que se hizo la firma, de modo que una clave retirada puede quedarse en el archivo con una fecha de fin y los commits antiguos siguen siendo verificables. Una línea con cert-authority confía en toda clave certificada por la autoridad de certificados SSH de una organización, lo que conviene a las empresas que ya emiten certificados SSH de corta duración. El archivo puede vivir en el repositorio, así que sus cambios también se revisan y se firman.
Con OpenPGP, la correspondencia vive en el anillo de claves y en la base de datos de confianza de cada verificador, y por eso los sistemas de CI y las forjas importan las claves de forma explícita. La red de confianza puede conectar a desconocidos, pero rara vez se usa fuera de comunidades con mucha trayectoria. Las claves llevan sus propias fechas de caducidad y sus certificados de revocación, y las subclaves permiten a un desarrollador guardar la clave principal sin conexión y rotar las subclaves de firma sin cambiar de identidad.
Las forjas añaden su propia capa: GitHub y GitLab muestran «Verified» cuando la clave de firma está registrada en la cuenta a la que pertenece la dirección de correo electrónico verificada del committer. Esa insignia depende de los registros de la forja; la verificación persistente de GitHub, en versión preliminar desde noviembre de 2024, guarda el resultado en el momento del push, de modo que eliminar la clave más tarde no lo cambia.
Cuándo elegir cada uno
- Desarrolladores individuales en GitHub o GitLab: SSH, idealmente con una clave
ed25519-sken una llave de seguridad, para que firmar exija un toque. - Equipos y empresas: SSH con un archivo
allowed_signersen el repositorio, o con certificados SSH emitidos por tu CA actual. - Proyectos que verifican con OpenPGP las etiquetas de upstream (kernel de Linux, muchas distribuciones, responsables de publicación con claves publicadas): OpenPGP.
- Commits hechos en la CI o por bots: ninguna de las dos claves de larga duración; usa la firma keyless con certificados de corta duración (por ejemplo, gitsign de Sigstore; consulta Sigstore frente a GPG) o las firmas de bots de la propia forja.
- Entornos regulados con tarjetas inteligentes X.509:
gpg.format=x509con una herramienta de firma S/MIME, compatible desde Git 2.19.
Notas de migración
- Configura la firma SSH:
git config --global gpg.format ssh,user.signingkeycon la ruta de tu archivo de clave pública (okey::seguido de la clave), ycommit.gpgsign trueytag.gpgsign true. - Registra la clave como clave de firma en tu forja; GitHub y GitLab aceptan la misma clave para la autenticación y para la firma, pero una clave de firma independiente es más fácil de rotar.
- Publica los firmantes permitidos en el repositorio (por ejemplo,
.gitsigners), apuntagpg.ssh.allowedSignersFilea ese archivo en la CI y ejecutagit verify-commiten los merges; añadevalid-aftercuando incorpores una clave yvalid-beforecuando retires una. - Conserva las claves públicas OpenPGP antiguas a disposición de los verificadores y en las cuentas de la forja, para que los commits firmados antes del cambio sigan verificándose.
- Exige commits firmados en las ramas protegidas (conjuntos de reglas de GitHub, reglas de push de GitLab) cuando todos los committers hayan hecho el cambio.
Referencias
- Documentación de Git: git-config gpg.format, gpg.ssh.allowedSignersFile, gpg.ssh.revocationFile
- Notas de la versión 2.34 de Git
- Notas de la versión 8.1 de OpenSSH y manual de ssh-keygen, ALLOWED SIGNERS
- OpenSSH PROTOCOL.sshsig
- RFC 9580, OpenPGP
- Registro de cambios de GitHub: verificación de commits firmados con SSH
- GitLab: firmar commits y etiquetas con claves SSH