Saturday August 7 2021

Last updated: Thursday Dec 29 2022

Setting up GnuPG/PGP on a Yubikey 5

I recently purchased a few Yubikey 5s with the intention of using them to strengthen my password management. Since I use pass that means using them as an OpenPGP smart card.

This is normally known to be quite the headache and involved process. This guide is an attempt to solve that problem, it’s not an end-all super detailed walk you through every step of the process kind of guide. It’s here to quickly point you in the right direction and hopefully doing this on your own if you’re already somewhat comfortable with these tools.

Do not blindly copy and paste anything from here. Figure out what you are doing as you go along.

Offline key generation

There’s not much sense in taking all of the care in creating these OpenPGP smart carts if we’re not going to take the care to avoid exposing the key to our systems. To do this, we’re going to use a live Linux distro.

If you wish to save your keys to a storage medium to make more copies of your hardware keys in the future you should have two flash drives on hand, one for the live image, and one for the key backups.

I have an extra computer without any wireless capabilities I used for this purpose. Most laptops allow you to unplug the wireless card if you take the back cover open. If you’re less paranoid, you can avoid connecting to any WiFi during this process.

I decided to use the Fedora Security Lab mostly because I trust them a bit more than anything based on Debian.

Don’t forget to follow their steps to verify the image you just downloaded.

Double check the system clock. Live Linux distros sometimes end up with some messed up time, so it’s worth double checking since those timestamps are going to be in your key and going to dictate whether or not it’s valid on a normal system. ( I.e. if you generate a key form the “future” GPG will complain loudly about it and refuse to work )

Talking to the Yubikey

To see if you have connectivity use gpg --card-status, if successful it should look something like this on a fresh yubikey:

[root@localhost-live ~]# gpg --card-status
gpg: directory '/root/.gnupg' created
gpg: keybox '/root/.gnupg/pubring.kbx' created
Reader ...........: XXXX:XXXX:X:0
Application ID ...: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 14971821
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Generating the key

Use gpg --full-gen-key:

[root@localhost-live ~]# gpg --full-gen-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

********** TRIMMED OUTPUT **********

gpg: key DC34AE68B21ED661 marked as ultimately trusted
gpg: directory '/root/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as
'/root/.gnupg/openpgp-revocs.d/A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661.rev'
public and secret key created and signed.

pub   rsa4096 2021-08-07 [SC]
      A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661
uid                      Mitchell Riedstra <mitch@riedstra.dev>
sub   rsa4096 2021-08-07 [E]

[root@localhost-live ~]#

Exporting the key

GnuPG will remove the key from your disk when you use keytocard, to facilitate backups as well as installing the key to multiple cards it’s worthwhile to export it now.

[root@localhost-live pgp]# gpg --armor --export A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661 > public.asc
[root@localhost-live pgp]# gpg --armor --export-secret-keys A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661 > private.asc
[root@localhost-live pgp]#

Note that your exported private key is saved with the passphrase you used during generation unless you’ve explicitly removed it with --edit-key and passwd.

Optionally, backup the key to a flash drive

If you have a separate flash drive, you can mount it and copy over the private.asc file we created above. Be aware though, anyone who acquires it or makes a copy and knows the passphrase can make as many copies as they want. There’s also the risk of the passphrase being broken.

Optionally, you can encrypt the key file with the PGP key itself, meaning in order for anyone to decrypt it they’ll have to possess your Yubikey. In order to be able to use the decrypted key, the passphrase protecting it as well.

[root@localhost-live pgp]# gpg -e --armor -r \
	A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661 < private.asc > private-enc.asc

Setting the pin on the Yubikey

Pretty straightforward, run gpg --edit-card followed by admin and passwd, then follow the prompts to set the PIN and the Admin PIN. The defaults are 123456 and 12345678 respectively.

Despite being a “PIN” you can use other chars as well.

Loading the key on the Yubikey

We’re going to run through --edit-key here, and run the keytocard three times. Once for each one of the parts of our key.

In the example below DC34AE68B21ED661 is the first selected key, we’ll run keytocard once for the Signature key and then again for the Authentication key.

After that we’ll run key 1 which will select the next key down, 1D7C1F8E11C42189 in this example. We’ll send this to the card as well as the encryption key.

[root@localhost-live pgp]# gpg --edit-key A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  rsa4096/DC34AE68B21ED661
     created: 2021-08-07  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/1D7C1F8E11C42189
     created: 2021-08-07  expires: never       usage: E
[ultimate] (1). Mitchell Riedstra <mitch@riedstra.dev>

gpg> keytocard
Really move the primary key? (y/N) y
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

sec  rsa4096/DC34AE68B21ED661
     created: 2021-08-07  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/1D7C1F8E11C42189
     created: 2021-08-07  expires: never       usage: E
[ultimate] (1). Mitchell Riedstra <mitch@riedstra.dev>

gpg> keytocard
Really move the primary key? (y/N) y
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 3

sec  rsa4096/DC34AE68B21ED661
     created: 2021-08-07  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb  rsa4096/1D7C1F8E11C42189
     created: 2021-08-07  expires: never       usage: E
[ultimate] (1). Mitchell Riedstra <mitch@riedstra.dev>

gpg> key 1

sec  rsa4096/DC34AE68B21ED661
     created: 2021-08-07  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb* rsa4096/1D7C1F8E11C42189
     created: 2021-08-07  expires: never       usage: E
[ultimate] (1). Mitchell Riedstra <mitch@riedstra.dev>

gpg> keytocard
Please select where to store the key:
   (2) Encryption key
Your selection? 2

sec  rsa4096/DC34AE68B21ED661
     created: 2021-08-07  expires: never       usage: SC
     trust: ultimate      validity: ultimate
ssb* rsa4096/1D7C1F8E11C42189
     created: 2021-08-07  expires: never       usage: E
[ultimate] (1). Mitchell Riedstra <mitch@riedstra.dev>

gpg>

Checking your work

If you saved an encrypted copy of your key earlier checking this is somewhat easy.

[root@localhost-live pgp]# rm -rf ~/.gnupg/
[root@localhost-live pgp]# pkill -9 gpg-agent
[root@localhost-live pgp]# gpg --import < public.asc
[root@localhost-live pgp]# gpg -K
[root@localhost-live pgp]# gpg --card-status

********** TRIMMED OUTPUT **********

[root@localhost-live pgp]# gpg -K
/root/.gnupg/pubring.kbx
------------------------
sec>  rsa4096 2021-08-07 [SC]
      A1DC3429557CF8E4DBE9E03CDC34AE68B21ED661
      Card serial no. = 0006 14971821
uid           [ unknown] Mitchell Riedstra <mitch@riedstra.dev>
ssb>  rsa4096 2021-08-07 [E]

[root@localhost-live pgp]#

Running through the commands you see above, we’re going to remove our entire GnuPG folder, keys and all. Make sure the agent isn’t running. Import our public key, and then check and make sure we don’t have any secret keys.

You’ll note that after we run gpg --card-status, gpg -K shows our secret key on the card.

Now we can try and decrypt that file we made earlier:

[root@localhost-live pgp]# gpg -d < private-enc.asc >/dev/null
gpg: encrypted with 4096-bit RSA key, ID 1D7C1F8E11C42189, created 2021-08-07
      "Mitchell Riedstra <mitch@riedstra.dev>"
[root@localhost-live pgp]#

You should be prompted for the card’s pin at this point, after which you’ll get a message similar to what’s above if successful.

This Yubikey is now all set, if you have multiple keys, head back up to the setting the pin step and follow along until you get back here.

Now that you’re done, power off the system, head over to another machine, import the public key and send it to a keyserver. Use --edit-card to fill out the url field, once you’ve done that you can more easily bring the card to new computers by running --edit-card followed by fetch to automatically download the PGP key.

Yubikey usage overview

The general process set up your Yubikey turned smart card is to run:

$ gpg --edit-card
> fetch
> quit
$ gpg --edit-key <KEYID>
> trust
> 5
> q

This will fetch the public side of the key, and automatically associate the private side with your Yubikey. Setting the trust to ultimate also may be necessary for some operations depending on your configuration.

Using on Windows

Install GPG4Win. It should pick up on the smart card with only a few clicks in the GUI. Once installed you’ll need to create and/or edit %APPDATA%\gnupg\gpg-agent.conf and add the line:

enable-putty-support

If you already had kelopatra running, you’ll probably need to quit and start it back up again. Once that’s been done it should work out of the box with PuTTY.

If you’re using git on windows, you may need to set the environment variable GIT_SSH to plink.exe in order for it to pick up on your key.

With plink it will pick up on your configuration aliases similar to ~/.ssh/config on Linux. So you can have a server named git and clone quickly with git clone git:mitch/<repo> once you’ve saved that.

Don’t forget to import the public key, decryption may throw odd errors about not having a secret key if you don’t.

Using on MacOS, Linux and BSD

Mac OS

I just installed the GPGTools GPG Suite and it worked out of the box. Setup for SSH is the same is other Unix like systems and is covered below.

Fedora

Works out of the box.

RHEL 9 / Rocky 9 / Oracle Linux 9

# yum -y install pcsc-lite
# systemctl start pcscd

Ubuntu

# apt install scdaemon

Alpine Linux

# apk add gnupg gnupg-scdaemon
# adduser <your_username> gnupg

Void Linux

In addition to GPG and whatever pinentry program you prefer:

# xbps-install gnupg2-scdaemon pcsc-ccid
# ln -s /etc/sv/pcscd /var/service/

OpenBSD

$ doas pkg_add -r gnupg pcsc-tools
$ doas rcctl enable pcscd
$ doas rcctl start pcscd

SSH with GPG on Unix-like systems

In your ~/.gnupg/gpg-agent.conf file:

enable-ssh-support

Now let’s get the “keygrip” for the list of allowed ssh keys:

$ gpg -K --with-keygrip 1462E2BFDAC8B6988D6BF6AA0F8720729950477B
sec>  rsa4096 2021-08-06 [SC]
      1462E2BFDAC8B6988D6BF6AA0F8720729950477B
      Keygrip = 3859CC47647442EBB283F67F3E7826A8C5EF287A
      Card serial no. = 0006 13145275
uid           [ultimate] Mitchell Riedstra <mitch@riedstra.dev>
ssb>  rsa4096 2021-08-06 [E]
      Keygrip = B7528476E3A844D29FD82A0193A7B362585D7C4F

Then in ~/.gnupg/sshcontrol file:

B7528476E3A844D29FD82A0193A7B362585D7C4F

In your ~/.profile or (probably) ~/.bashrc

export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"

Listing the public side of your SSH key

Pretty straightforward:

$ ssh-add -L