Creating an offline GnuPG master key with Yubikey-stored subkeys

Aug 11, 2016

Two yubikeys

I recently noticed that I’d accidentally lost my previous GPG private key — whoops. It was on a drive that I’d since formatted and used for a fair amount of time, so there’s no hope of getting it back (but, on the plus side, there’s also no risk of anyone else getting their hands on it). I could have created a new one in a few seconds and been done with it, but I decided to treat it as an exercise in doing things properly.

Background: GPG? Yubikey?

GPG or GnuPG is short for Gnu Privacy Guard, which is a suite of applications that provide cryptographic privacy and authentication functionality. At a basic level, it works in a similar way to HTTPS certificates: each user has a public key which is shared widely, and a private key that is unique to them. You can use someone else’s public key to encrypt messages so only they can see them, and use your own private key to sign content so that others can verify it came from you.

A Yubikey is a small hardware device that offers two-factor authentication. Most Yubikey models also act as smartcards and allow you to store OpenPGP credentials on them.

Introducing subkeys

GnuPG supports subkeys, which provide fairly significant security advantages. Instead of just having a single public and private key, you have a master pair and then any number of subkey pairs. The subkeys are automatically associated with the master key, but they can be revoked independently.

Having a master key fall into the wrong hands is a problem — you have to revoke the whole thing (assuming you have access to a revocation certificate) and start again, convincing everyone else that your new key is the “real” you. With subkeys, you can issue a revocation signed with your master key and then sign some new subkeys. There’s no loss of trust, and as long as people refresh your key from a keyserver, everything carries on as normal.

The other advantage to using subkeys is that you can keep the master key elsewhere. It doesn’t need to be routinely accessible, and using it doesn’t require access to the Internet. The master key is kept offline, significantly reducing the risk of anything bad happening to it.

Setting up a secure environment

My main desktop runs Windows, and most of my other devices are work ones which come with automatic backups and network mounts that I don’t fully grok. Neither of those is a particularly good option if I want to do something security sensitive.

Tails is the defacto choice for a secure, live linux system, so I went through their installation process and eventually ended up with a USB drive that can boot into Tails. The installation process from Windows is slightly convoluted, as it involves creating a bootable Tails image, then booting to that and using the Tails installer to create the real image on a different drive. If you’re starting on a Linux box, you can just use the Tails installer directly instead of doing the two-drive shuffle.

Just to be completely paranoid, I disconnected my PC from the network before booting Tails. This is known as air-gapping, and is done to eliminate the possibility of a remote attacker doing something to your system. Ideally the machine would never have been connected to the network, but I didn’t happen to have an unused machine laying around.

The final thing I needed was a secure place to store my master key. I opted for an IronKey — a hardware-encrypted USB drive that self-destructs if there are too many unsuccessful attempts to access it. It works out-of-the-box on both Windows and Linux, presenting a small unencrypted drive with software to run to interact with the secure partition.

Creating the keys

Now I had a nice over-the-top setup it was time to actually the keys. There is an excellent guide by Simon Josefsson that walks through the entire process of creating the master key, creating three subkeys, and then transferring them to a Yubikey.

The only point where I had to deviate from Simon’s guide was setting the machine up to work with the Yubikeys. I was setting up two keys (a nano and a neo), and one just worked out of the box with the version of libykpers-1-1 that was in Tails’ apt repository. The other needed a slightly newer version but that was also available in apt and can be selected by specifying the version manually as pointed out in this GitHub issue. The version numbers have since changed, but apt-policy makes it easy to figure out what’s needed. As I was using an air-gapped system this process was a bit more complicated than it sounds, involving several USB drive transfers.

After finishing the guide, I had my master key and a pre-generated revocation certificate stored securely on my IronKey, and the three subkeys stored on each Yubikey. Time to go back to Windows.

GPG, Windows and SSH

Now with the IronKey disconnected and the master key out of harms way, it’s time to go back to Windows. I downloaded the GnuPG Modern distribution and followed the instructions at the end of Simon’s guide to import my public key and make GPG aware of the subkeys on the Yubikey. After that Enigmail was able to sign and encrypt e-mail in Thunderbird.

XKCD: Wisdom of the ancients
XKCD #979: Wisdom of the ancients

Next up, I enabled PuTTy support and started the GPG agent, as documented over on Yubico’s site. This allows you to use the authentication GPG key to authenticate SSH sessions from PuTTy. To find the SSH key you need to add to .authorized_keys, simply run gpg --export-ssh-key. At first I could SSH into a host but not use agent forwarding. After lots of unsuccessful Googling, I realised that GPG couldn’t access the key anymore locally. Another quick search and I found a forum thread where someone had the same issue and found it was a problem with exclusive access to the card. They even passed on their wisdom and updated the thread with a solution, which got everything working for me.


I have a Ubuntu image running inside VMWare on my desktop that I use for most development activities. I’m unlikely to want to sign e-mail, but I probably want to sign commits (especially now that GitHub exposes verified signatures).

To do that I need to pass the Yubikey through to the virtual machine. In its default configuration, VMWare recognises the Yubikey device but doesn’t pass it through correctly. You need to configure it to allow HIDs, after which gpg --card-status starts working from the VM.

Unfortunately passing through the device makes it exclusively available to the VM so the host OS can no longer use it. As I have two Yubikeys, I just configured VMWare to ignore the nano key that’s always plugged in, and pass through the neo when I plug that in. While swapping the configuration around, I found out that GPG remembers the ID of the smartcard that store credentials - if you swap the two keys with identical subkeys it demands the other one is reinserted. You can get rid of the references to the previous card using gpg --delete-secret-keys.

In conclusion…

… I have a new PGP key you can use to verify things I sign or encrypt messages to me.