Using GPG key for Git commits on Linux

Posted by Stefan Kecskes on Wednesday, November 29, 2023

Introduction

“Hey, Stefan, can you please push your changes to the Git repository?” - asked my colleague.
“and make sure you sign your commits with GPG!” - he added.
“Sure, I will do that!” - I replied. But wait a minute, what is GPG signing? And why should I use it?

PGP (Pretty Good Privacy) was initially developed by Phil Zimmermann in 1991 as a commercial product and was designed as a way to encrypt and decrypt email messages. In 1997, it was released as an open-source standard called OpenPGP and since then the OpenPGP became industry standard specification that was implemented by many software vendors, with the most popular implementation being GPG which stands for GNU Privacy Guard. GPG became popular mainly because it was free and open-source, had a community fixing bugs, it was available on multiple platforms (Linux, Windows, MacOS, etc.) and supported a range of cryptographic operations (encryption, decryption, signing, verification, key management, etc.)

Today, we will use GPG for signing Git commits and tags in Git, so that we can verify that the commit or tag was made by you and not someone else. So to say that the code was not tampered with. This is a great way of making sure that the code you are working on is not modified by someone else without your knowledge.

How GPG keys work

GPG uses a public-key cryptography, which means that you have a pair of keys: a public key and a private key.

The public key can:

  • encrypt the data, that means that by using public key you can lock the data.
  • can verify signatures signed by the private key

The public key is meant to be shared with others in order for them to encrypt the message/data/signature. The public key can’t decrypt (unlock) the message back that was encrypted by the very same public key. In fact nobody except the private key can do that.

The private key:

  • can decrypt (unlock) the data encrypted by public key.
  • is kept secret by owner and should never be shared with anyone.
  • can sign the data/message.

For our purpose, like signing git commits, we will use the private key to sign the commit and the public key to verify the signature of commits and we will not send encrypted data. The signature is nothing else than a hash of the commit encrypted by the private key. So when you commit you changes to the Git repository, only the signature (hash of the changes in commit) is added to the commit by you. The public key should be provided to Git Server, so that Git Server can verify the signature. If the signature is valid, then it means that the commit was made by you and not someone else. And if the signature provided in git commit doesn’t match the signature generated by the public key on the Git server, then it means that the commit was modified by someone else, and therefore it is not valid. Let’s see how it works in practice with pseudocode:

dev = Developer() 
git_server = GitServer()
gnupg = GnuPG()

(private_key, public_key) = gnupg.generate_gpg_key_pair()
dev.set_private_key(private_key)
dev.send_to(git_server).key(public_key)
git_server.set_public_key(public_key)

commit = dev.commit("Hello World")
signature = dev.sign(commit)  // "abc123"
dev.send_to(git_server).commit(commit).signature(signature)

calculated_signature = git_server.get_signature(commit) // "abc123"
if calculated_signature == received_signature:
    git_server.accept(commit)
else:
    git_server.reject(commit)

Generating GPG keys

To use GPG on Ubuntu 22.04, you need to install gnupg package with the following command:

sudo apt install gnupg

after that we are ready to generate a new GPG key with command

gpg --full-generate-key

The cli will ask you a few questions

Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)

Select 1 for RSA and RSA (default) as it is the most popular option

What keysize do you want? (2048)

Select 4096 because it is the most secure option

Key is valid for? (0) 

Select 1y (it means that the key will expire in 1 year)

Is this correct?

Type y and press Enter

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name:

Type your name and press Enter

Email address:

Type your email address and press Enter

Comment:

Type your comment and press Enter

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?

Type O and press Enter

You need a Passphrase to protect your secret key.
Enter passphrase:

Type your passphrase and press Enter. This passphrase is used to protect your private key, and you will be asked to provide it every time you will sign or encrypt anything. So make sure that it is strong enough.

Repeat passphrase:

Type your passphrase again and press Enter

We need to generate a lot of random bytes. 
It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; 
this gives the random number generator a better chance to gain enough entropy.

Move your mouse randomly and then type y and press Enter

gpg: key XXXXXXXX marked as ultimately trusted

This means that you trust this key to be used for signing

gpg: revocation certificate stored as '/home/user/.gnupg/openpgp-revocs.d/XXXXXXXX.rev'

This means that the revocation certificate was generated successfully

public and secret key created and signed.

This means that the public and private keys were generated successfully. Great, now we have a GPG key pair. Let’s see our GPG keys:

gpg --list-secret-keys --keyid-format=long

The output should look like this:

/home/stefan/.gnupg/pubring.kbx
-------------------------------
sec   rsa4096/EF1438B66E632694 2023-11-29 [SC] [expires: 2024-11-28]
9951D14964BE4BE94BB09192EF1438B66E632694
uid                 [ultimate] Stefan Kecskes (GPG is cool) <info@skey.uk>
ssb   rsa4096/ED96BCFBE0D4584B 2023-11-29 [E] [expires: 2024-11-28]

Exporting GPG public key

Amazing, our key is ready waiting for us to export it. To get the GPG public key I will need the KEY_ID, which is the part after / from the sec line, so in my case it is EF1438B66E632694. Now we can export the public key with the following command:

gpg --armor --export EF1438B66E632694

The output will be the public key in ASCII format, which can be shared with others and also added to GitHub, GitLab, etc. To set GPG key on GitHub, you need to go to GitHub website and into Settings -> SSH and GPG keys -> New GPG key and paste the public key that you exported.

GUI for GPG

There is also a graphical user interface for GPG, that is called Seahorse. Seahorse is a graphical GNOME application for managing encryption keys (SSH, GPG, etc.) and passwords. We will not use it in this blog post, but it is good to know that it exists. so if you prefer that, you can install seahorse package with the following command:

sudo apt install seahorse

Note: seahorse is installed by default on Ubuntu. It is called “Passwords and Keys” in Ubuntu.

Transfer GPG keys to another machine

The private key is stored in ~/.gnupg/private-keys-v1.d/ folder and should never be shared with anyone, but you might want to use the same GPG key on another machine, so that you can commit changes to git from your laptop and desktop and the Git server will still be able to validate your commit signatures. To do that, you need to export the private key with the following command:

gpg --export-secret-keys --armor EF1438B66E632694 > private.key

and then import it on another machine with the following command:

    gpg --import private.key

Set GPG key to work with Git

There are two ways of setting GPG key to work with Git. The first way is to set it globally and the second way is to set it per repository.

In most cases you will have only one GPG key, so you can set it globally with the following command:

git config --global gpg.program gpg
git config --global commit.gpgsign true
git config --global user.signingkey EF1438B66E632694

The first command will set the GPG program to gpg and the second command will enable GPG signing for every repository on your computer and the third command will set the GPG key that should be used by Git client on your computer globally. This is helpful, so that you don’t have to set it manually for every repository, you don’t have to remember the GPG key ID and you also don’t have to add -S flag to every git commit command. After that, your GPG key will be automatically used to sign commits and tags.

If you have multiple GPG keys in your system, then you can override the global setting with GPG key per repository. I use it all the time because I work on multiple projects and I have different GPG keys for work and personal projects. My personal GPG key is set globally and I override it for work projects. To do that, you need to go into the work repository folder and set the GPG key per repository with the following command:

git config user.signingkey YOUR_WORK_GPG_KEY_ID

See, that this time we didn’t use --global flag, so the GPG key will be applied to .gitconfig for this repository only. This setting is actually stored in .git/config file in the repository folder, and you can edit the file manually if you want to or use the above command to set it. I prefer to use the above command, because it is less error-prone.

If we wouldn’t set the commit.gpgsign to true, we would have to use the -S flag with every git commit command, like this:

git commit -S -m "My commit message"
git tag -s v1.0.0 -m "My tag message"

but because we set commit.gpgsign to true, we don’t have to use -S flag to sign commits, and we continue to use git commit and git tag commands as usual.

If you forgot to sign the last commit, you can sign it with the following command:

git commit --amend -S --no-edit

and if you already pushed it to the remote repository, you will need to force push it with the following command:

git push --force

But be careful with force pushing, because it can cause problems to your colleagues who already pulled the changes from the remote repository. Please, avoid force pushing if possible.

Conclusion

That’s it for today. In this blog post we learned how to generate GPG keys, how to export/import them and how to use them with Git. I hope that you found this blog post useful and that you will start using GPG keys for signing your commits and tags. Thank you for reading and if you have any questions, please send me a message.

References