Using GPG to sign Git tags and commits

Ángel Ortega

Use case: you use Git as your version control system and you want to sign your tags and/or commits using a PGP key because it sounds very cool and probably enhances security somewhat.

If you already have a GPG key, jump to next section.

Create a new, generic GPG key by invoking this command:

gpg --full-generate-key

This is an interactive command. It says many nerdy things. To the "what kind of key you want" question, enter 1 (or whatever the option is) for "RSA and RSA (default)". Informed people say that these days EC25519 keys are cooler, but I have no idea. I also have no idea why you cannot create this kind of elliptic curve keys using this interactive mode, because you can do it from the command line. I don't give a damn.

Give it a keysize of 4096, set an expiration date to "key does not expire" and give reasonable answers to the rest of questions. Enter a clever, long passphrase. You'll end up with a new keyring with your new key.

pub   rsa4096 2024-04-12 [SC]
      8A7E286E3A81676C5718B64E278FD009868D43DE
uid                      Waaaah <sexmachine@example.com>
sub   rsa4096 2024-04-12 [E]

We'll create a special key for signing your code, because it's cool and make things overly complicated and you'll look like you know what you are doing. We'll also set a short expiration life, so that you have to keep returning to this page over and over in the future because you'll have forgotten all these cumbersome steps. Run:

gpg --edit-key sexmachine@example.com

This is interactive again. Type:

gpg> addkey

Enter the option for "RSA (sign only)", 4096 bits keysize, and 1y (1 year) expiration date. You will need to supply the passphrase for the key. Confirm everything and save and such. You'll see something like

sec  rsa4096/278FD009868D43DE
     created: 2024-04-12  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/A34BD7637BCADEBF
     created: 2024-04-12  expires: never       usage: E   
ssb  rsa4096/1CA4911800F8A2E5
     created: 2024-04-12  expires: 2025-04-12  usage: S   
[ultimate] (1). Waaaah <sexmachine@example.com>

We now have a signing-only key (usage: S) with an expiration date. Take note of its key Id (in this case, it's the 1CA4911800F8A2E5 string).

If you need to get this information again, run gpg --list-secret-keys --keyid-format=long.

It's a good thing to upload your keys to a keyserver for the rest of people to find it. Keyservers come and go. These days, https://keys.openpgp.org:443 seems to work fine. So, send your keys there using the following command:

gpg --keyserver https://keys.openpgp.org:443 --send-key 1CA4911800F8A2E5

People will be able to get your key by typing

gpg --keyserver https://keys.openpgp.org:443 --search-keys sexmachine@example.com

This command allows people to interactively add your key to their keyring. But, it sometimes fails with the cryptic message no valid OpenPGP data found. Why not. I found that going to that web site, searching and downloading the ASCII armored key and importing it by hand works. Meh.

Move to the working directory of your git project and type:

git config user.signingkey '1CA4911800F8A2E5!'

But use your key Id, not this one. Note the ! at the end. It seems it's necessary because it's a subkey. I don't know why.

Now you are ready to sign tags! You have to do it this way from now:

git tag 0.01 -s -m "New development release 0.01 (codename Potato Head)"

Note the -s argument. Also, signed tags must have a message. If you don't provide one, a $EDITOR will be spawned for you to do it.

If everything is OK, GPG will ask you for the passphrase. I've found that this sometimes fails, I have no idea why. It's probably related to the gpg-agent program or shit. Try killing it. Or signing some dummy text from the standard input by running gpg --sign -a, entering the passphrase and recalling the git tag command. All this GPG thing looks very fragile.

If you are still here, you'll see that a git log or git tag does not show anything new. You must use git show {tag name}. You'll see something like

tag 0.01
Tagger: Waaaah <sexmachine@example.com>
Date:   Fri Apr 12 19:42:58 2024 +0200

New development release 0.01 (codename Potato Head)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEkCwFKCMlLL6wb6f47iFRZCH8+ZIFAmYZcqIACgkQ7iFRZCH8
+ZL9QxAAlLlENIcEJe/hHRNfvlDgk7/PS1MJUYyam32E8tO8MsLRHZ164icAoaGC
XuEY2fiFrH7q0az5+LzA9Cy2EZND1UCHgM7m4Vu6beNR7xKw80eHu/0FTc+stLGO
vDxGmHOpVtSG03PU8E/bmTk2TXB9qcJzR/jBeqz4V+31+8gaF6XOSBhMfP+hdUu4
/FpAAXCOT6NGIG3vYCMaobxudnIZBLtqBtVzHHrk5qu3SJB4ReddO2HFu9528E/j
nzzpXlVqAGSJ5iY80K49CE/kirvICZwJf6LlaKuXJvrLntia+sr3Axq4DPjdujex
r8wMjxYiIce0iGRywWng3ZXwafFdRHmCSUbv85NeHLymdapSs7ly1NbqFBo5NKzX
Fhj81KD4HUeP+7BsppYzM0zW5tjngHA2qzhHIK4xMIqtVLCXI460rBsz+xbGbGOW
PMjGXEw6pBwbiRXmwe8iE0JP4AHsxN+05B6mHKeqemsinOx5/B7n+KgbjhLtWANX
kNFzdaRScfDcdnqlApV8aL6//d2P14Mh3ncqgfQNJh2SIgEsdxsGPDfUA2jTZ0K/
fLxxm+C7P52/LvTLHbzH6EHINUz27j5NKsS8Z5K2ozHRGwSErrKGw++GfDxeeQnP
W56MS4uvjVdafjP581ldggjHayo653heJxBvQhRIwfLbMRvZbMc=
=HAcq
-----END PGP SIGNATURE-----

Oh yeah.

Cool guys that have your GPG public key available in their keyring can verify the tag using git tag -v {tag name}. GPG barfs a noisy message telling something about a good signature.

You can also sign individual commits. You must add the -S (capital S) argument to your git commit line, because using -s like you did in git tag would be too easy to remember.

Again, it seems like everything remains the same, unless you add the magic incantation --show-signature to the git log command. This dumps specially joyful messages from GPG talking about good signatures and such. If your logs contain many signed commits it may became very slow.

Creating a signed commit:

git commit -S -a -m "Commit message"

Creating a signed tag:

git tag -s 1.23 -m "Tag description"

Verifying a signed tag:

git tag -v 1.23

Showing signature information in logs:

git log --show-signature

Is this worth the effort? I'm not sure. It surely looks techy.

If you found this post useful, you may buy Ángel a coffee.