A Yubikey is a physical security device for your computer (and phone if you get the NFC version). In this post we will look at adding GPG and SSH keys to the Yubikey for additional privacy and security.

What is a Yubikey?

The YubiKey is a hardware authentication device manufactured by Yubico to protect access to computers, networks, and online services that supports one-time passwords, public-key cryptography, and authentication, and the Universal 2nd Factor and FIDO2 protocols developed by the FIDO Alliance.

The YubiKey - like other, similar devices - is a small metal and plastic key about the size of a USB stick. They plug into your computer, and some also connect to your phone. You can use them in either place, along with your password, to authenticate web logins.

But what does it do?

Primarily, the Yubikey is a hardware 2FA for your online accounts. Instead of using a highly insecure SMS code to login to your account, the Yubikey will generate a one-time password (OTP) when it is inserted in the USB port of your computer, proving to e.g. Google or Facebook that you have the security device, and hence you should be allowed access to your account.

NOTE notice I did not say “proving that you are you”, only “proving that you have access to the key that was used to secure the account” - it is a subtle but important difference.

What do you mean an SMS is not a real 2FA?

Multi-factor authentication (MFA, sometimes called 2FA) is based on the idea that the person trying to log into a system can prove that they are allowed to log in to the system using an authenticating factor. These can be split into three categories:

  • Something you have (a smartcard, a key)
  • Something you know (a password, a secret code)
  • Something you are (a fingerprint, a retinal scan)

But, above all, for this system to work, the factor should be unique. E.g. no one else can have the same fingerprint as you, no one else can have the same security number burned into their smartcard chip as you.

So, you see, an SMS cannot really be a MFA. This is because the message is stored and forwarded by every cell tower between the routing device and your handset. Maybe the message was not even encrypted as it passed to the routing device from Google dot com, or wherever. Also, if someone has SIM cloned your phone - well they have the SMS too. Maybe they can type quicker than you?

A Yubikey is an hardware alternative to the software HOTP/TOTP codes that are generated by Authy app, or Google Authenticator app. It is a genuine MFA device.

How to setup a Yubikey

For apps like Facebook and Google it is extremely straightforward, just go to the security page on your account and look for 2FA or MFA and follow the instructions. You will be told to insert the Yubikey in the laptop and press the gold disc to create a code for Google Chrome. Done. Easy.

Setup a Yubikey for GPG

This is a TL;DR version of the setup guide. So, the plan is that we will make a 4096 bit RSA Certification key as a Master key, then derive subkeys, then export all the keys to a safe place, then we will add the keys to the Yubikey.

Create a 4096-bit Master Certification Key

Do this

$ gpg --quick-gen-key "Jas Powell <jas@davepowell.net>" rsa4096 cert 50y
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 0xF43BA3EA4862E172 marked as ultimately trusted
gpg: revocation certificate stored as '/Users/jas/.gnupg/openpgp-revocs.d/7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.rev'
public and secret key created and signed.

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub   rsa4096/0xF43BA3EA4862E172 2022-03-04 [C] [expires: 2072-02-20]
      Key fingerprint = 7367 E301 2B4F 6A98 D2BD  9D4D F43B A3EA 4862 E172
uid                              Jas Powell <jas@davepowell.net>

Add the Subkeys

Do this:

$ gpg --quick-add-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 rsa4096 encr 2y
$ gpg --quick-add-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 rsa4096 sign 2y
$ gpg --quick-add-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 rsa4096 auth 2y

Check the Keys

The public keys:

$ gpg -k
/Users/jas/.gnupg/pubring.kbx
-----------------------------
pub   rsa4096/0xF43BA3EA4862E172 2022-03-04 [C] [expires: 2072-02-20]
      Key fingerprint = 7367 E301 2B4F 6A98 D2BD  9D4D F43B A3EA 4862 E172
uid                   [ultimate] Jas Powell <jas@davepowell.net>
sub   rsa4096/0x6B4ECE4444C100D4 2022-03-04 [E] [expires: 2024-03-03]
sub   rsa4096/0x1F99A7878C6BF622 2022-03-04 [S] [expires: 2024-03-03]
sub   rsa4096/0x3B2D4EF6A6A88608 2022-03-04 [A] [expires: 2024-03-03]

And the secret/subkeys keys:

$ gpg -K
/Users/jas/.gnupg/pubring.kbx
-----------------------------
sec   rsa4096/0xF43BA3EA4862E172 2022-03-04 [C] [expires: 2072-02-20]
      Key fingerprint = 7367 E301 2B4F 6A98 D2BD  9D4D F43B A3EA 4862 E172
uid                   [ultimate] Jas Powell <jas@davepowell.net>
ssb   rsa4096/0x6B4ECE4444C100D4 2022-03-04 [E] [expires: 2024-03-03]
ssb   rsa4096/0x1F99A7878C6BF622 2022-03-04 [S] [expires: 2024-03-03]
ssb   rsa4096/0x3B2D4EF6A6A88608 2022-03-04 [A] [expires: 2024-03-03]

Trust the Generated Key

First, make sure that our new keys are trusted…

$ gpg --edit-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172
Secret subkeys are available.

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> trust
pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> quit

Export the Keys for Storage

Do this:

$ gpg --export-secret-keys 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 > 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sec

$ gpg --export-secret-subkeys 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 > 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sub.sec

$ gpg --export 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 > 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.pub

$ ls -la
total 72
drwxr-xr-x   5 jas  staff    160 Mar  4 14:12 .
drwxr-xr-x+ 69 jas  staff   2208 Mar  4 14:12 ..
-rw-r--r--   1 jas  staff   6896 Mar  4 14:12 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.pub
-rw-r--r--   1 jas  staff  13890 Mar  4 14:12 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sec
-rw-r--r--   1 jas  staff  12151 Mar  4 14:12 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sub.sec

Detach the Master Key

Do this:

$ gpg --delete-secret-keys 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172

sec  rsa4096/0xF43BA3EA4862E172 2022-03-04 Jas Powell <jas@davepowell.net>

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

And re-add the subkeys:

$ gpg --import 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sub.sec
gpg: key 0xF43BA3EA4862E172: "Jas Powell <jas@davepowell.net>" not changed
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key 0xF43BA3EA4862E172: secret key imported
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

Check that the Master key is detached:

$ gpg -K
/Users/jas/.gnupg/pubring.kbx
-----------------------------
sec#  rsa4096/0xF43BA3EA4862E172 2022-03-04 [C] [expires: 2072-02-20]
      Key fingerprint = 7367 E301 2B4F 6A98 D2BD  9D4D F43B A3EA 4862 E172
uid                   [ultimate] Jas Powell <jas@davepowell.net>
ssb   rsa4096/0x6B4ECE4444C100D4 2022-03-04 [E] [expires: 2024-03-03]
ssb   rsa4096/0x1F99A7878C6BF622 2022-03-04 [S] [expires: 2024-03-03]
ssb   rsa4096/0x3B2D4EF6A6A88608 2022-03-04 [A] [expires: 2024-03-03]

We know the master key is detached when we see sec# instead of sec in the secret keys.

Setup the Yubikey

Insert the Yubikey, let’s check it out:

$ gpg --edit-card

Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D2760001240102010006066341410000
Application type .: OpenPGP
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 06634141
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
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Choose a PIN

Use the following commands to change the PIN. Note the default PIN is 123456. You need to enter that before choosing a new PIN.

gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. D2760001240102010006066341410000 detected

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

Your selection? 1
PIN changed.

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

The admin PIN can be changed also. The default there is 12345678.

Set user preferences

Change the name, language, email, etc.

gpg/card> admin
Admin commands are allowed

gpg/card> name
Cardholder's surname: Powell
Cardholder's given name: Jas

gpg/card> lang
Language preferences: en

gpg/card> login
Login data (account name): jas@davepowell.net

gpg/card> list

Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D2760001240102010006066341410000
Application type .: OpenPGP
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 06634141
Name of cardholder: Jas Powell
Language prefs ...: en
Salutation .......:
URL of public key : [not set]
Login data .......: jas@davepowell.net
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Add the Keys to the Yubikey

Transfer the signature key. Notice the * symbol next to the selected key.

$ gpg --edit-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172
Secret subkeys are available.

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> key 2

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb* rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> keytocard
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

You will be prompted to enter the admin PIN of the Yubikey. If you did not change it, the default is 12345678.

Now, let’s transfer the authentication key. Note that we need to type key 2 again to deselect the signing key…

gpg> key 3

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb* rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb* rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> key 2

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb  rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb* rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> keytocard
Please select where to store the key:
   (3) Authentication key
Your selection? 3

And now we add the encryption key:

gpg> key 1

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb* rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb* rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> key 3

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb* rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

gpg> keytocard
Please select where to store the key:
   (2) Encryption key
Your selection? 2

pub  rsa4096/0xF43BA3EA4862E172
     created: 2022-03-04  expires: 2072-02-20  usage: C
     trust: ultimate      validity: ultimate
ssb* rsa4096/0x6B4ECE4444C100D4
     created: 2022-03-04  expires: 2024-03-03  usage: E
ssb  rsa4096/0x1F99A7878C6BF622
     created: 2022-03-04  expires: 2024-03-03  usage: S
ssb  rsa4096/0x3B2D4EF6A6A88608
     created: 2022-03-04  expires: 2024-03-03  usage: A
[ultimate] (1). Jas Powell <jas@davepowell.net>

Make sure we save progress:

gpg> save

And check the status of the secret keys again:

$ gpg -K
/Users/jas/.gnupg/pubring.kbx
-----------------------------
sec#  rsa4096/0xF43BA3EA4862E172 2022-03-04 [C] [expires: 2072-02-20]
      Key fingerprint = 7367 E301 2B4F 6A98 D2BD  9D4D F43B A3EA 4862 E172
uid                   [ultimate] Jas Powell <jas@davepowell.net>
ssb>  rsa4096/0x6B4ECE4444C100D4 2022-03-04 [E] [expires: 2024-03-03]
ssb>  rsa4096/0x1F99A7878C6BF622 2022-03-04 [S] [expires: 2024-03-03]
ssb>  rsa4096/0x3B2D4EF6A6A88608 2022-03-04 [A] [expires: 2024-03-03]

Notice now that the subkeys have ssb> where the > symbol indicated that the key has been exported to a card, in this case the Yubikey.

Check the Status

Check on the Yubikey to see if it is configured correctly:

$ gpg --card-status
Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D2760001240102010006066341410000
Application type .: OpenPGP
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 06634141
Name of cardholder: Jas Powell
Language prefs ...: en
Salutation .......:
URL of public key : [not set]
Login data .......: jas@davepowell.net
Signature PIN ....: not forced
Key attributes ...: rsa4096 rsa4096 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: DAD3 64D5 BE9E DA14 3404  A5FB 1F99 A787 8C6B F622
      created ....: 2022-03-04 11:42:13
Encryption key....: 6C00 12BA 26A1 1826 93DA  AC4A 6B4E CE44 44C1 00D4
      created ....: 2022-03-04 11:42:06
Authentication key: 33C8 1D78 CAB6 59ED 1589  F47D 3B2D 4EF6 A6A8 8608
      created ....: 2022-03-04 11:42:18
General key info..: sub  rsa4096/0x1F99A7878C6BF622 2022-03-04 Jas Powell <jas@davepowell.net>
sec#  rsa4096/0xF43BA3EA4862E172  created: 2022-03-04  expires: 2072-02-20
ssb>  rsa4096/0x6B4ECE4444C100D4  created: 2022-03-04  expires: 2024-03-03
                                  card-no: 0006 06634141
ssb>  rsa4096/0x1F99A7878C6BF622  created: 2022-03-04  expires: 2024-03-03
                                  card-no: 0006 06634141
ssb>  rsa4096/0x3B2D4EF6A6A88608  created: 2022-03-04  expires: 2024-03-03
                                  card-no: 0006 06634141

Export the stub keys

Something to know is that the keytocard command replaced the secret keys on the host machine with key “stubs”, basically a notice to the decryption program to look for the Yubikey smartcard instead of actually knowing the secret key. We should export this now, since any other machine we might want to use the Yubikey with will need to have the key stub imported on it.

$ gpg --export-secret-keys 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 > 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sec.stub

And on any other machine:

$ gpg --import 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172.sec.stub

NOTE on my Linux box I needed to run apt install scdaemon first so that my smart card was recognized.

Test Encryption

Do this:

$ echo Hello, world! > plain.txt

$ gpg --default-key 7367E3012B4F6A98D2BD9D4DF43BA3EA4862E172 -e -r jas --armor plain.txt

$ ls
plain.txt
plain.txt.asc

$ cat plain.txt.asc
-----BEGIN PGP MESSAGE-----

hQIMAwAAAAAAAAAAAQ/9FCfamY/icA1w1AajoQXRe3YsGTTdSUHzToWvZ3X0ax2S
1lI2Bs7kUAq05EhLkpGAfPS/9w45d3yI6yzHkxaDMC4sKHIFbLKmxM3ebhSmo/LV
N7MmfARcIG9j+Ikgc5oBQ5Zi4tDeBcYu5Lko1ZGCRo5C2hwPWDEx+ub5yKNzQp+J
dPsDVEFlDpy1wXf4DYk6R2jE1W2gN+buL7HQBvpK9hC/IkAYsUgYT0JhIepw+ob6
jq0E2/JcrudVNI2D+Jp7QOhiOkSnOZDMuEwkwZOCySk4mslzz9cIKqGqd4KSfuLY
I3hpbCnoyzWJUkVqP15iJE4tEoCS/DPrGkAlmwqEX3g7Pou3saYTRA0hBXedgoHc
lqBVLKsecIP76ZYnNSiEovNkGVVt7Gr2WzvOUKoJulVQnblqSpftAq9AW29rNOiH
GFacFXN9x+QfnrJz+RZmvSze1uwoMAHi/qklVXgxJbKaHQHQPIL7OFNrPirPZQ0C
8fV0Se/Hrm7+tKghOLjDDOqyTXrufN2MXOR/l0X2ik0GsO34GPGWqrVvqm+sz99x
iDwtndDjltv0iLYk9nyZGO1XpSjTEAthDTH9c94xTi01ubtLY/ZQ5PBlI8KhQsUh
Et7ziVSw4ub9b7Ty4jzsAx9UYbp6zobSCp9f79NIoi0KmDFILWMNuHLzXvcJU6nS
SAGsvDUX4H1kRhNolcd2CAfR33yyP0eQ/YxjizGQeRG1TuchKkb3rLKLQq48tA5L
FvDX5P2Cbqt1zMxwuJFDWP5Km1zidxUywA==
=r1UV
-----END PGP MESSAGE-----

$ gpg -d plain.txt.asc
gpg: all values passed to '--default-key' ignored
gpg: selecting card failed: Operation not supported by device
gpg: anonymous recipient; trying secret key 0x6B4ECE4444C100D4 ...

At this point we are prompted to insert the Smart Card (Yubikey)

Prompt to insert the Yubikey

And then we put the PIN and the message is decoded!

gpg: okay, we are the anonymous recipient.
gpg: encrypted with RSA key, ID 0x0000000000000000
Hello, world!