You may already be signing your Git commits with a GPG key, but as of today you can instead choose to sign with your SSH key! Signing in SSH is a relatively new feature that lets you use your private SSH key to sign arbitrary text and others to verify that signature with your public key.
This is great, because pretty much everyone has an SSH key (and pretty much no one has a GPG key). As developers, we’ve probably all uploaded one or more SSH keys to GitHub because that’s necessary to use
git(1) to push to GitHub.
This all makes it pretty easy to sign and verify Git commits with keys we already have. There are a few settings you’ll need to configure to start signing.
First, we’ll want to configure Git to use SSH as the format for signing:
$ git config --global commit.gpgsign true $ git config --global gpg.format ssh
Next, tell Git what key to use for signing. You can find your SSH keys in
~/.ssh/ or list them with
$ ssh-add -L
On the off chance that you don’t already have an SSH key, GitHub have instructions you can follow.
To set the signing key:
$ git config --global user.signingkey "ssh-ed25519 <your key id>"
I ran this, which won’t be useful to you:
$ git config --global user.signingkey \ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf"
To confirm that you’ve configured signing correctly, run:
$ git commit --allow-empty --message="Testing SSH signing"
You should see something like
[main 02f6137] Testing SSH signing if you’ve configured signing correctly.
Git also lets you verify signatures, but there are more steps involved in this. First, some background. GPG/PGP provides a trust framework that allows you to specify specific keys to trust as well as to let you say “I trust this person(‘s key) to verify others’ keys” so that if you trust Caleb and Caleb vouches for Derek,
gpg(1) will agree that Derek’s valid signatures can be trusted.
SSH doesn’t have this “web of trust”. Instead, it uses files like “~/.ssh/authorized_keys” and “~/.ssh/known_hosts” to configure what keys and hosts it should trust, respectively. For verification, we’ll need to create a file to configure allowed signers (see
ssh-keygen(1) ALLOWED SIGNERS). While there’s no formal place to store this file yet, it makes sense to store a global “database” of allowed signers in “~/.ssh/allowed_signers” and a project-specific file might be stored in “
So given all of that, let’s see what our signature looks like on that commit we just made:
$ git show --show-signature error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification commit 52b2c062225a77eea1e142c4073b7b0907eeef5c No signature Author: Caleb Hearth <[email protected]> Date: Mon Nov 15 16:28:46 2021 -0600 Testing SSH signing
The first thing you’ll note is that Git claims there is “No signature” even if we made one. Don’t Panic. We also see on the first line an error from Git noting that we need to configure an allowed signers file. We can set that up globally with:
$ git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers $ touch ~/.ssh/allowed_signers $ git show --show-signature commit 52b2c062225a77eea1e142c4073b7b0907eeef5c Good "git" signature with ED25519 key SHA256:l1J5VwpF7tSJ0SE9/iawdFhwR7BdzPPxdV3jIURg/yo sig_find_principals: sshsig_get_principal: key not found^M No principal matched. Author: Caleb Hearth <[email protected]> Date: Mon Nov 15 16:28:46 2021 -0600 Testing SSH signing
We’re making progress here, and we see that there is a good signature, but that the key is not found. That’s because the allowed_signers file is empty. We can populate it with our own key like this:
echo "[email protected] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf" \ > ~/.ssh/allowed_signers
The format for the allowed signers file is documented in
ssh-keygen(1) ALLOWED SIGNERS and effectively boils down to:
<email>[,<email>...] <key type> <public key>
What is not made explicit in that documentation is that wildcards can be used in place of the email portion. While this may make reduce security in principle by allowing anyone with access to the key to sign, in practice Git allows both committer and author emails to be specified so there is no real security impact here and it makes things somewhat easier.
* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf
When we try showing the signature again, we’ll see a successful verification:
commit 52b2c062225a77eea1e142c4073b7b0907eeef5c Good "git" signature for [email protected] with ED25519 key SHA256:l1J5VwpF7tSJ0SE9/iawdFhwR7BdzPPxdV3jIURg/yo Author: Caleb Hearth <[email protected]> Date: Mon Nov 15 16:28:46 2021 -0600 Testing SSH signing
User SSH Keys from GitHub
GitHub provide public SSH keys for all users at URLs like https://github.com/calebhearth.keys, so you can use that along with the Git author email address to add new allowed signers to that file. That’s manual and a bit of a pain, so I wrote up this very quick-and-dirty script that will pull all keys for all signed commits since 14 November 2021 (the day before Git SSH signing was released) and print them out in allowed_signers format. You can run it on any repo and append the output to your allowed_signers, after which you may want to de-duplicate entries.
After all of this, here are the changes to my
~/.gitconfig that I’ve made to sign with SSH:
[gpg] format = ssh [gpg "ssh"] allowedSignersFile = ~/.ssh/allowed_signers [user] signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf
You’ll need to upload your key to GitHub as a signing key, even if it has already been uploaded as an authentication key (this is a new dichotomy introduced to support this feature). Once that’s been done, you’ll see that commits signed with your SSH key are correctly shown as verified on GitHub:
Using SSH to sign and verify Git commits, after a bit of setup, should be really easy and a great alternative to GPG signing and verification.
I do have an early wishlist for improvements:
- GitHub support for displaying SSH signatures (Fixed! See the GitHub section above)
- a default global allowed_keys file both for Git and SSH
- bugfixes such as to remove those
^MWindows linefeed characters from the error output (Fixed!)
- hopefully we’ll see companies setting up cert-authority for their domains to make setting this up even easier