Intro

In the past year Yubico has updated their firmware to support Ed25519. This finally brings support for elliptical curve encryption, and much shorter ssh public keys.

Yubikeys are really useful, they allow you to do git commit signing, ssh, and store your private key on an external device.

This lets you jump between computers easily, and you never have your private key sitting on a local filesystem.

One critical piece to this setup is making backup keys, this has been covered by other blog posts, but there's a less common issue out there: plugging in a cloned key will cause a GPG error that you have to work around on your own... This is frustrating if you setup two yubikeys, and frequently use them both.

This guide will cover creating the GPG master key. Setting it up for commit signing, using this master key with ssh, how to make backups, and how to setup multiple computers. Unfortunately it's a bit involved for newcomers, but once you have this system setup, you're left with an extremely secure SSH + GPG solution!

Getting Started

You need to purchase a Yubikey that was made after November 2019. That's around the time that 5.2.3 came out. You can read more about it here. This firmware version added support for curve25519. I have used the 5CI, 5C nano, 5C, 5 NFC, and the brand new 5C NFC. I've duplicated my master key across each of my keys. I highly recommend you buy at least two keys, so that if you lose one you are not locked out of servers or other systems.

Let's get started by installing the gpg tooling. Be sure to do this step on every computer that you plan to use your Yubikey with!

brew install gnupg pinentry-mac

If you're not running a mac, be sure to download the gnupg utility for your OS.

Generating the Master Key

This will be the master GPG key for all of our Yubikeys, our ssh key is also derived from it. Open up a terminal, and create a new directory. I will use a folder called gpg and will reference it later. Then run the following:

gpg --expert --full-gen-key

Now that we are inside the gpg tool, select 9 for ECC. Followed by 1 for curve25519.

I tend to chose 0 on the next step, so that my key never expires. It will then ask for your name and email address, then you can hit "O" for okay.

The final step is a prompt to choose a password. Be sure to choose something long and very random. I wrote my secret key down on paper for safe keeping. You will need to retype this secret key a lot unfortunately. 3 times per yubikey in order to copy it over, so don't forget it!

Adding subkeys

You should now see a line outputted after its creation that looks like this:

gpg: key A5CA05BB6F4730D4 marked as ultimately trusted

To make our next steps easier to follow, please run this in your terminal:

echo "A5CA05BB6F4730D4" >> keyid

Replace the key id above with your own. We are just throwing it into a file in the current folder for safe keeping.

We need to create authentication and signature subkeys before our master key is complete.

gpg --expert --edit-key $(cat keyid)

Now we are inside the gpg tool and need to do the following:

addkey
choose "11" ECC (set your own capabilities)
choose "A" Toggle the authenticate capability
type "Q"
choose "1" Curve 25519

After that, you can set the expiration, and then type in your secret key to finish creating the sub key. We need to do it once more for the signing key:

addkey
choose "10" ECC (sign only)
choose "1" Curve 25519

Set expiration, and choose yes to create, and then yes again to really create it..... these gpg tools are not very user friendly are they?

Okay! We did it... we created the keys and just need to type save to get out of the gpg tool. We can finally move on.

Exporting the Key

Inside your gpg folder, run the following:

gpg --armor --export-secret-keys $(cat keyid) > mastersub.key
gpg --armor --export-secret-subkeys $(cat keyid) > sub.key
gpg --armor --export $(cat keyid) > public.key

We've successfully exported our key, and the corresponding gpg public key. This will be needed later when we setup commit signing.

THIS IS VERY IMPORTANT

be sure to make a zip file of your gpg folder that we ran the commands inside of. You should have the following files inside:

keyid        
mastersub.key 
public.key    
sub.key

This zip file (gpg.zip) should be backed up offline to a usb drive, or other secure location. It is also very important, because each time we move our gpg key over to a yubikey, the gpg tool destroys the key. So we have to copy over a duplicate each time.

Side note... this is yet another annoyance with the gpg tool. I am trying to make this guide as straight forward as I can, it took me forever to do all of this because of how overly complicated the gpg tools are. Thankfully some core contributors are rewriting the spec in rust.

Setting up your Yubikey

We'll move on to getting our yubikey ready! We start by configuring it. GPG recognizes Yubikeys as smart cards:

gpg --card-edit
admin
passwd

On this prompt, you will want to choose 1 to change the pin. When the prompt comes up, type 123456 for the current pin, this is the default for yubikeys. After, set a secure password. This will be used to unlock the secret key on your Yubikey for ssh or gpg usage. I like to keep mine kind of short, but also something that isn't too easy.

After that, type 3. This time we need to change the admin pin. The initial pin is 12345678. I tend to use the same pin for admin and normal pins. I also use the same pin on each of my backup yubikeys, so that I don't accidentally get locked out. After that's done, type q, then there's a few extra commands you can set if you want to:

name
lang
login

Before we add the keys to our Yubikey, there are a couple optional setup steps. If you want to, you can go download Yubikey Manager CLI. And run these commands:

ykman openpgp set-touch aut off
ykman openpgp set-touch sig on
ykman openpgp set-touch enc on

It's up to you to set these to on or off. This is just telling your yubikey that any authentication, signature, or encryption key usage, requires a physical touch of the device before it will do the operation. This can be useful for ultra security conscious individuals. A program wouldn't be able to sign or encrypt anything with your key in the background, because it would require a touch before any action.

The last thing I tend to do, is install up the Yubikey Manager GUI, go to Applications -> OTP, and disabled the short touch action. This is on by default and if you bump it during a slack message, it can be really weird sending these authenticator codes by accident.

I like to configure the long touch action with a static password, I will choose something really random, but use it across all of my backup keys. If I am on a public computer without access to 1Password, I can use it as a secure password option, or you could even store your 1Password secret key here.

Oh, and before I forget, you can also go to Applications -> FIDO2 and set a pin there. This will be used for Webauthn when supported. If you use your Yubikey for 2FA on the web, it will require a pin, this protects you from someone stealing your yubikey and attempting to use it to access a service online, they would also need your pin. Also note that this is separate, and not the same as the GPG smart card pin we created earlier. All these specs in one device.... confusing huh?

There's so much these keys can do, and its spread across wayyyy too many applications and configurations!

Adding to a Yubikey

This section can be followed again for every Yubikey that you want to use. These will be exact clones with the master key on them, and will expose the same ssh public key every time you do this process.

You need to run the following commands outside of the gpg directory that we created in the key creation step.

Your current directory should have gpg.zip in it. Start by unarchiving the gpg.zip that we created earlier. Each time you do this section (for every key) you need to delete the gpg folder, and unarchive gpg.zip again. We can't reuse the gpg folder each time, because the gpg smart card commands delete the secret key, so you MUST have a fresh copy of the gpg files each time.

# unzip gpg.zip
export GNUPGHOME=$(mktemp -d)
cp -r gpg $GNUPGHOME
cd $GNUPGHOME/gpg

gpg --import mastersub.key
gpg --edit-key $(cat keyid)

Now we begin copying each of the three keys off the card. It's a bit verbose, so here's how you do it:

key 1
keytocard (choose encryption, 1)
key 1


key 2
keytocard (signature)
key 2


key 3
keytocard (auth)
key 3
save

GPG makes you select the key, then after doing keytocard you have to deselect it by typing the same thing again. It sometimes says "operation not supported by device" after you do the initial yubikey setup and run the keytocard command. Just unplug your yubikey, then plug it back in, type keytocard again, and it should show the key selection menu.

Each time you do keytocard you will have to enter the master key's secret key that you wrote down earlier, followed by the card's admin pin that you set. Like I said, pretty verbose, and can take a while if you have a really long secret key!

After this step is complete, your yubikey is ready to go.

Setting up GPG Signing

This process should be done on each computer that you want to do commit signing on.

git config --global user.signingkey $(cat keyid)
git config --global commit.gpgsign true
gpg --import public.key

The last command is the most important part. You must import the public key from your gpg master key, otherwise git won't recognize your yubikey. Don't ask how many hours I spent being confused as to why this didn't work on my second computer the first time :P  

Be sure to add the contents of our public.key file to GitHub or other service, so that your commits will show as verified.

Using SSH

On a Mac you need to do the following:

vi ~/.gnupg/gpg-agent.conf

the contents are:

use-standard-socket
pinentry-program /usr/local/bin/pinentry-mac

This will be different on other OS's. Please look up "gpg agent ssh setup" for your OS if you are not on Mac.

Now, edit your .bashrc, .zshrc, etc, depending on what shell you use, with the following:

export "GPG_TTY=$(tty)"
export "SSH_AUTH_SOCK=${HOME}/.gnupg/S.gpg-agent.ssh"
gpgconf --launch gpg-agent

Be sure to unplug and replug your yubikey in if you just copied the keys over to it. Re-open your terminal (or source ~/.zshrc) and then this command should work:

ssh-add -L

It should list your public key, which is derived from your master gpg key! You will notice cardno:xxxx at the end, in case you have other existing keys. Add this to github or any server just like any other ssh key would be used.

Using Duplicated Keys

Plug in your first key, try to ssh, or make a git commit. Once you do that, remove it and plug in your second key. If you attempt to do the same thing, the GPG agent will complain, and say Please insert card no XXX. It wants you to insert the first card... It's very annoying because our second card has our key on it and should work.

You have to restart the agent to get it to work anytime you swap cards. I added a function to my ~/.zshrc

resetcard() {
  rm -r ~/.gnupg/private-keys-v1.d
  gpgconf --kill gpg-agent
  gpg --card-status
}

Anytime you see the Please insert card error, type resetcard in your terminal, and then it will work. For some reason the GPG agent checks the card number with the SSH public key and wants to refuse to use the second card unless you do this. With this small fix, you're able to use any of your keys in any of your devices. They all work the same, and you'll never have to generate a new SSH key on your computer again!

Conclusion

Thanks for reading through this guide, I hope it helped someone out there. I've spent countless hours setting these up over the years, and needed a better reference. I wish the gpg tooling would correctly support duplicate keys, and make the setup process a bit easier. Yubico also requires you to download the CLI tool for some setup steps, and the GUI has other sets of options. The ecosystem is pretty scattered. It's also unfortunate we must configure the gpg agent manually to support ssh usage. I would like for it to "just work" immediately without any setup, and to include the required pin-entry program automatically. Maybe one day we will get there...

Once you have your keys setup and cloned, it is only a few commands to have git commit signing on a new computer, and a few more configuration options to support ssh, so it's not too bad. The initial process is A LOT though. I can't believe it took 2300+ words to explain it all.

Feel free to send me any questions @zachcodes on twitter :)