Git provides the ability to sign your commits with a GPG key. I felt this could be a fun thing to start doing, and since I’ve had trouble finding simple guides for doing this specifically with ed25519 keys, I’m writing my own.
If you’re just looking for a quick summary of the commands, here they are:
1 | export KEYUID='Firstname Lastname (username) <firstname.lastname@email.com>' |
installation prerequisites
My development environment is a Macbook Pro, so your milage may vary depending on the system you’re using. You’ll need to install 2 items, gnupg and pinentry:
1 | brew install gnupg pinentry-mac |
While the first is self-evident, pinentry is not - and unfortunately, if it is not installed, the errors you might run into are incomprehensible:
1 | error: gpg failed to sign the data: |
From what I have found, the regular TTY is insecure for entering passwords, so GnuPG has developed their own alternative (GnuPG architecture diagram found here). However, I’ve found a lot of users reporting usability problems with this tool, especially over SSH, so I’ll include a section on bypassing it altogether.
configuring gnupg to use pinentry
In ~/.gnupg/gpg.conf:
1 | use-agent |
and in ~/.gnupg/gpg-agent.conf:
1 | pinentry-program /opt/homebrew/bin/pinentry-mac |
You can choose to cache it in macOS’ Keychain so it does not need to be entered repeatedly.
bypassing pinentry entirely
In ~/.gnupg/gpg.conf:
1 | use-agent |
and in ~/.gnupg/gpg-agent.conf:
1 | allow-loopback-pinentry |
These instructions were found online here: https://d.sb/2016/11/gpg-inappropriate-ioctl-for-device-errors
And then run the following to let gpg-agent pick up the configurations:
1 | echo RELOADAGENT | gpg-connect-agent |
generating the gpg keys
Many guides still show how to generate RSA keys. However, the modern alternative for a while has been ED25519. To use it, run:
master key
1 | gpg --quick-generate-key '<User ID>' ed25519 cert never |
This generates a Certify-only (cert) ed25519 key that never expires.
The <User ID> can generally be anything. The standard format follows the convention of:
Your Name (comment) <your.email@address.com>
However, keep in mind that for commits to appear as valid on websites such as Github, you need to include an email that matches one of your verified Github emails. The angle brackets surrounding the email (< >) are required.
Once you hit Enter, it’ll ask you to provide a password. If you followed the configuration steps in the first section, it’ll only ask for it once, so be sure to type/paste it correctly.
GPG will then print the new key:
1 | pub ed25519 2024-12-20 [C] |
The [C] stands for Certify. Generally, since GPG relies on a ‘web of trust’ where people hold onto your key for a long time, you want your root level key to rarely expire. It will also have no abilities, existing solely to sign subkeys that you actually use for day-to-day work.
signing key
The signing key is a subkey of the master key, and they are both logically considered part of the same GPG key.
1 | gpg --quick-add-key 55BE5089F634003042AE70985E88702C976C97B1 ed25519 sign 5y |
This generates a Sign-only (sign) ed25519 key that expires in 5 years (5y). The 55BE5... string is the master key’s full fingerprint, which was printed above.
To continue signing after this time has passed, you’ll need to generate a new subkey again with the master key. The idea behind this is that if you’ve had your key copied/stolen somehow, it can’t be used forever.
Note on ed25519 encryption
Adding an encryption key involves a very small change: instead of ed25519, you’ll need to specify cv25519 for the encryption algorithm:
1 | gpg --quick-add-key 55BE5089F634003042AE70985E88702C976C97B1 cv25519 encr 5y |
listing keys
While there’s numerous ways to list your keys, I’ve found that you generally don’t need to worry about most of them. Regardless of how many subkeys your GPG key has, referencing either the master key’s ID or any part of your UID will allow GPG to pick the appropriate subkey. If you have multiple subkeys with the same function, GPG picks the most recently created one.
However, here’s a few I’ve found useful:
The default:
1 | $ gpg --list-keys username |
Same as above, but with the master key ID split into 4-character chunks:
1 | $ gpg --fingerprint username |
You can also go all-out and list each of the subkey fingerprints:
1 | $ gpg --list-keys --with-subkey-fingerprints --keyid-format=LONG username |
Finally, to export your public key in ASCII plaintext to share, run:
1 | $ gpg --armor --export username |
Or directly exported to a file:
1 | gpg --armor --export --output username.asc username |
signing commits with them
A good idea to ensure you never forget to sign commits is to run the command:
1 | git config --local commit.gpgsign true |
in the root directory of your git repository. This will add a configuration entry in .git/config for that repo, automatically requiring a signature for every commit. This is more convenient than having to manually type the -S flag to sign every git commit as a one-off.
To specify your new key for that repository, run one of the following:
1 | # some part of the UID |
You could also (confusingly) specify the key fingerprint or full key ID of either the master key or the signing key:
1 | # master key fingerprint (last 16 characters of full key ID) |
GPG will automatically pick the signing key you created. If you have multiple, it appears to use the most recently created one.
If you want to use this key globally for every repository on your machine, replace the --local flag with --global. This will place the configuration options in your home directory instead, inside ~/.gitconfig.
resources
- Git guide to signing your work:
https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work - Github guide to using your GPG key with Github:
https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key - Fixing GPG “Inappropriate ioctl for device” errors:
https://d.sb/2016/11/gpg-inappropriate-ioctl-for-device-errors - Guide for
pinentry-mac:
https://velvetcache.org/2023/03/26/a-peek-inside-pinentry/ - Another article covering a slightly different solution to signing commits over ssh:
https://ertt.ca/blog/2022/01-10-git-gpg-ssh/