I’ve written recently on how I use a Yubikey as a hardware security token for two factor authentication.
One item I was missing was GPG, and this was mostly because setting up GPG is a bit tricker to set up and I simply hadn’t had the time. My previous key recently expired, so this is a good time to address that.
This article explains the basics of how Yubikey + GPG works, and how to get started.
First glance at the device
yubikey-manager is required. When installed correctly, running
ykman info should print the following:
Device type: YubiKey 5C NFC
Serial number: 12345678
Firmware version: 5.2.7
Form factor: Keychain (USB-C)
Enabled USB interfaces: OTP, FIDO, CCID
NFC transport is enabled.
Applications USB NFC
FIDO2 Enabled Enabled
OTP Enabled Disabled
FIDO U2F Enabled Enabled
OATH Enabled Enabled
YubiHSM Auth Not available Not available
OpenPGP Enabled Enabled
PIV Enabled Enabled
There’s three “Enabled USB interfaces” here:
- OTP: This is the basic OTP functionality of the yubikey. Use with
compatible applications (e.g.:
Yubikey Authenticator) it can generate classic OTP tokens. These are the same kind used by mainstream websites like Twitter and alike. Regrettably, there is little tooling around this functionality, it seems to be tied to this specific brand only, and it can only be used with the mentioned app, which is not very convenient, and somewhat buggy.
- FIDO: This is basically
webauthn, and it’s basically the second factor when logging into website where I’m prompted for “tap your key to log in”. See my previous article for details on this.
- CCID (chip card interface device): This interface allows the Yubikey to be used as a smartcard. The protocol is pretty standard and is used by devices which provide on-device keys for things like signing or encrypting. This is what I’ll be using for GPG. Good to see that it’s on by default.
For GPG usage, the Yubikey works as a “smart card”, so I will use this term in future when referring to it. I will use the term “key” to refer to actual GPG keys (e.g.: the blob of bytes that is a key).
Preparing a GPG key
It’s possible to create the key on-device, which is the safest approach, given it cannot be extracted. However, I want to set up a backup device, which needs to have the same key material. Because I can’t extract the key it from one smart card, I can’t put it in the backup one, so I need to generate the key on a computer and the import it into both smart cards. To do this, it’s best to do it on an offline machine. I booted a separate laptop with no network to do this. For full-on paranoid mode, use a Tails live image on a computer with no storage or wireless adapters.
Usually, generating the private key only needs to be done once. On later years, it’s possible to simply extend the expiration of the existing key1.
Plug in the smart card and then generate a key with:
gpg --expert --full-gen-key
I want an ed25519 key, so pick
ECC and ECC first, then
Curve 25519. Real
name is not really important, but I just put my own. The key is going to be
tied to my identity anyway. I’m not 100% sure how important email is here, but
also provided the right one – this’ll make the public key easier to map to my
The output will include a line that looks something like this:
gpg: key 0x7880733B9D062837 marked as ultimately trusted
The key’s id in this case is
GPG keys can have one or more subkeys. These are additional key pairs that are tied to the master key and can be stored, revoked and expire separately. Each subkey can have one or more of the following capabilities:
S, signing: Used to sign arbitrary data.
C, certification: Used to sign other keys. We usually call this “key signing”.
E, encryption: Used to encrypt data.
A, authentication: This seems to be used by SSH. It’s relatively new, and I’m not 100% familiar.
Inspecting the key with
gpg --list-secret-keys indicates that the generated
key has an
SC subkey (Signing, Certification) and an
E subkey (Encryption).
Let’s create an Authentication key too with
gpg --expert --edit-key $KEYID:
(11) ECC (set your own capabilities)
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(1) Curve 25519
The key should now look something like this:
created: 2022-07-10 expires: 2023-07-10 usage: SC
trust: ultimate validity: ultimate
created: 2022-07-10 expires: 2023-07-10 usage: E
created: 2022-07-10 expires: 2023-07-10 usage: A
[ultimate] (1). Hugo Osvaldo Barrera <firstname.lastname@example.org>
Exporting the keys
At this point, the keys need to be exported. When the keys are later imported by GPG into the primary smart card, GPG will delete its copy of the secret key material. I’ll then re-import these for the second/backup smart card:
gpg --armor --export-secret-keys $KEYID > master.key
gpg --armor --export-secret-subkeys $KEYID > subkeys.key
gpg --armor --export $KEYID > public.key
Make sure this is an ephemeral filesystem, it’s a bad idea to keep these files around after finishing this whole process.
public.key MUST be copied onto any system so that GPG can
identify the private key on the smart card. Failing to save the public key
will result in the smart card’s private key being unusable.
Importing the keys into the Yubikey
GPG’s smart card support works here:
First of all, let’s change the admin PIN (default is
3 - change Admin PIN
Next, let’s change the user PIN (default is
1 - change PIN
Now let’s move the keys onto the device:
key 1 # selects the first subkey
key 1 # de-selects the first subkey
key 2 # selects the second subkey
key 2 # de-selects the second subkey
key 0 # selects the master key
Importing the keys into the BACKUP Yubikey
Importing the keys into the PRIMARY Yubikey has deleted them from GPG’s local
storage, so they need to be imported again. It’s best to completely delete
.gnupg to avoid any inconsistencies left over from the previous key too.
rm -rm ~/.gnupg # You're using an ephemeral system, right!?
gpg --import master.key
gpg --import subkeys.key
gpg --import public.key
The steps from the previous section need to be repeated, but with the backup card plugged in this time.
Finally, configure the Yubikey to require a touch to sign or decrypt anything. This avoids any random program from using the key without manual approval.
I couldn’t do this on the air-gapped machine that did the key generation,
because it need
ykman. Doing this on the daily desktop is fine:
ykman openpgp keys set-touch aut off
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch enc on
Import the public key here too, so the smart card’s key can be used on this device:
gpg --import public.key
It’s a good idea to upload the public key to a keyserver at this point, so it cannot be lost. It is a public key after all.
Finally, mark the key as trusted with
gpg --edit-key 0x7880733B9D062837:
5 = I trust ultimately
Extending the expiration date
The key will expire in a year, so its expiration needs to be extended around that time. I’ll try to publish a follow-up on how to extend the key expiration when the time comes.
Update 2023-07-13: See Extending an expired GPG key.
Configuring git to use this key
When signing tags,
git will use a key matching the email configured, so if
the right email is configured via
git config --global user.email email@example.com then git will use the correct key.
If previous keys exist, it’s best to explicitly specify the key via:
git config --global user.signingkey 0x9D062837
I mention Yubikey a lot here and in other articles. This is because I own this particular brand, however, there are several alternatives which can be considered. I’ve heard that the Nitrokey 3 is a good FOSS alternative, but have not had a chance to try it out. Researching alternatives is an exercise for the reader.
Want to sponsor my work? See this page for details.