Using YubiKeys for SSH authentication and password management
A practical setup for SSH authentication and password management where private keys never leave the hardware
March 30, 2026- Scope
- Before we start, these are good questions to ask
- Installing dependencies
- Setting up the YubiKey for SSH
- Installing passage
- Setting up the YubiKey for passage
- Now do the same for a second YubiKey
- Disaster recovery
Scope
This page shows how YubiKeys can be used for SSH authentication and password management. If you are only interested in SSH authentication for now, this page will point out which parts are relevant for SSH only and which for password management.
YubiKeys can be used for a lot more (e.g. logging into your computer, screen lock, or multi-factor authentication) but these are not shown here.
The setup below follows Filippo Valsorda's age+YubiKeys password management solution. Valsorda maintains the cryptography standard library of the Go programming language, has been the lead of the Go Security team at Google until 2022, and was on the Cryptography team at Cloudflare until 2017. He is the maintainer of age, passage, and yubikey-agent. Password security is all about trust and I trust him.
Before we start, these are good questions to ask
- Why YubiKeys?
- Because then the secret keys stay on the YubiKey, never leave it, and cannot be extracted. You don't have to remember and type a long passphrase and typing the passphrase then cannot be key-logged. You only have to remember a relatively short PIN (up to 8 numbers/characters/symbols).
- What if I lose a YubiKey or it breaks?
- We will create two keys. If (when!) one breaks, I buy a new one and, using the other key that I still have, re-encrypt the password vault to the new key. For SSH, I expire the key that I lost and generate a new one.
- If someone finds/steals my YubiKey, can they impersonate me or get my passwords?
- Probably not, because I protect the keys with a PIN; after 3 failed PIN attempts and 3 failed PUK attempts, the key will lock up and become unusable. And the encrypted passwords are not on the YubiKey, they are on an encrypted hard drive that the attacker would have to get to.
- What if I lose or break both YubiKeys?
- For this I create a disaster recovery key-pair that is not a YubiKey (see below).
- Why passage?
- There are many password managers but most require a cloud account. With passage you store the data yourself using open-source tools, don't have to trust a company, don't pay monthly fees, and are not vulnerable to the company being hacked or sold or going out of business (these happen all the time).
- It's good to have full control over passwords stored encrypted on a hard drive,
but what if my hard drive fails or I accidentally delete them?
- I synchronize/backup the encrypted passwords to other computers using encryption and open-source tools.
- Why passage and not pass?
- But some of these tools look unmaintained!
- Please read https://words.filippo.io/maintenance-policy/.
Installing dependencies
We will need the following tools:
If you also want to use age/passage with YubiKeys, you need:
Optionally, to generate a printable QR code for recovery you might need:
- imagemagick
- qrencode
- zbar
Setting up the YubiKey for SSH
For more details, see: https://github.com/FiloSottile/yubikey-agent
Plug the YubiKey in and run the interactive setup:
$ yubikey-agent -setup
On newer YubiKeys the above can fail and the workaround is (see issue):
$ ykman piv access change-management-key --algorithm TDES --management-key 010203040506070801020304050607080102030405060708 --new-management-key 010203040506070801020304050607080102030405060708
$ yubikey-agent -setup
yubikey-agent -setup generates a new random management key and stores it in
PIN-protected metadata.
Select a PIN (up to 8 numbers/characters/symbols) and keep the defaults:
- PIN policy: Once (A PIN is required once per session, if set)
- Touch policy: Always (A physical touch is required for every decryption)
The tool will output your public SSH key.
If you forgot to take note of your public key, use the following command to see the public key:
$ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
$ ssh-add -L
Create a symlink to make SSH configuration easier:
$ ln -s "$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock" ~/.ssh/yubikey-agent.sock
The reason for the symlink is that .ssh/config knows how to expand "~" but
cannot expand $XDG_RUNTIME_DIR.
Then in your .ssh/config you can set:
Host *
ServerAliveInterval 60
ServerAliveCountMax 2
IdentityAgent ~/.ssh/yubikey-agent.sock
If you prefer to use the YubiKey SSH authentication only for few selected
hosts, you can define IdentityAgent for those hosts.
And you probably want to repeat the same for a second key so that you have one when the first one breaks or you lose it.
Installing passage
This section is about using the YubiKey also for password management and you can skip it if you are only interested in SSH authentication right now and can fast-forward down to disaster recovery.
passage can be installed using package managers but it's also just a shell script and since I am rather paranoid about installing password-related tools, I "install" it by hand and inspect it with my own eyes, similar to this:
mkdir -p $HOME/bin
wget -q --show-progress -O $HOME/bin/passage https://raw.githubusercontent.com/FiloSottile/passage/4e4c5ae14be91833791d45608f50868175c1490f/src/password-store.sh
chmod +x $HOME/bin/passage
For the password store and identities, you can decide where they are stored. For instance it could be here:
export PASSAGE_DIR="$HOME/passage/store"
export PASSAGE_IDENTITIES_FILE="$HOME/passage/identities"
For more details, see https://github.com/FiloSottile/passage and try:
$ passage --help
How about passwords on a phone? Please see section "Mobile" at https://words.filippo.io/passage/.
Setting up the YubiKey for passage
For more details, see: https://github.com/str4d/age-plugin-yubikey.
Plug the YubiKey in and run the interactive setup:
$ age-plugin-yubikey
Select a PIN (up to 8 numbers/characters/symbols) and keep the defaults:
- PIN policy: Once (A PIN is required once per session, if set)
- Touch policy: Always (A physical touch is required for every decryption)
Then add the key to the identities file and to the .age-recipients file:
$ age-plugin-yubikey --identity >> $PASSAGE_IDENTITIES_FILE
$ age-plugin-yubikey --list >> $PASSAGE_DIR/.age-recipients
The identities file is used to decrypt passwords and the .age-recipients file
is used to encrypt passwords.
If you add new keys to .age-recipients, you need to re-encrypt the vault:
$ passage reencrypt
Make sure that nobody other than you can write to the .age-recipients file, otherwise
they could just add their public key and, next time you re-encrypt everything, they
can decrypt everything.
Now do the same for a second YubiKey
The same two steps (for SSH and for passage).
SSH:
$ yubikey-agent -setup
If you forgot to take note of your public key, use the following command to see the public key:
$ export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
$ ssh-add -L
passage:
$ age-plugin-yubikey # run interactive setup
$ age-plugin-yubikey --identity >> $PASSAGE_IDENTITIES_FILE
$ age-plugin-yubikey --list >> $PASSAGE_DIR/.age-recipients
Then re-encrypt passage store to the new key:
$ passage reencrypt
Disaster recovery
For SSH keys, if I lose one or both YubiKeys, I remove the corresponding public key(s) from accounts that use them and generate new ones. Good idea to keep a list of places where you upload your public SSH key to so that you can remove/expire them there, if needed.
For password management, we should prepare for the case where both YubiKeys break or are lost so that we can recover and re-encrypt the password store.
Here is one of many ways to do that:
- On a computer you trust, generate a new key pair and create a QR code from the encrypted cipher:
$ age-keygen | age -p -a | qrencode -o - | magick - -units PixelsPerInch -density 300 -resize 1000x1000 -gravity center -extent 2480x3508 /dev/shm/secret.pdf - Let the tool auto-generate a secure passphrase for you (tool prompts "leave empty to autogenerate a secure one").
- Write the auto-generated passphrase down on a piece of paper.
- Add the public key to
$PASSAGE_DIR/.age-recipients. - Print the QR code.
- Clean up:
$ shred -u /dev/shm/secret.pdf - Re-encrypt the vault for the added public key.
- Keep the QR code and the piece of paper in two separate, secure places.
To recover the vault:
- Create an identity file from the QR code:
$ zbarimg --quiet --raw secret.pdf > recovery-identity $ chmod 600 recovery-identity - Then use the identity file to decrypt the password(s):
$ env PASSAGE_IDENTITIES_FILE=recovery-identity passage example.com