Sign git commits on GitHub with GPG in macOS
When you are pushing commits to your repositories you are authenticating with either HTTPS or SSH. Telling GitHub that it is really you. That only ensures that it's the correct user doing the pushing. You could also add an extra level of verification by signing the actual commits before pushing.
This will give you a cool badge on each commit as in the screenshot below and also other users can trust that they are actually coming from you.
There is a proprietary tool called GPG Suite that contains some tools that will help you manage your gpg keys in a GUI and integrate with Apple Mail for encrypting email. For now we'll be focusing on doing it manually with the standalone tools to avoid bloating our system.
Generating GPG keys
I assume you don't already have GPG installed but if you do you probably know what you are doing and can skip ahead. The first step is to install GnuPG, a free implementation of the OpenPGP standard.
brew install gnupg
GnuPG has multiple dependencies and when all are installed we can generate a new key pair.
gpg --full-generate-key
- Pick
RSA and RSA
- Enter a key size of
4096
- No expiration (unless you want it to be invalid after a certain time of course)
- Enter your real name or your GitHub user name
- Enter your commit email address (find out with
git config --global user.email
). If you aren't using a private address you really should. - You can enter
GitHub
as comment.
It may take a while (or not if you have a fast computer) and when done it will be saved under ~/.gnupg
. Both the private key and the public key are saved here. The private key should never be sent to others or be lost.
Add the key to GitHub
To connect you (with your private key) to GitHub we can upload the public key to your profile. First we need to find out which key to copy, you might have multiple.
gpg --list-secret-keys --keyid-format LONG
This will output something similar like this:
/Users/name/.gnupg/pubring.kbx
------------------------------------
sec rsa4096/EE05XXXXXXXXXXXX 2021-01-14 [SC]
215556521B98225XXXXXXXXXXXXXXXXXXXXXXXXX
uid [ultimate] Name Nameson (GitHub) <1234567+user@users.noreply.github.com>
ssb rsa4096/A226A4XXXXXXXXXX 2021-01-14 [E]
What we want here is the GPG key ID, it's the part after sec rsa4096/
, so in this case it's EE05XXXXXXXXXXXX
. And to copy the public key we do that with this command.
gpg --armor --export EE05XXXXXXXXXXXX | pbcopy
This will copy the long public key to your clipboard. If you rather want it to be printed in the terminal you can omit the | pbcopy
part and then copy it manually. It will be a long string inside a block, the whole content should be copied including start and end parts.
-----BEGIN PGP PUBLIC KEY BLOCK-----
SUPER+LONG+STRING+IS+HERE+ABC123+ETC
-----END PGP PUBLIC KEY BLOCK-----
Now we head over to GitHub and navigate to Settings -> SSH and GPG keys
and click on New GPG key
.
Paste the public key and then save it. You'll probably need to enter you password.
Sign commits with your key
Next we need to tell git to use your newly created key when signing the commits. That is easily done by adding a property to your git config. Change the key to match your ID.
git config --global user.signingkey EE05XXXXXXXXXXXX
This is saved in ~/.gitconfig
if you want to manually organize it. Please keep in mind that if you are using dotfiles synced across multiple devices this will also require the same key to be synced. You can skip the --global
flag to add it to a specific repository instead. Using the same keys across multiple devices is described at the end.
When using gpg there is a deamon (gpg-agent
) running in the background managing the keys regardless of what protocol you are using, but we need to add an environmental variable to let it know the current shell. Add this to your ~/.zshrc
, .bashrc
or whatever shell you are using.
export GPG_TTY=$(tty)
After restarting your terminal you should be able to sign your commits by adding -S
or --gpg-sign
to the commit command, git commit -S
. gpg-agent should now show you a prompt to enter your private key password. If entered correctly and you push this commit, GitHub will validate the signing with the provided public key and then mark it as a verified one, congratulations 🥳
This is all you have to do to make commit signing work. If you want to boost the process a bit more you can continue reading.
Sign commits automatically
To avoid entering the -S
flag on each commit you can make signing the default behavior by adding the property to your git config.
git config --global commit.gpgsign true
If you commit often it will probably be a bit tiring after a while to enter your password every time. This can be solved by saving the password in macOS keychain, like we easily can do with ssh key passwords with the ´ssh-agent´. Unfortunately gpg-agent
has no built-in support for this so we need add a separate tool.
The tool mainly used for this is called pinentry-mac
which is now part of the GPG Suite mentioned in the beginning. Luckily for us it is still open-source and recently updated (compared to the initial one that was last updated 2015).
You can install the tool with brew:
brew install pinentry-mac
If not existing already create the file ~/.gnupg/gpg-agent.conf
and add the following line:
pinentry-program /usr/local/bin/pinentry-mac
Then just stop the currently running gpg-agent and next time you try to sign a commit you'll be prompted to save it to the macOS Keychain.
gpgconf --kill gpg-agent
Use signing with keys on multiple devices
In short you have two choices here, either you generate a new key on each of your devices or you use the same key across all of them. If you generate new ones you also need to add them to GitHub but if you lose one device it's easy to just remove the public one on GitHub. So it's safer but also adds a bit more work and currently GitHub doesn't distinguish keys in a good way in my opinion. The latter is easier but if one device is compromised so are all of the other private keys you use, because they are the same.
When it comes to ssh keys I always generate one for each device but I don't do it for my GPG keys, even though it is a bit more secure. The reason is with an example for encrypting/decprypting email. If I receive an email that has been encrypted with my public key I need the private key to be able to decrypt and read the email. If I do this locally I need the same private key connected to the public key, different keys won't work. So I organize my gpg keys by what they are used for instead. One for each GitHub account and one for each email address.
Export your keys
We export the key the same way as we did when adding it to GitHub but we also add a flag for saving it to a file. We use the same ID as in the above example.
gpg --output my_public_key.gpg --armor --export EE05XXXXXXXXXXXX
And for the private key it's another command where you also need to enter your password.
gpg --output my_private_key.gpg --armor --export-secret-key EE05XXXXXXXXXXXX
They will be saved in your current folder when just writing the file names like this.
Transfer and import your keys on another device
Now we need to transfer them to the new device. It's very important to do this in a secure way when you include the private key, this means no email. You can use a USB flash drive or transfer them with scp
for example. On the new device you need to install everything mentioned above and then you can import the keys.
gpg --import my_public_key.gpg
And another command for the private key when importing as well, combined with the password.
gpg --allow-secret-key-import --import my_private_key.gpg
And last but not least, remember to clean up all the exported keys from USB flash drives and other places. It can also be a good idea to backup your keys in a safe or password manager.