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.
Key generation ¶
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:
addkey (11) ECC (set your own capabilities) (S) Toggle the sign capability (A) Toggle the authenticate capability (Q) Finised (1) Curve 25519
The key should now look something like this:
sec ed25519/7880733B9D062837 created: 2022-07-10 expires: 2023-07-10 usage: SC trust: ultimate validity: ultimate ssb cv25519/69799729DDF6BDD3 created: 2022-07-10 expires: 2023-07-10 usage: E ssb ed25519/F32635370237664C created: 2022-07-10 expires: 2023-07-10 usage: A [ultimate] (1). Hugo Osvaldo Barrera <email@example.com>
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
admin passwd 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 keytocard key 1 # de-selects the first subkey key 2 # selects the second subkey keytocard key 2 # de-selects the second subkey key 0 # selects the master key keytocard
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.
Extra steps ¶
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:
trust 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.
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 firstname.lastname@example.org 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.
Have comments or want to discuss this topic?
Send an email to ~email@example.com (mailing list etiquette)
Want to sponsor my work? See this page for details.