Pular para o conteúdo
ecdsa.com

SSH vs GPG para assinar commits no Git: qual chave usar

Os dois colocam no objeto de commit uma assinatura que o Git e as plataformas de hospedagem de código conseguem verificar. A assinatura SSH reaproveita chaves que os desenvolvedores já têm; o OpenPGP traz expiração, revogação e um modelo de confiança próprio.

SSH vs GPG para assinar commits é a escolha de qual chave o Git usa quando você roda git commit -S ou git tag -s. Com o OpenPGP (o padrão, normalmente via GnuPG), o Git guarda uma assinatura OpenPGP no cabeçalho gpgsig do commit. Com o SSH (gpg.format=ssh, Git 2.34 ou posterior), o Git chama ssh-keygen -Y sign e guarda uma assinatura SSHSIG no mesmo lugar. Nos dois casos, a assinatura cobre o objeto de commit, que nomeia a árvore, os pais, o autor e a mensagem, de modo que uma única assinatura autentica todo o histórico até aquele commit.

Visão geral

Assinatura SSHAssinatura OpenPGP (GPG)
Suporte no GitGit 2.34 (novembro de 2021), gpg.format=sshgit commit -S desde o Git 1.7.9 (2012); tags assinadas bem antes
Ferramenta de assinaturassh-keygen -Y sign, OpenSSH 8.1 ou posterior (evite a 8.7)gpg ou outra implementação do OpenPGP
Formato da assinaturaSSHSIG com namespace git, com armadura ASCII BEGIN SSH SIGNATUREPacote de assinatura OpenPGP, com armadura ASCII BEGIN PGP SIGNATURE
Tamanho no objeto de commit (Ed25519, medido)306 bytes279 bytes
Em quem se confiaChaves listadas em gpg.ssh.allowedSignersFileChaves do chaveiro com confiança suficiente no dono (owner trust)
Expiração e rotaçãovalid-after e valid-before por chave no arquivo de signatários permitidos (OpenSSH 8.8+)Data de expiração dentro da chave, prorrogável; subchaves
Revogaçãogpg.ssh.revocationFile (lista de chaves ou KRL)Certificado de revogação publicado com a chave
Chaves de hardwareChaves FIDO2 (ed25519-sk, ecdsa-sk), agentes apoiados em enclaves segurosCartões inteligentes OpenPGP e chaves de segurança com applet OpenPGP
Autoridades certificadorasCertificados SSH por meio de linhas cert-authorityNenhuma; identidades certificadas por outras chaves
“Verified” no GitHubDesde 23 de agosto de 2022Desde abril de 2016
“Verified” no GitLabDesde a 15.7 (dezembro de 2022), com disponibilidade geral na 15.8Sim

Assinar e verificar das duas formas

O script abaixo cria uma chave SSH e uma chave OpenPGP para a mesma identidade em um diretório temporário, assina um commit com cada uma e verifica os dois (Git 2.54, OpenSSH 10.3, GnuPG 2.5.21). A configuração global do Git fica desativada, então nada muda na sua máquina.

Um repositório, um commit assinado com SSH e outro com 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"
Saída
== 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)

A verificação SSH nomeia o principal do allowed_signers e a impressão digital da chave; a verificação OpenPGP nomeia o ID de usuário e informa a confiança registrada no chaveiro. Os tamanhos são parecidos porque os dois formatos carregam uma assinatura Ed25519; o SSHSIG também embute a chave pública completa, e o OpenPGP, só a impressão digital dela. O verificador de assinaturas SSH e o inspetor de OpenPGP decodificam qualquer um dos dois blocos a partir de git cat-file commit.

Como decidir de quem aceitar assinaturas

Uma assinatura válida só importa se o verificador souber quais chaves pertencem a quem. No SSH, esse mapeamento é o arquivo allowed_signers: uma linha por principal e chave, opcionalmente restrita ao namespace git e a uma janela de validade. O Git confere valid-after e valid-before com o horário em que a assinatura foi feita, então uma chave aposentada pode continuar no arquivo com uma data de fim, e os commits antigos continuam verificáveis. Uma linha com cert-authority confia em toda chave certificada pela autoridade certificadora que emite os certificados SSH de uma organização, o que serve bem a empresas que já emitem certificados SSH de curta duração. O arquivo pode ficar no repositório, e assim as alterações nele também passam por revisão e são assinadas.

No OpenPGP, o mapeamento fica no chaveiro e no banco de dados de confiança de cada verificador, e é por isso que sistemas de CI e plataformas de código importam chaves explicitamente. A rede de confiança consegue conectar desconhecidos, mas raramente é usada fora de comunidades antigas. As chaves trazem as próprias datas de expiração e certificados de revogação, e as subchaves permitem que o desenvolvedor mantenha a chave primária offline e rotacione as subchaves de assinatura sem mudar de identidade.

As plataformas acrescentam uma camada própria: o GitHub e o GitLab mostram “Verified” quando a chave de assinatura está cadastrada na conta a que pertence o endereço de e-mail verificado do committer. Esse selo depende dos registros da plataforma; a verificação persistente do GitHub, em versão prévia desde novembro de 2024, armazena o resultado no momento do push, de modo que remover a chave mais tarde não o altera.

Quando escolher cada um

  • Desenvolvedores individuais no GitHub ou no GitLab: SSH, de preferência com uma chave ed25519-sk em uma chave de segurança, para que assinar exija um toque.
  • Equipes e empresas: SSH com um arquivo allowed_signers no repositório, ou com certificados SSH emitidos pela CA que você já tem.
  • Projetos que verificam tags upstream com OpenPGP (kernel Linux, muitas distribuições, gerentes de release com chaves publicadas): OpenPGP.
  • Commits feitos no CI ou por bots: nenhuma das duas chaves de longa duração; use assinatura keyless com certificados de curta duração (por exemplo, o gitsign do Sigstore; veja Sigstore vs GPG para assinatura) ou as assinaturas de bot da própria plataforma.
  • Ambientes regulados com cartões inteligentes X.509: gpg.format=x509 com uma ferramenta de assinatura S/MIME, com suporte desde o Git 2.19.

Notas de migração

  1. Configure a assinatura SSH: git config --global gpg.format ssh, user.signingkey apontando para o arquivo da sua chave pública (ou key:: seguido da chave), além de commit.gpgsign true e tag.gpgsign true.
  2. Cadastre a chave como chave de assinatura na sua plataforma; o GitHub e o GitLab aceitam a mesma chave para autenticação e assinatura, mas uma chave de assinatura separada é mais fácil de rotacionar.
  3. Publique a lista de signatários permitidos no repositório (por exemplo, .gitsigners), aponte gpg.ssh.allowedSignersFile para ela no CI e rode git verify-commit nos merges; acrescente valid-after ao adicionar uma chave e valid-before ao aposentá-la.
  4. Mantenha as chaves públicas OpenPGP antigas disponíveis para os verificadores e nas contas das plataformas, para que os commits assinados antes da troca continuem sendo verificados.
  5. Exija commits assinados nos branches protegidos (conjuntos de regras do GitHub, regras de push do GitLab) quando todos os committers tiverem migrado.

Referências

Todas as comparações