Why Is It Important?

A strict policy of signing all commits could prevent someone committing as you (perhaps with GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL) from fully blaming you for a change. You can verify signatures using git log:

$ git log --show-signature
commit e80c6f611429db4e437a377d7b1b76c167594dcd
gpg: Signature made Sat Apr  9 11:19:58 2016 WEST using RSA key ID 6C1EEE05
gpg: Good signature from "Rui Afonso Pereira <rap_2@fake.com>" [ultimate]
gpg:                 aka "Rui Afonso Pereira <rap@fake.com>" [ultimate]
Author: Rui Afonso Pereira <rap@fake.com>
Date:   Sat Apr 9 11:19:40 2016 +0100

This article shows how to use public-key cryptography to sign git commits.

Getting Started

This article relies on GNU Privacy Guard (GnuPG), which is a tool for secure communication. It is a complete and free implementation of the OpenPGP standard as defined by RFC4880, also known as PGP — short for Pretty Good Privacy.

We shall start by installing the GnuPG tool. On macOS, you can grab it using Homebrew:

brew install gpg2

Furthermore, this binary can be aliased as gpg.

In other *NIX systems, either gpg or gnupg is likely already installed. From this point on, due to the different ways that systems refer to the binary, I’m going to address it as gpg.

Make Your Keys

To use the GnuPG system, you’ll need a public key and a private key, also known together as a keypair. A public key is available to many, whereas the private key must be kept secret. You should never share your private key with anyone, under any circumstances.

If you don’t have an existing keypair, let’s proceed by generating one.

$ gpg --gen-key
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?

Let’s start by pressing Enter and accepting (1) RSA and RSA (default) as default.

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

You should type 4096 here.

Requested keysize is 4096 bits
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)

I do not want to bother with refreshing my key regularly, so mine never expires.

Then, we can move on to the next step.

GnuPG needs to construct a user ID to identify your key.

Real name: Rui Afonso Pereira
Email address: rap@fake.com
Comment:
You selected this USER-ID:
    "Rui Afonso Pereira <rap@fake.com>"

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

Now take the time to review the information and press o for Okay.

Finally, GnuPG needs a passphrase to protect the primary and subordinate private keys that you keep in your possession.

You need a Passphrase to protect your private key.

Enter passphrase:

A good passphrase is crucial to the secure use of GnuPG so it should be carefully chosen.

Adding Identities

We shall consider having multiple email addresses: a work email and a personal email. It would be tedious if we had to go through this whole process for each. You can easily edit your key to add another user ID:

$ gpg --edit-key rap@fake.com

Secret key is available.

pub  2048R/6C1EEE05  created: 2016-04-08  expires: never       usage: SC
                 trust: ultimate      validity: ultimate
sub  2048R/8C44BD6A  created: 2016-04-08  expires: never       usage: E
[ultimate] (1)  Rui Afonso Pereira <rap@fake.com>

gpg>

In this prompt, you can type help for more information. To add a new user ID, type adduid.

gpg> adduid
Real name: Rui Afonso Pereira
Email address: rap_2@fake.com
Comment:
You selected this USER-ID:
    "Rui Afonso Pereira <rap_2@fake.com>"

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

You need a passphrase to unlock the secret key for
user: "Rui Afonso Pereira <rap@fake.com>"
2048-bit RSA key, ID 6C1EEE05, created 2016-04-08


pub  2048R/6C1EEE05  created: 2016-04-08  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/8C44BD6A  created: 2016-04-08  expires: never       usage: E
[ultimate] (1). Rui Afonso Pereira <rap_2@fake.com>
[ultimate] (2)  Rui Afonso Pereira <rap@fake.com>

gpg> save

The key now has two UIDs attached.

$ gpg --list-keys 6C1EEE05
pub   2048R/6C1EEE05 2016-04-08
uid       [ultimate] Rui Afonso Pereira <rap_2@fake.com>
uid       [ultimate] Rui Afonso Pereira <rap@fake.com>
sub   2048R/8C44BD6A 2016-04-08

Exporting a Public Key

To add your public key to your development platform, such as GitHub, you must first export it. To do so, paste the text below, substituting in the GPG key ID you’d like to use. In this example, the GPG key ID is 6C1EEE05:

$ gpg --armor --export 6C1EEE05
# Prints the GPG key, in ASCII armor format

You shall now proceed by adding the GPG key to your GitHub account.

Signing Commits

We should now configure git to automatically gpgsign commits. This consists of pointing git to your signing key ID, and then enabling automatic signing of git commits.

$ git config --global user.signingkey <YOUR-SIGNING-KEY-PUB-ID>
$ git config --global commit.gpgsign true

Furthermore, since we are using the gpg2 binary on macOS, we should also tell this to Git:

$ git config --global gpg.program gpg2

Finally, from now on, every commit will be automatically signed. However, it is still required to insert the passphrase every single time.

Automatic Commit Signing on macOS

For security reasons, GnuPG always requires the passphrase every time it needs to sign something. This effectively means that the passphrase is required on every single git commit. If this sounds like a lot of work to you, we can automate the process, in a clear trade-off between security and convenience.

We shall start by installing the following binaries:

$ brew install gpg-agent pinentry-mac

Now navigate to your GPGHOMEDIR, which is $HOME/.gnupg by default, and add the following to your gpg.conf file:

# Uncomment within config (or add this line)
use-agent

# This silences the "you need a passphrase" message once the passphrase
# handling is all set.
batch

While inside the same directory, let’s configure our gpg-agent.conf:

# Enables GPG to find gpg-agent
use-standard-socket

# Connects gpg-agent to the macOS keychain via the brew-installed
# pinentry program from GPGtools. This is the macOS magic, allowing
# the gpg key's passphrase to be stored in the login keychain,
# enabling automatic key signing.
pinentry-program /usr/local/bin/pinentry-mac

Now, it’s time for the real trick in this whole process. For GPG to find the gpg-agent, the later must be running, and there must be an environment variable pointing GPG to its socket. The following will either start gpg-agent or set up the GPG_AGENT_INFO variable if it’s already running. You should add this script to your .bash_profile or .zprofile so that it starts for every shell.

if [[ -f ~/.gnupg/.gpg-agent-info ]] && [[ -n "$(pgrep gpg-agent)" ]]; then
  source ~/.gnupg/.gpg-agent-info
  export GPG_AGENT_INFO
else
  eval $(gpg-agent --daemon --write-env-file ~/.gnupg/.gpg-agent-info)
fi

The high level diagram for the automatic signing is thus git -> gpg -> shell/env variable -> gpg-agent -> pinentry -> keychain.

Automatic commit signing should now be successfully configured.