Signing GitHub Commits With A Passphrase-protected Key and GPG2

GitHub recently added support for signed commits. The instructions for setting it up can be found on their website and I do not intend to rehash them here. I followed those instructions and they work splendidly. However, when I set mine up, I had used the version of GPG that came with my Git installation. A side effect I noticed was that if I were rebasing some code and wanted to make sure the rebased commits were still signed (by running git rebase with the -S option), I would have to enter my passphrase for the GPG key for every commit (which gets a little tedious after the first five or so).

Shows some commits on GitHub with the Verified indicator showing those that have been signed
How GitHub shows signed commits

Now, there are a couple of ways to fix this. One is easy; just don't use a passphrase protected key. Of course, that would make it a lot easier for someone to sign commits as me if they got my key file, so I decided that probably was not the best option. Instead, I did a little searching and found that GPG2 supports passphrase protected keys a little better than the version of GPG I had installed as part of my original git installation.

Using the GPG4Win website, I installed the Vanilla version1. I then had to export the key I had already setup with GitHub from my old GPG and import it into the new. Using gpg --list-keys, I obtained the 8 character ID for my key (the bit that reads BAADF00D in this example output):

gpg: WARNING: using insecure memory!
gpg: please see http://www.gnupg.org/documentation/faqs.html for more information
/c/Users/Jeff/.gnupg/pubring.gpg
--------------------------------
pub   4096R/BAADF00D 2016-04-07
uid                  Jeff Yates <jeff.yates@example.com>
sub   4096R/DEADBEEF 2016-04-07

Which I then used to export my keys from a Git prompt:

gpg -a --export-secret-keys BAADF00D > privatekey.txt
gpg -a --export BAADF00D > publickey.txt

This gave me two files (privatekey.txt and publickey.txt) containing text representations of the private and public keys.

Using a shell in the GPG2 pub folder ("C:\Program Files (x86)\GNU\GnuPG\pub"), I then verified them (always a good practice, especially if you got the key from someone else) before importing them2:

> gpg privatekey.txt

And rather than give me details of the key, it showed me this error:

gpg: no valid OpenPGP data found.
gpg: processing message failed: Unknown system error

What was going on? I tried verifying it with the old GPG and it gave me a different but similar error:

gpg: WARNING: using insecure memory!
gpg: please see http://www.gnupg.org/documentation/faqs.html for more information
gpg: no valid OpenPGP data found.
gpg: processing message failed: eof

I tried the public key export and it too gave these errors. It did not make a whole heap of sense. Trying to get to the bottom of it, I opened the key files in Visual Studio Code. Everything looked fine until I saw this at the bottom of the screen.

Encoding information from Visual Studio Code showing UTF16
Encoding information from Visual Studio Code

It turns out that Powershell writes redirected output as UTF-16 and I had not bothered to check. Thinking this might be the problem, I resaved each file as UTF-8 and tried verifying privatekey.txt again:

sec  4096R/BAADF00D 2016-04-07
uid                            Jeff Yates <jeff.yates@example.com>
ssb  4096R/DEADBEEF 2016-04-07

Success! Repeating this for the publickey.txt file gave the exact same information. With the keys verified, I was ready to import them into GPG2:

> gpg --import publickey.txt
gpg: WARNING: using insecure memory!
gpg: please see http://www.gnupg.org/documentation/faqs.html for more information
gpg: key BAADF00D: public key "Jeff Yates <jeff.yates@example.com>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
> gpg --import privatekey.txt
gpg: WARNING: using insecure memory!
gpg: please see http://www.gnupg.org/documentation/faqs.html for more information
gpg: key BAADF00D: secret key imported
gpg: key BAADF00D: "Jeff Yates <jeff.yates@example.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

With the keys imported, I ran gpg --list-keys to verify they were there and then made sure to delete the text files.

Finally, to make sure that Git used the new GPG2 instead of the version of GPG that it came with, I edited my Git configuration:

> git config --global gpg.program "C:\Program Files (x86)\GNU\GnuPG\pub\gpg.exe"

Now, when I sign commits and rebases, instead of needing to enter my passphrase for each commit, I am prompted for the passphrase once. Lovely.

  1. You could also look at installing the command line tools from https://www.gnupg.org/download/ though I do not know if the results will be the same []
  2. Note that I am not showing the path to the file here for the sake of brevity, though I am sure you get the idea that you'll need to provide it []