‹ back home

Using a Yubikey for GPG

2022-07-11 #gpg #open-source #security #yubikey

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:

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 email address.

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 0x7880733B9D062837.

Subkeys

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:

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 <hugo@whynothugo.nl>

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.

Additionally, 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:

gpg --edit-card

First of all, let’s change the admin PIN (default is 12345678):

admin
passwd
3 - change Admin PIN

Next, let’s change the user PIN (default is 123456):

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.

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 hugo@whynothugo.nl 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

Disclaimer

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.


  1. https://web.archive.org/web/20210228103524/https://riseup.net/en/security/message-security/openpgp/best-practices/#use-an-expiration-date-less-than-two-years ↩︎

Have comments or want to discuss this topic?
Send an email to ~whynothugo/public-inbox@lists.sr.ht (mailing list etiquette)

— § —