Vanja Ćosić

Sign your git commits using SSH keys

Why sign your commits? #

Before you can use git on your machine, you have to set your name and email address in the git configuration. That information is embedded into each commit you create and can displayed in the git command line and on services like GitHub.

But the author information in commits is just a bit of text pulled from a configuration and is not verified. That means anyone could change their local git configuration and type in your name and email address and push commits pretending to be you.

If you choose to sign your commits using cryptographic keys, that signature will be included alongside the commit and author information, meaning others can be (reasonably) certain that it was you who made those commits and not someone pretending to be you.

For example, on GitHub signed commits are displayed with a “Verified” badge in the commit history:

GitHub commit log showing that my commit was signed and verified

This is specially important for authors and maintainers of open source software that many others rely on. If trusted contributors are signing all their commits, it makes it much harder for attackers to sneak in malicious code into projects.

Why use SSH keys for signing? #

There are three methods for signing commits and tags in git.

Historically PGP was the default choice for signing commits, encrypting emails, and other kinds of data. But GPG / PGP can be quite cumbersome and overwhelming to use and is no longer recommended for most purposes.

Luckily we can use SSH key pairs for signing our commits, which is relatively speaking a fairly new addition that many developers don’t know about. The ability to sign arbitrary data was added to OpenSSH in 20191 and support for signing commits with SSH keys was added to git in version 2.34, which came out in 2021.2

The third option for signing commits is to use S/MIME and X.509 certificate keys. This makes might make sense for environments that require a more scaleable setup and has a Public Key Infrastructure. But that is a whole different can of worms, so I will just skip over that.

SSH key pairs are more familiar to most developers since they are the same types of keys we are already using to connect and authorize to servers and platforms. And they are much easier to generate, manage, and use in practice than certificates or PGP.

If you don’t have an SSH key pair or are using very old ones, you should strongly consider rotating them. I wrote a post recently about how to create new and secure SSH key pairs and use them with GitHub.

Prepare git for signing #

  1. Configure git to use SSH instead GPG

    git config --global gpg.format ssh
  2. Let git know where your (.pub) signing key is

    git config --global user.signingkey ~/.ssh/
  3. Create a file that will store trusted signatures

    touch ~/.config/.git_allowed_signers
  4. Add your email and public key to the file

    # Email        # Key type  # Key signature
    [email protected] ssh-ed25519 AAAAC3NzaC1BZFI3NTE5AAAAIPQSEV470Fi[...]
  5. Configure git to use this file to look for signers

    git config --global gpg.ssh.allowedSignersFile ~/.config/.git_allowed_signers

How to sign commits #

There are two options when it comes to actually doing the signing. Set-and-forget where git does it every time or the manual option, where you chose exactly which commits are signed.

Automatic signing #

Configure git to sign all your commits:

git config --global commit.gpgsign true

Manual signing #

If you prefer to control which commits are signed, you can skip the above step and do it manually each time using -S.

git commit -S -m "My commit message"

Perhaps if you only want to sign commits in a work context and not for private projects or something like that. But I would discourage that as it just increases the likelihood that you will forget to do it going forward.

Testing #

It’s always a good idea to test that signing works in a non-critical repository before you start using it in your daily workflow.

For this example, I will create a new dummy repository locally and delete it afterwards.

  1. Make a commit in a repository

    mkdir test && cd test
    git init
    touch testfile
    git add .
    git commit -m "Just testing my new signature"
  2. Verify that the commit is signed

    git log --show-signature

    If it worked, the commit details will say Good "git" signature.

    The result should look like this:

    $ git log --show-signature
    commit 5bbeeb0fe1fb2b07d71c8cf00fb1b105f432f92f (HEAD -> main)
    Good "git" signature for [email protected] with ED25519 key SHA256:9bDstFrkRXpCiK13fpJRd2Vk[...]
    Author: Your Name <[email protected]>
    Date:   Mon Jun 19 21:39:34 2023 +0200
        Just testing my new signature

If you are on MacOS and get an error that looks like this:

$ git commit -m "Some commit"

error: Load key "/var/folders/_c/2ck_whcd361g7feth13w_kxc0020gn/T//.git_signing_key_tmpCaeBpa": invalid format?

fatal: failed to write commit object

It might be because you need to load your SSH key into the ssh-agent again. You can do so using ssh-add:

ssh-add --apple-use-keychain ~/.ssh/id_ed25519

Tell GitHub which key you are using #

Now that signing is working locally, you need to set it up for the git hosting services you use.

Go to your GitHub account settings, find the SSH and GPGP keys section, and add your key. (GitHub docs)

If you, like me, already use SSH keys for authenticating with GitHub and other servers, you can use the same key pair for signing as well. But you need to upload the key again and choose Signing key as the type as shown below, otherwise it will not work.

Adding SSH keys as signing keys on GitHub

Once you started pushing signed commits to GitHub they will appear as “Verified” in the commit history:

GitHub commit log showing that my commit was signed and verified

Bonus: Protect your branches #

You can enable branch protection for your repositories on GitHub. This feature allows you to enforce rules that need to be followed before a commit can be merged into a branch.

One of the rules you can configure, is a requirement that new commits must be signed and verified. This is useful to prevent accidental pushes of unsigned commits from yourself and others.

Bonus: Vigilant mode #

If you want to go the extra mile, you can enable vigilant mode for your GitHub account. Though you should only do this if you know what you are doing.

By default, signing your commits marks them as “Verified” and unsigned commits just look like regular commits. Vigilant mode goes a step further and marks unsigned commits as “Unverified” which is a strong indicator to you and others that someting could be wrong.

  1. It’s Now Possible To Sign Arbitrary Data With Your SSH Keys by Andrew Ayer ↩︎

  2. From the git project release notes, [ANNOUNCE] Git v2.34.0:
    In addition to GnuPG, ssh public crypto can be used for object and push-cert signing. ↩︎