How to Manage GnuPG Keys and Sign and Verify Git Commits
Introduction
Software and infastructure security is more prominent than ever, and an important part is signing stuff.
I have postponed using GnuPG for many years, but now is the time. GnuPG has a reputation for being cumbersome to use. One of its major intended uses apart from signing is to facilitate a web of trust in a community. Suprisingly, in my professional life related to software and infrastructure, I have seen nothing of this yet.
This post is about creating and managing a primary key and one subkey, and using the latter to sign Git commits. Also, it is shown how commits and other signatures can more easily be verified by others.
The main resources I used are listed at the end of this article.
Create the Primary Key
Create a primary key for signing and certification only, using gpg --full-generate-key
, choose ECC (sign only), Curve
25519, and a suitable expiry, e.g. 2 years. Create a strong passphrase, e.g. using Diceware. You can
specify a comment to be included in your identity. Here, we create a key for Alice.
Use gpg --list-keys --keyid-format LONG
to get the ID and the fingerprint of your primary key. Note that you can add part of your
identity to this command as a filter, e.g. gpg --list-keys alice
.
pub ed25519/FE0306B61A94B0D7 2024-05-04 [SC] [expires: 2024-06-03]
48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid [ultimate] Alice (demo only) <alice@mgfeller.net>
FE0306B61A94B0D7
is the key ID, and 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
is the fingerprint.
Create the Subkey
Create a subkey to be used for signing only. Some recommend a different subkey for each device you use and for each customer engagement.
Create the subkey using gpg --edit-key ID
. ID can be the key ID, the fingerprint of the primary key, or a part of the identity.
At the gpg>
prompt, type addkey
, choose ECC (sign only), Curve
25519, and a suitable expiry, e.g. 2 years.
The output of gpg --list-keys alice
is now:
pub ed25519 2024-05-04 [SC] [expires: 2024-06-03]
48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid [ultimate] Alice (demo only) <alice@mgfeller.net>
sub ed25519 2024-05-04 [S] [expires: 2024-06-03]
Create a Backup
Backup your public and secret keys, and the whole GnuPG directory for good measure:
gpg --export --export-options backup --output public-backup.gpg 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-secret-keys --export-options backup --output secret-backup.gpg 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-secret-subkeys --export-options backup --output secret-subkeys.pgp 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-ownertrust > trust.gpg
umask 077; tar -cf ./gnupg-backup.tar -C $HOME .gnupg
Copy your backup securely to a secure place. This is a fascinating aspect in itself, but not discussed here. There are plenty of resources on the internet about this topic.
Secure the Local Key
You might now like to change the local passphrase for your key, see https://wiki.debian.org/Subkeys.
Remove the primary secret key from the current device by deleting the file $HOME/.gnupg/private-keys-v1.d/KEYGRIP.key
(GnuPG
2.1 or later).
The KEYGRIP can be obtained by running gpg --list-secret-keys --with-keygrip
.
After having done that, the output of gpg --list-secret-keys
indicates this by the #
suffix added to the sec
label.
Publish the Public Key
Export the public key in ascii format:
gpg --output 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc --armor --export 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
This can e.g. be added to your GitHub account, as a new GPG key.
In order for others to verify a signature, the public key needs to be publicly accessible. It can be uploaded to a (known) key server. Alternatively, it can be uploaded to your website as plain document, accessible using https. I keep mine on openpgp.mgfeller.net, named using the key’s fingerprint.
For the key just generated, the URL of the public key might thus become https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc .
This URL can be included in the signature, and automatically be retreived by someone else’s GnuPG.
To achieve this, use the option --sig-keyserver-url to specify the URL. It can also be specified in the GnuPG config file.
Sign Git Commits
Use gpg --list-secret-keys --keyid-format=long ID
to get the ID of the subkey used for signing:
sec# ed25519/FE0306B61A94B0D7 2024-05-04 [SC] [expires: 2024-06-03]
48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid [ultimate] Alice (demo only) <alice@mgfeller.net>
ssb ed25519/490AC8047B27173D 2024-05-04 [S] [expires: 2024-06-03]
Configuring Git for signing commits with the chosen key is quite straightforward:
git config user.signingkey 490AC8047B27173D!
which is the subkey be used, note the required!
suffix.git config commit.gpgsign true
tells Git to sign all commits by default.export GPG_TTY=$(tty)
, put this in your.bashrc
file, or equivalent.
Using git commit -S -m "clarify description"
, commit the change and sign the commit.
The signature can be inspected like this:
git log -1 --show-signature
commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made lø. 04. mai 2024 kl. 18.01 +0200 CEST
gpg: using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Good signature from "Alice (demo only) <alice@mgfeller.net>" [ultimate]
gpg: Preferred keyserver: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
Author: Alice (demo only) <alice@mgfeller.net>
Date: Sat May 4 17:57:57 2024 +0200
clarify description
Everything is good because we’re verifying it on our PC. Note that the preferred keyserver is the URL of the public key, as discussed above.
Verify Git Commit Signatures
For another user, the signature cannot be verified yet as nothing has been done to download and import Alice’s public key.
Looking at the Git log will thus show
git log -1 --show-signature
commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made Sat 04 May 2024 04:01:00 PM UTC
gpg: using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Key available at: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
gpg: Can't check signature: No public key
Author: Alice (demo only) <alice@mgfeller.net>
Date: Sat May 4 17:57:57 2024 +0200
clarify description
GnuPG can be configured to automatically download keys, and honor included key location by adding the following lines to
~/.gnupg/gpg.conf
:
auto-key-retrieve
keyserver-options honor-keyserver-url
This results in
commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made Sat 04 May 2024 04:01:00 PM UTC
gpg: using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Key available at: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
gpg: requesting key 490AC8047B27173D from https server openpgp.mgfeller.net
gpg: key FE0306B61A94B0D7: public key "Alice (demo only) <alice@mgfeller.net>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: Good signature from "Alice (demo only) <alice@mgfeller.net>" [unknown]
gpg: Preferred keyserver: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 48DC 4FF5 BC33 43C2 E427 C7A2 FE03 06B6 1A94 B0D7
Subkey fingerprint: 7EDF C2DB 2A8E ED75 DD12 55A4 490A C804 7B27 173D
Author: Alice (demo only) <alice@mgfeller.net>
Date: Sat May 4 17:57:57 2024 +0200
clarify description
Of course, the key is not yet trusted. How to handle that will be topic of another blog post.
Summary
This articles described how to
create a primary key, for certification and signing only
create a subkey, for signing only; that is the key actually used for signing commits
backup the public and secret keys
secure the local primary key
publish the public key to a website
sign Git commits, including the address of the public key
Also, it was shown how another user can configure her GnuPG to automatically download public key referenced in the signature.
GnuPG Config File
Configuration options can be specified in the file ~/.gnupg/gpg.conf
, assuming the default GnuPG home directory.
The example used for this blogpost is
list-options show-uid-validity show-keyserver-urls
verify-options show-uid-validity
default-new-key-algo ed25519/cert
auto-key-retrieve
keyserver-options honor-keyserver-url
sig-keyserver-url https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
References
https://davesteele.github.io/gpg/2014/09/20/anatomy-of-a-gpg-key/
https://dlorenc.medium.com/should-you-sign-git-commits-f068b07e1b1f
https://sites.google.com/view/chewkeanho/guides/gnupg/export-primary-public-key-with-all-subkeys
https://www.howtogeek.com/816878/how-to-back-up-and-restore-gpg-keys-on-linux/
https://www.reddit.com/r/GnuPG/comments/vjas2e/proper_key_management/
https://yanhan.github.io/posts/2014-03-04-gpg-how-to-trust-imported-key/