This post assumes that you already know what GPG is, and why you want to use it, but you don't have your own set of keys yet. If you are further along (e.g. you already have your own set of keys), you can skip those parts and use your already existing keys instead of generating new ones.
It also assumes that your YubiKey can hold 4096 bits RSA keys. As far as I know, this is true for all of the 5th generation Yubikeys, but it is not true for the YubiKey 4 NFC. If your YubiKey can only hold 2048 bits RSA keys, you will need to generate smaller subkeys in the appropriate step (the master key should still be kept at 4096 bits).
Step 1: Install and set up GPG
If you are using windows, you will need gpg4win
. When I was writing this post, the latest version was 3.1.5.
If you are on linux, you likely already have gpg
installed, but you should check it's version -- e.g. on Ubuntu 16.04 LTS, gpg
is GPG in version 1.4.20. I strongly
recommend getting GPG in version 2.x.x.
If you want to use gpg from within WSL together with YubiKey, you have to install gpg in version 2.x.x inside WSL and
install gpg4win on the side of Windows.
Settings
On Windows, GPG (and related) settings are in AppData/Roaming/gnupg
. On Linux, the settings can be found in ~/.gnupg/
. The settings files themselves are gpg.conf
for the gpg binary, scdaemon.conf
for the SmartCard daemon and gpg-agent.conf
for the gpg-agent.
These will become important later, but if you are on Windows I recommend placing charset utf-8
into gpg.conf
straight away.
Step 2: Generate a new set of keys
After the previous step, you should have GPG set up and ready to generate keys. In my case, the executable name ended up being gpg2
, so I will use that in examples through this post. We will need to generate 3-4 keys, or rather 1 key and 2-3 subkeys. They will be
A master key that should be backed up and kept strictly offline,
An Encryption key, a subkey of the master key that is used for encryption
A Signing key, a subkey of the master key that is used for signing
An (Optional) Authentication key, a subkey of the master key that can be used for SSH or similar
The master key is used for issuing/revoking subkeys and confirming other people's identities. This makes it essentially a person's online identity and thus should be kept securely backed up on offline medium and removed from the PC where it was generated afterwards.
The Encryption and Signing keys are the keys used during everyday activity, and because they are bound to the master key, if they are ever compromised they can be easily revoked, at least as long as you retain control over your master key. The Authentication key is a bit different in that some people think it is pointless (or even that it shouldn't be used), while other people are using it regularly. This post assumes that you will want to use it as well, but you can always just skip those steps.
Generating the master key
Because GPG (at least in the version I have) still defaults to 2048 bits RSA keys and we want to generate 4096 bits RSA keys in the interest of future-proofing, we will have to run GPG with the --full-key-gen
option so we can customize properties of the generated key. GPG will then ask you about various properties of your new key, as you can see below, where gpg X>
means that GPG is asking you about X:
$ gpg2 --full-gen-key
gpg keytype> 1 (RSA and RSA)
gpg keysize> 4096
gpg expiry> 3y
gpg correct> y
gpg real name> ${your name}
gpg email addr> ${your email}
gpg comment>
gpg okay> O
gpg passphrase> ${password to protect this key}
You should always have your key expire eventually -- as long as you have access to it, you can extend its expiration date when it becomes relevant -- and you should also always have a passphrase on your master key. Real name and email address are hopefully self-explanatory, but comments are controversial. Some people hold the opinion that comments are a mistake and should not be used
, while other people see comments as OK, as long as you avoid pointless redundancy (e.g. repeating your email address in the comment). Personally, I don't really care if your user-id looks like this
Sir Mix-A-Lot (I like big butts, and I cannot lie) mixalot@yahoo.com
but I would prefer if it didn't look like this
Jan Novák ( jan.novak@gmail.com
) jan.novak@gmail.com
If the key generation was successful, you should see something like this:
gpg: key 6129F208 marked as ultimately trusted
gpg: directory '/home/xarn/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/xarn/.gnupg/openpgp-revocs.d/1356ED7D349B649687E5D1ECA8F90C096129F208.rev'
public and secret key created and signed.
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: PGP
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2021-11-04
pub rsa4096/6129F208 2018-11-09 [S] [expires: 2021-11-08]
Key fingerprint = 1356 ED7D 349B 6496 87E5 D1EC A8F9 0C09 6129 F208
uid [ultimate] Jan Novák <email@example.com>
sub rsa4096/BF36D4AC 2018-11-09 [] [expires: 2021-11-08]
This tells you that 2 keys were created, a master key with id 6129F208
and an encryption sub key with id BF36D4AC
. For now, the master key id is the important one, the subkey id is not. Also note that both of these ids are in so-called "short" (32 bits) format, which is generally considered insecure
, and either the long (64 bits) key id, or the full key fingerprint should be used instead. To get the long key id, you can pass --keyid-format long
flag to gpg, e.g.:
$ gpg2 --list-keys --keyid-format long
/home/xarn/.gnupg/pubring.kbx
-----------------------------
pub rsa4096/A8F90C096129F208 2018-11-09 [SC] [expires: 2021-11-08]
uid [ultimate] Jan Novák <email@example.com>
sub rsa4096/72FBD8C2BF36D4AC 2018-11-09 [E] [expires: 2021-11-08]
This means we actually want to use A8F90C096129F208
as the master key id during the next steps.
Because we are using newer gpg, something called revocation certificate has also been generated -- a revocation certificate can be uploaded to key servers if you lose control of a key to mark the key as invalid. Obviously, you should back up the revocation certificate somewhere.
Adding more user ids
You might want to have more than one user identity ( userid
) in your master key. This is primarily used to either connect an internet screen name with a real-world name or to add associate more email addresses with your identity. In either case, you can do that by editing the master key:
$ gpg2 --edit-key A8F90C096129F208
gpg> adduid
Real name:
Email address:
Comment:
Generating subkeys
We already have the encryption subkey, now we also have to add the signing and authentication subkeys. This is done by editing the master key in expert mode (note that without --expert
we can't set the key type by ourselves) and using the addkey
command:
$ gpg2 --expert --edit-key 6129F208
gpg> addkey
gpg key-kind> 8 (RSA, own capabilities)
this will open up a menu where you can select what capabilities the new key should have. When using it, keep in mind that "toggle" does mean toggle and that the key starts with the S(ign) and E(ncryption) bits enabled. After you select the right set of capabilities (for this tutorial it means the key has only S or only A capability), you will get to the dialogue for creating keys -- set the size of the key to 4096 bits, expiration date to something reasonable and pick a passphrase again.
After creating both S(ign) and A(uthentication) keys, you should end the editing session and check that your keys were created properly:
gpg> save
xarn@DESKTOP-B2A3CNC:~ :) gpg2 --list-keys --keyid-format long
You should see something like this:
/home/xarn/.gnupg/pubring.kbx
-----------------------------
pub rsa4096/A8F90C096129F208 2018-11-09 [SC] [expires: 2021-11-08]
uid [ultimate] Jan Novák <email@example.com>
sub rsa4096/72FBD8C2BF36D4AC 2018-11-09 [E] [expires: 2021-11-08]
sub rsa4096/94D8AB7C17FCE986 2018-11-09 [S] [expires: 2021-11-08]
sub rsa4096/03F0A89596D8D340 2018-11-09 [A] [expires: 2021-11-08]
i.e. 4 keys, 3 of which are subkeys (marked with sub
) and each of the subkeys has only one of the A/E/S capabilities.
Publishing and backing up the master key
Now that we have our keys ready, it is time to
Publish the public part of the key
Backup and securely store the private parts of the master key
Publishing is easy enough, as long as you find a keyserver that accepts uploads. I had some trouble finding one, but as of the time of writing, fks.pgpkeys.edu
worked:
$ gpg2 --keyserver fks.pgpkeys.edu --send-key A8F90C096129F208
If this succeeds, people can download your key by its id from the public key server pools.
Backing the key up is also relatively simple, the first step is to export it. This is usually done in a format called ASCII armor
, because cat
ing a binary file into your terminal is no fun:
$ gpg2 --armor --export-secret-key A8F90C096129F208 > secret-key.asc
The second step is to securely back up secret-key.asc
-- the usual recommendation is to use 1 or more encrypted USB cards. You should also delete the master key from the computer, but doing so right now would prevent you from moving the subkeys to the YubiKey.
Step 3: Setting up the YubiKey
If you used gpg
inside WSL to generate your keys, you will have to first set up a bridge between gpg-agent
inside WSL and gpg-agent
inside Windows. See "Extras: gpg-agent bridge" for details.
First, we need to check that gpg can see the YubiKey when it is plugged in -- If it does not, check section "Extras: gpg does not detect YubiKey" for help.
$ gpg2 --card-status
Reader ...........: Yubico YubiKey OTP FIDO CCID 0
Application ID ...: D2760001240102010006090200580000
Version ..........: 2.1
Manufacturer .....: Yubico
<snip>
Moving subkeys to the YubiKey
The option to move keys to the YubiKey is once again under --edit-key
:
$ gpg2 --edit-key A8F90C096129F208
gpg> key 1
gpg> keytocard
gpg> <pick the right slot>
gpg> <repeat for the other keys>
gpg> save
keytocard
is a destructive operation and removes the private subkey from the local key store. Now that the subkeys are stored on the YubiKey, you should delete the master key. To do that, you need to know its keygrip:
gpg2 --list-secret-keys --with-keygrip
/home/xarn/.gnupg/pubring.kbx
-----------------------------
sec rsa4096/6129F208 2018-11-09 [SC] [expires: 2021-11-08]
Keygrip = 5436620CA40373692E45B41A7831BEC2ACE624AB
uid [ultimate] aslkdjfs (sjsj)
ssb> rsa4096/BF36D4AC 2018-11-09 [E] [expires: 2021-11-08]
Keygrip = D75AA532535A5E93C90353A3F273C0391FE25516
ssb> rsa4096/17FCE986 2018-11-09 [S] [expires: 2021-11-08]
Keygrip = B14D4AE1729E43DD1E1304C6CA083DA1CA8C6059
ssb> rsa4096/96D8D340 2018-11-09 [A] [expires: 2021-11-08]
Keygrip = 2F35594B4CFBA552BD73E4542065E7988BDE1564
from the listing above, the keygrip of the master key is 5436620CA40373692E45B41A7831BEC2ACE624AB
and it can be deleted via
$ gpg-connect-agent "DELETE_KEY 5436620CA40373692E45B41A7831BEC2ACE624AB" /bye
You can verify that it has been deleted by listing the private keys again -- the master key should have a #
next to it to signify that it cannot be used (the >
next to the subkeys means that they are on the YubiKey).
Change YubiKey's PIN
All YubiKeys share the same factory PIN, 123456, and the same admin PIN, 12345678. Because PIN is what the YubiKey asks for to use a key, you need to change it. You can either do this via Yubico's management utility or via gpg:
$ gpg2 --change-pin
gpg> 1 (change PIN)
gpg> 3 (change admin PIN)
gpg> q
Enable touch protection for GPG keys
I also recommend enabling touch protection for GPG keys on the YubiKey. This means that to use any of the GPG keys on the YubiKey, you need to do 2 things:
Enter the PIN (this is usually cached for a couple of hours)
Touch the YubiKey's touch sensor
The upside is that even in the case a piece of malware manages to get onto your machine and intercepts your PIN, it still will not be able to use the GPG keys on your YubiKey. The downside is that you will be made painfully aware of every single usage of your GPG keys, which can sometimes be annoying.
To enable touch protection, you will need the Yubikey Manager utility
. Once you have it installed, you can enable the touch protection for each key slot separately:
$ ykman openpgp touch sig on
$ ykman openpgp touch aut on
$ ykman openpgp touch enc on
And that is it, now you have your GPG subkeys on the YubiKey, the YubiKey is set up correctly and you should be able to just use it with gpg.
Extras:
git config
Signing git commits seems to be the most common reason for using GPG, so here are the necessary configuration steps.
Tell git to use the right version of gpg. If you are using Git for Windows, it will likely try to use the wrong gpg
binary. Similarly, if you had to install gnupg2
package to get modern gpg, you need to configure git to use gpg2
instead of gpg
binary.
# Windows
git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"
# Linux
git config --global gpg.program gpg2
Tell git which key to use
git config --global user.signingkey <signing-subkey-id>
Tell git to sign each commit
# Add --global if you want to sign every commit of every git tree
# Keep it like this to only enable signing for this specific tree
git config commit.gpgsign true
SSH Authentication via GPG key on YubiKey
This section does not apply to using YubiKey for SSH auth inside WSL.
To use your Auth subkey for SSH auth, you need to enable ssh support in gpg-agent.
To do so, you need to add enable-ssh-support
to gpg-agent.conf
, restart the gpg-agent and set it up to run on login (so that it is available when SSH asks for keys). You also need to set environment variable SSH_AUTH_SOCK
to ~/.gnupg/S.gpg-agent.ssh
.
You can check that everything works with ssh-add -L
-> you should see the auth key from YubiKey in SSH format.
Note that keys in Auth slot on the YubiKey are given to SSH even if they are not in the sshcontrol
file.
Troubleshooting -- GPG does not see the YubiKey
The most common reason for GPG not to see the YubiKey is that there are multiple SmartCard readers in the system. This is caused by the fact that if there is more than one SmartCard reader in the system, scdaemon
just defaults to checking the first one and if that is not a GPG compatible smart card (in our case the YubiKey), it does not try the other ones.
To solve this, you will need to add reader-port <port id or device name>
to scdaemon.conf
. You can find the proper name from scdaemon
logs, because it enumerates all readers, even though it only picks one:
# scdaemon.conf
debug-level guru
log-file <path>
Afterwards, you need to find lines saying "detected reader", specifically the one talking about YubiKey.
# scdaemon.log:
2018-11-06 18:11:14 scdaemon[11056] detected reader 'Alcor Micro USB Smart Card Reader 0'
2018-11-06 18:11:14 scdaemon[11056] detected reader 'Yubico YubiKey OTP+FIDO+CCID 0'
2018-11-06 18:11:14 scdaemon[11056] reader slot 0: not connected
Going by this log you should set reader-port
to Yubico YubiKey OTP+FIDO+CCID 0
.
WSL GPG bridge
Because the only devices visible from WSL are drives, which the YubiKey is not, gpg
inside WSL cannot use the YubiKey directly. Luckily, we can work around that by redirecting requests to gpg-agentunder WSL to the gpg-agent running under Windows.
This can be done by combining the npiperelay
utility on the Windows side with socat
on the Linux side.
Getting npiperelay.exe
There are two ways to get the npiperelay.exe
binary
You can download it from the GitHub releases
Build it yourself
The second option has a small problem in that if you install an older version of go
(e.g. 1.6.2 from apt on Ubuntu 16.04), it will compile fine, but it will fail at runtime, and that the Readme in the linked repo is not updated to reflect the fork's address.
Setting things up
You will need the Windows-side gpg-agent to run right after startup. The easiest way of doing that is to add a shortcut to "C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" /bye
in the %AppData%\Microsoft\Windows\Start Menu\Programs\Startup
folder. You should also set the shortcut to run minimized, to avoid pointless cmd pop-up on login.
On the WSL side, you should add this to ~/.profile
or similar:
#####
## Autorun for the gpg-relay bridge
##
SOCAT_PID_FILE=$HOME/.misc/socat-gpg.pid
if [[ -f $SOCAT_PID_FILE ]] && kill -0 $(cat $SOCAT_PID_FILE); then
: # already running
else
rm -f "$HOME/.gnupg/S.gpg-agent"
(trap "rm $SOCAT_PID_FILE" EXIT; socat UNIX-LISTEN:"$HOME/.gnupg/S.gpg-agent,fork" EXEC:'/mnt/c/PATH_TO_NPIPERELAY/npiperelay.exe -ei -ep -s -a "C:/Users/WINDOWS_USERNAME/AppData/Roaming/gnupg/S.gpg-agent"',nofork </dev/null &>/dev/null) &
echo $! >$SOCAT_PID_FILE
fi
with paths modified accordingly.
WSL SSH bridge
I was unable to create a bridge between the WSL gpg-agent and Windows gpg-agent that would use gpg-agent's ssh support, but I managed to get it to work with gpg-agent's PuTTY support thanks to WSL-SSH-Pageant
, and here are the steps:
Enable PuTTY support in Windows-side gpg-agent by adding enable-putty-support
to gpg-agent.conf
, and restarting the gpg-agent.
Get a wsl-ssh-pageant.exe
, either from the GitHub Releases page or by compiling it yourself. Once you have it, you need to pick a path where it and a socket file will live -- I picked c:\ubuntu\wsl-ssh-pageant\
, so the path to the executable is c:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe
and to the socket is
c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock
.
Set WSL environment variable SSH_AUTH_SOCK
to /mnt/c/ubuntu/wsl-ssh-pageant/ssh-agent.sock
(the path to the socket).
From the Windows side, run C:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe --wsl c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock
to start the bridge.
If everything worked correctly, you can now call ssh-add -L
from WSL and see the GPG Auth key on YubiKey in SSH format. If it works, it is time to set up autorun.
Autorun
Because running wsl-ssh-pageant
blocks the terminal for as long as it is running, if we just set up an autorun shortcut the terminal will remain open until you log off. To avoid this, we will write a trivial Visual Basic script that will run wsl-ssh-pageant
in a hidden window, and place it in the autorun folder:
Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run("C:\ubuntu\wsl-ssh-pageant\wsl-ssh-pageant.exe --wsl c:\ubuntu\wsl-ssh-pageant\ssh-agent.sock"), 0, True
Troubleshooting
wsl-ssh-pageant will silently fail if you give it path into a folder that does not exist. This means that you should double check the paths you pass to it.