Git identity is self-declared. user.email is a config value, not a
credential — anyone can set it to your address, commit, and push
wherever they have write access. GitHub renders your avatar on the
result. Nothing in the process verifies that you wrote anything.
Commit signing fixes that, and since Git 2.34 you can do it with the SSH key you already push with. No GPG keyring, no key servers, no expiry dance.
Setup
Four config lines:
[user]
signingkey = ~/.ssh/id_ed25519.pub
[gpg]
format = ssh
[commit]
gpgsign = true
Note signingkey points at the public key. Git derives the private
half from it — and if you use an agent, it never touches the file at
all.
Then upload the same public key to GitHub a second time, as a signing
key (Settings → SSH and GPG keys → New SSH key → key type: signing).
Authentication keys and signing keys are separate slots even when the
key material is identical; uploading to one does not populate the
other. With gh installed:
gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "signing-key"
That needs the admin:ssh_signing_key scope, which the default gh
login does not include — gh auth refresh -h github.com -s admin:ssh_signing_key
first if it 404s.
Verifying locally
GitHub verifies signatures server-side against the key you uploaded.
For git log --show-signature to work on your own machine, Git needs
to know which keys to trust, via an allowed-signers file:
echo "you@example.com $(cat ~/.ssh/id_ed25519.pub)" \
> ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
The first field is an email, and it must match the committer email on the commits you want verified — a mismatch produces “No principal matched” even though the signature itself is fine. Then:
$ git log -1 --show-signature
Good "git" signature for you@example.com with ED25519 key SHA256:TbzyV5...
What this does and doesn’t get you
It proves a commit was made by someone holding your private key. That is a real improvement over an unauthenticated email string, and it is what GitHub’s Verified badge attests to.
It does not prove the commit is good, and it does not protect a key sitting readable on a laptop that gets compromised. If you want the private half to be genuinely unextractable, generate the key on a hardware token:
ssh-keygen -t ed25519-sk -C "signing key"
Same config, same GitHub upload — the key now lives on the token and signing requires a physical touch. Buy two and register both, because a hardware key you lose without a backup is a lockout, not an inconvenience.
One caveat about history
Signing is not retroactive. Commits made before you turned it on stay unsigned, and rewriting history to sign them changes every hash from that point forward. Not worth it on a shared branch. Turn it on, and let the verified history start from today.