Home / Articles / YubiKey Config

A photo of a usb card reader with a YubiKey and a flash drive.

   

What and why

I use hardware security keys to access my servers and sign Git commits.

With this setup all private key material is stored on my YubiKeys in a way that prevents it from ever being extracted — not even by me. This makes it impossible for software to exfiltrate those secrets.

This is typically a tricky thing to set up correctly and consistently, so I created a series of scripts that do all the hard work for me and ensure a consistent setup. Those scripts are published on git.xo.codes.

   

My keys

HomeWorkLaptopsPurseSafe--|||

I keep several YubiKeys all provisioned identically. I keep a key on me, at each of my personal computers and another stored securely in another location.

   

Information Storage

Secret Secrecy Storage
SSH private key Highest YubiKey only (cannot be exported).
SSH public key None SSH servers, Git servers, anywhere.
SSH stub (ecdsa-sk) None ~/.ssh/yubikey-<serial> on each PC; manually copied to new machines.
SSH stub (ed25519-sk) None YubiKey; regenerated to any PC on demand with ssh-keygen -K.
master-secret-key.asc Highest Offline storage.
secret-subkeys.asc Highest YubiKeys + offline storage.
gpg-signing-key.asc High Offline storage.
revocation.asc High Offline storage.
public-key.asc None Git server, anywhere.
FIDO2 PIN High Your brain + YubiKeys.
OpenPGP user PIN High Your brain + YubiKeys.
OpenPGP admin PIN Higher Your brain + YubiKeys.

   

Provisioning

https://git.xo.codes/xo/yubikey-config

I use a series of scripts that set up my keys for me. The process is interactive and scripted to make the process easy, repeatable and sharable.

   

The provisioning process

00-provision-keys.bat(or)Errorsand00-provision-keys-colors.batfailedchecks01Checkprerequisitesexit02GenerateGPGkey03BackupGPGkeysonceperYubiKey04SetFIDO2PIN05GenerateSSHKey(onYubiKey(|y06ChangeOpenPGPPINs||07TransfersigningsubkeytoYubiKey08ConfigureGityes09TestSSH10TestSigningAnotherkeynoexit

   

The backup directory

After provisioning your first key, the backup directory will contain:

File Contents
master-secret-key.asc The offline master key – guard this carefully
secret-subkeys.asc The signing subkey (also on your YubiKeys)
public-key.asc Your public key – safe to distribute
gpg-signing-key.asc Public signing subkey – for importing on new machines
revocation.asc Revocation certificate – lets you invalidate the key if needed

Keep this directory somewhere secure and offline. I don't go all-out backing this up across multiple media types and locations, but I do keep a USB key somewhere safe. Worst case scenario I can always re-provision my keys again with a few headaches – but it is possible.

   

Provisioning multiple keys

The keytocard GPG command that moves the signing subkey onto a YubiKey is destructive – it removes the key from your local keyring when it's transferred. To provision additional keys, the script reimports the subkey from your backup before each transfer. This is why the backup is created in step 3, before the transfer in step 7.

   

Firmware and key types

YubiKey firmware can't be updated – whatever firmware your key has you are stuck with. The firmware version determines what kind of SSH key the YubiKey can generate:

  • Firmware 5.2.3 and above – generates ed25519-sk resident keys. Key material lives entirely on the hardware and nothing is written to disk.
  • Firmware 5.1 to 5.2.3 – generates ecdsa-sk non-resident keys. A stub file is written to disk during provisioning.

The scripts handle both automatically. The difference between ecdsa-sk and ed25519-sk isn't where the private key lives – that is always resident (on the YubiKeys). The difference is where the key handle lives. For ed25519-sk the key handle is also in resident storage. For ecdsa-sk the key handle is on disk.

   

Day to day

   

First time setup

With a couple of YubiKeys and a USB drive for backup I run 00-provision-keys-colors.bat and follow the prompts. This involves setting PIN codes and touching my YubiKey a few times.

Once my keys are provisioned I access whatever servers I want to use these keys on and I copy the contents of backup_drive/authorized_keys to the authorized_keys of any SSH user/server I want to access.

I also copy the signing public key to my Git server so it can verify the authenticity of my commits and I can get a little green checkmark next to commits.

   

Daily use

On a typical day I have my YubiKey plugged into the computer I'm using. When I want to SSH into a server I just type ssh myserver in a terminal and I'm prompted to enter a PIN and touch my key.

Similarly when I run git commit or push I am prompted to enter a PIN and touch my YubiKey.

   

Setting up on a new machine

With a YubiKey I want to use on this new machine and my backup drive:

  1. I import my public key from the backup directory:

    gpg --import gpg-signing-key.asc

  2. Plug in my YubiKey and run gpg --card-status. GPG detects the card and links it to the imported key automatically.
  3. Set ultimate trust on the key:

    gpg --edit-key <fingerprint>

    Type trust, select 5, then quit.

  4. Configure git:

    git config --global gpg.program "C:\Program Files\GnuPG\bin\gpg.exe"
    git config --global commit.gpgsign true
    git config --global user.signingkey <fingerprint>

    The value you set for gpg.program should match the top result from where gpg. If it doesn't, update gpg.program to match.

   

Switching between keys

GPG records the serial number of the last card it used. When you swap in a different YubiKey, GPG can't match it and signing fails. The fix is one command:

gpg --card-status

Run that after inserting a different key, then retry your commit.

If you find yourself doing this very often you could always create a gpg wrapper script and point git's gpg.program config value to the wrapper script. A wrapper might look something like this:

@echo off
gpg --card-status 2> nul
gpg %*

   

Other uses

I use a password manager for passkeys, time-based one-time passwords, and generated text-based passwords.

My password manager is secured with a strong text-based password and a time-based one-time password which is also stored on my YubiKeys.

I don't store passkeys or time-based one-time passwords (other than of my vault) on YubiKeys because there's a relatively low number of those that can be stored on YubiKeys and I don't want to have to remember which key goes with which account.

This article isn't about password managers though, but it's sort of related. So there you are.


The glorious OpenSSH banner from the OpenSSH website.

Back to top


xo © 2026 all rights reserved — attribution

Robots can go to hell.