Signing commits is a great way to add additional level of confidence to your code. This is especially important if you are an open source contributor. By signing your commit you’re saying that it originated from a verified author. This is accomplished by using GPG which is a free encryption and signing tool.

Github has taken this one step farther and now shows signed commit authors with a verified badge. Not only is this a great way to see at a glance if code comes from a trusted source, but the verified badge looks slick.

For the remainder of this tutorial, I’m assuming you don’t have a generated GPG key. If you do (or aren’t sure) you can run the command: gpg --list-secret-keys --keyid-format LONG. This will list out all available GPG keys with public and private keys. If you’d like to use an existing private/public key pair, go ahead and skip down to the step, Finding your GPG key”.

I’m also writing this tutorial from the perspective of a Linux distribution but it should work relatively the same for MacOS (not sure about Windows). I’ve now updated this tutorial for both Linux Debian and MacOSX.

Making sure GPG is installed

GPG (GNU Privacy Guard)

GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories.

https://gnupg.org/

Most variants of Debian should have GPG installed by default. You can check this by running which gpg which should return the installation location.

If it doesn’t then you need to install it with sudo apt-get install gpg for linux or brew install gnupg for homebrew on MacOS.

Generating a new GPG key

Github has some great documentation on generating gpg keys. Unfortunately, the command it recommends for gpg as well as gpg2 doesn’t actually work. What I found works is the following:

gpg --gen-key

UPDATE 2020: Upon running through this process on MacOS, I’ve found that gpg --full-generate-key works appropriately. For Linux based distributions you may still need to use the old –gen-key method above.

We’re going to run through what the process will look like step-by-step.

For the first prompt, just hit enter. This will select the default option. The default of RSA and RSA is what we want.

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)
Your selection?

Github recommends the maximum of 4096 so let’s use that. Type out 4096.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits

Next it will ask you if you want the key to expire. We don’t, so use the default by pressing enter.

Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)

Time for a confirmation. Is the above information correct? Type “y” to continue.

Is this correct? (y/N) y

Now we need to identify ourselves. It wouldn’t make sense for a signed commit to be without identity verification would it?

First step is to enter your full name as you’d like it to appear in the signature. I’ve listed my name out but please use your own (we don’t need any more Josh Frankel’s running amok).

NOTE: This name should match the one stored in your ~/.gitconfig user name key.

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: Josh Frankel

When asked to enter your email address, ensure that you enter the verified email address for your GitHub account. To keep your email address private, use your GitHub-provided no-reply email address.

Github

Next you’ll need to use the same email address that you use for Github. This is really important to ensure that your GPG signature matches the username. I believe it is also how Github determines whether or not to place the verified badge next to your commits.

Email address: josh@tutorial.com

You can leave Comment blank by pressing enter.

Comment:

Next you’ll get an identity confirmation. If all the below is correct then type O for Okay.

You selected this USER-ID:
    "Josh Frankel <josh@tutorial.com>"

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

The next two sections deal with passphrase prompting at the command line for Debian and MacOSX respectively.

Debian GPG passphrase

Now it will prompt you to enter a passphrase which for me looked like this (it may be a command line prompt for other distributions and operating systems):

Access prompt

You’ll be prompted to re-enter this to confirm. Don’t forget this password as it is necessary for signing commits. Think of it as logging into the GPG identity. I saved mine in a password manager.

MacOSX GPG Passphrase

Ideally, you should receive a passphrase prompt at this point. If not then you most likely don’t have one configured. While not really clear, you can add the pinentry-mac program.

brew install pinentry-mac

Next you’ll need to inform gpg-agent of pinentry-mac with the following append command:

echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf

After which if you’re still not able to sign commits you may need to restart your gpg-agent.

pkill gpg-agent
gpg-agent --daemon

I believe that this is due to gpg-agent running as a daemon which means that it can’t prompt you to enter a passphrase. Instead it has to rely on pinentry-mac to send the passphrase prompt.

Finish key generation

After the identity confirmation, the last step is to just do random work on your computer until the GPG key is marked as trusted

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.

gpg: key 3AA5C34371567BD2 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
pub   4096R/3AA5C34371567BD2 2019-06-01
      Key fingerprint = X72D 8C40 70ZB 73A2 EFAE  981C ED3A 021E 7160 C89A
uid                  Josh Frankel <josh@tutorial.com>
sub   4096R/42B317FD4BA89E7A 2019-06-01

That’s it we’ve created our first GPG key. Nice job!

Finding your generated GPG Key

Now we need to grab our generated GPG key and add it to our Github account. We can do this with a couple commands.

gpg --list-secret-keys --keyid-format LONG will list out all your existing keys that have a public and private pairing.

gpg --list-secret-keys --keyid-format LONG

/home/josh/.gnupg/secring.gpg
-----------------------------
sec   4096R/3AA5C34371567BD2 2019-06-01
uid                          Josh Frankel <josh@tutorial.com>
ssb   4096R/42B317FD4BA89E7A 2019-06-01

Now, we need to export our GPG key to be used on Github via the command: gpg --armor --export <key-id>

ASCII vs Raw mode

Since PGP can operate both in ASCII mode and “raw” mode, it’s important to understand when to use which one. When sending something that will be viewed as text (i.e. an email), you should use ASCII mode. On the other hand, when sending a file, you can (and should, it will make the encrypted file smaller) use the default non-ASCII mode. To operate in ASCII mode, use the --armor (or -a) switch.

The Privacy Guide

First, grab the key id which can be found on the sec line after the keysize specification of 4096R. In our case that means using 3AA5C34371567BD2 from above.

Using the key id, we’ll gather the GPG key signature by using the --armor flag which is useful for seeing keys in ASCII format. Without this we won’t be able to copy our key and paste it into Github. Additionally, the --export flag simply exports the key that we specify.

These must be used in tandem in order to gather proper output.

gpg --armor --export 3AA5C34371567BD2

This will output your GPG key to your console. Make sure to copy the entire thing starting with the BEGIN comment and ending with the END comment.

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

Lots of characters and lines of text
Lots of characters and lines of text
Lots of characters and lines of text
-----END PGP PUBLIC KEY BLOCK-----

Let’s add our key to github!

Adding your GPG key to Github

At this point we can follow Github’s guide directly which can be found here. It has some great screenshots and step-by-step instructions. I won’t repeat them here. Once you’re done move onto the next section below for learning how to configure git for automatic signing.

Automatically signing commits in Git

We’ve added our GPG key to Github and the key matches our Github email address, we can configure git to start using it.

This is done by setting our user.signingkey to match our GPG key id.

git config --global user.signingkey 3AA5C34371567BD2

Running this command, will add a new entry to ~/.gitconfig which looks like:

[user]
  email = ...
  name = ...
  signingkey = 3AA5C34371567BD2

Now by default Git will use the specified signingkey for signing commits.

Commits are signed with the format of git commit -S [More on signing commits here]. Now, if you’re like me remembering to type out the -S flag every time is more mental gymnastics that you’re willing to do. Let’s make it automatic so that we don’t even need to think about it. We can do this by specifying a new configuration option of true for commit.gpgsign.

git config --global commit.gpgsign true

If we take a look at our ~/.gitconfig configuration we’ll see another new entry:

[commit]
  gpgsign = true

Now the first time you go to sign a commit it will prompt you with entering a passphrase for the GPG key. We did this above after the identity verification step of generating a GPG key. If you have an existing key you’ll need to enter your created passphrase here as well.

Can’t remember it? You can always create a new one to use by following the Generating a new GPG key section of this tutorial.

My prompt allowed me to remember my passphrase which means I no longer need to enter it. I really like how simple this makes the signing process. Let me know if this isn’t the case for operating system or if you have a different solution.

On the flip-side, there’s something to be said about always entering the passphrase for every commit. This is a bit more secure if you consider the scenario of someone having access to your physical computer then a saved passphrase means it isn’t really you. That being said I’m not too worried about something like this as I’m using several other security features on my laptop.

Enough exposition!

Once you start signing commits you can see the signatures appear next to your commit headers in the log using --show-signature. Neat!

git log --show-signature

# Log output
commit xxxxxxxxx
gpg: Signature made Wed 29 May 2019 09:30:37 AM EDT using RSA key ID 3AA5C34371567BD2
gpg: Good signature from "Josh Frankel <josh@tutorial.com>"
Author: Josh Frankel <josh@tutorial.com>
Date:   Wed May 29 09:30:37 2019 -0400

    Did some stuff and signed a commit. Yay!

And if you push a newly signed commit to Github you can see your verified identity badge in all its glory.

Verified Github commit

The badge is what it’s all about.

Enabling Signing of commits in Sublime Merge or other Git clients

Signing of commits works on the command line beautifully now. We can alleviate any concerns of identity as now all of our commits are signed and verified. But what about external Git tools?

I prefer to use Git with a dedicated client. Having a Git client makes things like adding hunks or fixing conflicts a breeze. There are a ton of different ones out there but I’ve found that Sublime Merge is fast, simple, and integrates well into my existing Sublime Text workflow. (I’m a Sublime fan if you couldn’t tell.) It’s also free to try out just like Sublime

The problem with external tools is that they don’t use the command line and therefore work differently than running git commit -S directly. In other words, when I first tried to commit via Sublime Merge I got the following cryptic error:

Sublime Merge signing error

The tip off point for me was the first line of the error message that mentioned /dev/tty. This is just a reference to your current terminal.

gpg: cannot open tty `/dev/tty': No such device or address

So what can we do here?

Well we can specify that we want to run GPG in no-tty mode. This means that the terminal won’t be used for output or prompting. This is perfect for external applications.

To accomplish this we’ll need to add no-tty to our gpg.conf configuration. By doing so we can keep our process automated and ensure freedom of committing in our favorite external tools.

Open ~/.gnupg/gpg.conf and add no-tty to the end of the file. I added a comment to explain why the option was in there so that future me remembers.

# You may also list arbitrary keyservers here by URL.
#
# Try CERT, then PKA, then LDAP, then hkp://subkeys.net:
#auto-key-locate cert pka ldap hkp://subkeys.pgp.net

# No tty disables terminal prompts allowing Sublime Merge to work
no-tty

Now go back to Sublime Merge and try committing again. Viola, it works!

GPG Hooray Gif

One downside of adding no-tty to our gpg.conf is that trying to run gpg on the command line stops working because we just disabled tty (duh). If for some reason you need to use gpg you can always go back in and comment out the no-tty line on your gpg.conf file. I haven’t been able to find another way sign commits in a Git tool while not disabling gpg for the command line.

Sublime Merge signing success

Conclusion

So to quickly wrap up here’s what we accomplished:

With that git committing and as always I’d love to hear your questions and feedback in the comments. Thanks for reading.

« Previous Post
Testing request specs for invalid formats, Pundit authorization, and ActiveRecord failures
Next Post »
Don't let the null bytes bite

Join the conversation

comments powered by Disqus