Project: Setup ed25519 key with YubiKey 5 Nano

YubiKey 5 Nano YubiKey 5 Nano

I’ve been using USB security keys for SSH keys since 2015. I have switched from a generic brand to Yubikey about two years ago, mostly as part of gaining additional flexibility - both with types of keys (first USB keys only supported 1024, later 2048 byte sized RSA keys, I wanted 4096 and eventually decided I really like ed25519 ones) and available technologies.

I have recently upgraded my Yubikey 5 Nano key to a newer version. Actually, the key model (device itself) is exactly the same, but firmware version is newer and now supports ed25519 keys.

How I use Yubikeys for SSH

Here are a few important bits, they’re not necessarily representing best practices so please DO YOUR OWN RESEARCH if you’re not sure you want to configure keys the same way I do.

Keys in GnuPG assume expiration dates and even revocation - this means if you lost a key (or just lost access to the key), there’s a way to revoke it from use - meaning it’s not going to be trusted anymore.

Because I’m using Yubikey just for SSH access AND because I’m managing authorized_keys via Ansible for all my infrastructure, I tend not to use any certificates or revocation functionality. Instead, I simply revoke access myself - by making sure I remove old keys from authorized_keys on all my servers.

Always Buy From Yubikey Website

IMPORTANT: be sure to order Yubikey 5 Nano from Yubikey’s official webstore, otherwise you might end up buying a device with older firmware that you can’t upgrade yourself - meaning it will support RSA keys, but not ECC (ed25519) ones.

The firmware you need is 5.2.3 or later - my key has 5.2.7:

YubiKey 5 Nano YubiKey 5 Nano - You Need Firmware 5.2.3+ to support ed25519

Install relevant tools in macOS

Let’s install GnuPG with SmartCard support for interfacing with the Yubikey:

brew install gnupg pinentry-mac

Setting card defaults

We should be able to set card defaults now:

greys@mcfly $ gpg --card-edit

gpg-agent[21944]: card has S/N: XYZ60001240100000001234567890000
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: XYZ60001240100000001234567890000
Application type .: OpenPGP
Version ..........: 0.0
Manufacturer .....: Yubico
Serial number ....: 12345678
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
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Let’s enter the admin mode:

gpg/card> admin
Admin commands are allowed

and now reset the key to factory defaults, just in case:

gpg/card> factory-reset
gpg-agent[21944]: card has S/N: XYZ60001240100000001234567890000
gpg: OpenPGP card no. XYZ60001240100000001234567890000 detected

gpg: Note: This command destroys all keys stored on the card!

Continue? (y/N) y
Really do a factory reset? (enter "yes") yes

Changing Default PINs in Yubikey

Before we forget: let’s change the default PIN numbers for the Yubikey. Factory ones are: 123456 is the user PIN, and 12345678 is the admin PIN.

First it’s option 3 (change admin PIN), then option 1 (change user PIN):

gpg/card> passwd
gpg-agent[21944]: card has S/N: XYZ60001240100000001234567890000
gpg: OpenPGP card no. XYZ60001240100000001234567890000 detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/2'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/3'
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/2'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/3'
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Generating ed25519 Keys

While still in the admin mode, we must use the key-attr command to make sure we’ll generate ed25519 keys instead of the default RSA ones.

IMPORTANT: although it seems like the command is repeating itself, you’re actually configuring same settings for 3 different keys (notice how it says first ed25519, then cv25519, then ed25519 again in the output below). So please answer the same things (select ECC, then Curve 25519) three times in a row to get the same result I did.

gpg/card> key-attr
Changing card key attribute for: Signature key
Please select what kind of key you want:
(1) RSA
(2) ECC
Your selection? 2
Please select which elliptic curve you want:
(1) Curve 25519 _default_
(4) NIST P-384
(6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519
Note: There is no guarantee that the card supports the requested
key type or size. If the key generation does not succeed,
please check the documentation of your card to see which
key types and sizes are supported.
gpg-agent[21944]: DBG: handle*pincache_get: enter '0/openpgp/3'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned 'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/3'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/2'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/3'
Changing card key attribute for: Encryption key
Please select what kind of key you want:
(1) RSA
(2) ECC
Your selection? 2
Please select which elliptic curve you want:
(1) Curve 25519 \_default*
(4) NIST P-384
(6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: cv25519
gpg-agent[21944]: DBG: handle*pincache_put: caching '0/openpgp/3'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/2'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/3'
Changing card key attribute for: Authentication key
Please select what kind of key you want:
(1) RSA
(2) ECC
Your selection? 2
Please select which elliptic curve you want:
(1) Curve 25519 \_default*
(4) NIST P-384
(6) Brainpool P-256
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/3'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/2'
gpg-agent[21944]: DBG: handle_pincache_put: flushing cache '0/openpgp/3'

Excellent! So we’re ready to actually generate the keys.

gpg/card> generate
Make off-card backup of encryption key? (Y/n) n

gpg: Note: keys are already stored on the card!

Replace existing keys? (y/N) y

Please note that the factory settings of the PINs are
PIN = '123456' Admin PIN = '12345678'
You should change them using the command --change-pin

gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/2'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Gleb Reys
Email address: [email protected]
Comment:
You selected this USER-ID:
"Gleb Reys <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/3'->'65B87THISISNOTREAL616AF7D9B3D8A2CF0052ABDE123456'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/1'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_get: enter '0/openpgp/3'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned '65B87THISISNOTREAL616AF7D9B3D8A2CF0052ABDE123456'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/3'->'65B87THISISNOTREAL616AF7D9B3D8A2CF0052ABDE123456'
gpg-agent[21944]: DBG: handle_pincache_get: enter '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned 'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/1'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_get: enter '0/openpgp/3'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned '65B87THISISNOTREAL616AF7D9B3D8A2CF0052ABDE123456'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/3'->'65B87THISISNOTREAL616AF7D9B3D8A2CF0052ABDE123456'
gpg-agent[21944]: DBG: handle_pincache_get: enter '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned 'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/1'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg: key 76160F0DED364B16 marked as ultimately trusted
gpg-agent[21944]: DBG: handle_pincache_get: enter '0/openpgp/1'
gpg-agent[21944]: DBG: handle_pincache_get: cache returned 'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg-agent[21944]: DBG: handle_pincache_put: caching '0/openpgp/1'->'KLMNOP580C07D72DDE61THISISNOTREALCF0052ABCDEFGHIJ'
gpg: revocation certificate stored as '/Users/greys/.gnupg/openpgp-revocs.d/ABCDEF488F6B8THISISNOTREAL160F0DED3GHIJK.rev'
public and secret key created and signed.

We’re done with key creation - type q to complete the session:

gpg/card> q
pub ed25519 2021-07-11 [SC]
XYZ123488F6B876C5C4567895105676160F0ABCDEFGHI
uid Gleb Reys <[email protected]>
sub ed25519 2021-07-11 [A]
sub cv25519 2021-07-11 [E]

Using GPG agent with new Yubikey ed25519

Let’s start gnupg agent:

greys@mcfly $ `gpg-agent --homedir /Users/greys/.gnupg --use-standard-socket --daemon`  INT ✘  20:56:49
gpg-agent[43528]: WARNING: "--use-standard-socket" is an obsolete option - it has no effect
gpg-agent[43529]: gpg-agent (GnuPG) 2.3.1 started
zsh: command not found: SSH_AUTH_SOCK=/Users/greys/.gnupg/S.gpg-agent.ssh;

NOTE: I’m using ZSH and seems this command line isn’t 100% working, so if I don’t see SSH_AUTH_SOCK variable set - I just copy output from the above and paste it in my shell to set it:

greys@mcfly $ SSH_AUTH_SOCK=/Users/greys/.gnupg/S.gpg-agent.ssh;

we should be able to see our keys now (I’ve redacted the output a bit):

greys@mcfly $ ssh-add -l
256 SHA256:o4kAoPiyE+nEjBQ/YV8THISISNOTREALmIaaqHuDQlY cardno:12 345 678 (ED25519)

IMPORTANT: if you don’t see ED25519 at the end of this line, and instead see RSA - this means you didn’t complete the key-attr settings and need to redo the steps (all of them, starting with the factory-reset)

That’s it for now!

Please let me know if you follow this project and highlight any issues with it - I used the brand new Yubikey device, but kept previous macOS desktop which means some tools were already installed.

See Also




Keep Learning

Follow me on Facebook, Twitter or Telegram:
Recommended
I learn with Educative: Educative
IT Consultancy
I'm a principal consultant with Tech Stack Solutions. I help with cloud architectrure, AWS deployments and automated management of Unix/Linux infrastructure. Get in touch!

Recent Tweets