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