What are GPG keys?
If you’re not entirely familiar with all concepts surrouding GPG, you should read my article “What are GPG keys?” first. It will give you all the necessary concepts and vocabulary you will need to understand this article.
Configuring the YubiKey
Before we move the keys to the YubiKey, let’s make sure it is configured and safe to use. To edit the GPG configuration on the Yubikey, we can use gpg --card-edit
:
gpg --card-edit
Command output
Like gpg --edit-key
, this opens a console-like environment where we can interact with the GPG card:
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: D276000124010000000620141XXX0000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 20141XXX
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]
gpg/card>
How to list available commands
The available commands can be listed by running help
:
gpg/card> help
quit quit this menu
admin show admin commands
help show this help
list list all available data
fetch fetch the key specified in the card URL
passwd menu to change or unblock the PIN
verify verify the PIN and list all data
unblock unblock the PIN using a Reset Code
openpgp switch to the OpenPGP app
gpg/card>
Configuring the PIN
First things first, let’s change the factory default PIN and Admin PIN so only ourselves can administrate the key. We could use gpg --change-pin
but since we’re already in card edit, we can just run passwd
:
gpg/card> passwd
Command output
gpg/card> passwd
gpg: OpenPGP card no. D276000124010000000620141XXX0000 detected
gpg
then shows a series of PINEntry dialogs to change the PIN:
-
┌──────────────────────────────────────────────┐ │ Please enter the PIN │ │ │ │ PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └──────────────────────────────────────────────┘
As stated in Using Your YubiKey with OpenPGP – Yubico:
Note: If you haven’t set a User PIN or an Admin PIN for OpenPGP, the default values are 123456 and 12345678, respectively. If the User PIN and/or Admin PIN have been changed and are not known, the OpenPGP Applet can be reset by following this article.
-
┌──────────────────────────────────────────────┐ │ New PIN │ │ │ │ PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └──────────────────────────────────────────────┘
-
┌──────────────────────────────────────────────┐ │ Repeat this PIN │ │ │ │ PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └──────────────────────────────────────────────┘
Then it goes back to the console-like environment:
PIN changed.
gpg/card>
Configuring the Admin PIN
By default, only a subset of commands are available, so as we did with the --expert
1 flag for gpg --edit-key
, here we need to run admin
:
gpg/card> admin
Command output
gpg/card> admin
Admin commands are allowed
gpg/card> help
quit quit this menu
admin show admin commands
help show this help
list list all available data
name change card holder's name
url change URL to retrieve key
fetch fetch the key specified in the card URL
login change the login name
lang change the language preferences
salutation change card holder's salutation
cafpr change a CA fingerprint
forcesig toggle the signature force PIN flag
generate generate new keys
passwd menu to change or unblock the PIN
verify verify the PIN and list all data
unblock unblock the PIN using a Reset Code
factory-reset destroy all keys and data
kdf-setup setup KDF for PIN authentication (on/single/off)
key-attr change the key attribute
uif change the User Interaction Flag
openpgp switch to the OpenPGP app
gpg/card>
This also changes the behavior of passwd
, which can now be used to change the Admin PIN:
gpg/card> passwd
gpg: OpenPGP card no. D276000124010000000620141XXX0000 detected
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 3
Command output
-
┌────────────────────────────────────────────────────┐ │ Please enter the Admin PIN │ │ │ │ Number: 20 141 XXX │ │ Holder: │ │ │ │ Admin PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └────────────────────────────────────────────────────┘
As stated in Using Your YubiKey with OpenPGP – Yubico:
Note: If you haven’t set a User PIN or an Admin PIN for OpenPGP, the default values are 123456 and 12345678, respectively. If the User PIN and/or Admin PIN have been changed and are not known, the OpenPGP Applet can be reset by following this article.
-
┌────────────────────────────────────────────────────┐ │ New Admin PIN │ │ │ │ Admin PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └────────────────────────────────────────────────────┘
-
┌────────────────────────────────────────────────────┐ │ Repeat this PIN │ │ │ │ Admin PIN ________________________________________ │ │ │ │ <OK> <Cancel> │ └────────────────────────────────────────────────────┘
After the Admin PIN is changed, quit to go back to the console-like environment:
PIN changed.
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? Q
gpg/card>
Configuring your personal details
Now let’s customize the key:
gpg/card> name
Cardholder's surname: John
Cardholder's given name: Doe
┌────────────────────────────────────────────────────┐
│ Please enter the Admin PIN │
│ │
│ Number: 20 141 XXX │
│ Holder: │
│ │
│ Admin PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└────────────────────────────────────────────────────┘
gpg/card> lang
Language preferences: fr
gpg/card>
We could also change login
and url
if we want to. There is also salutation
but I have no idea how it should be used.
Moving the private subkeys to the YubiKey
Our OpenPGP smartcard (the YubiKey) is now ours and secured. Let’s move our private keys to it.
Danger
WAIT! Make sure you backup your master key, at least. Once a private key is moved to a YubiKey, it can never be extracted from it (by design). If you loose this YubiKey, you will never be able to generate new subkeys.
gpg --edit-key "${KEY_ID:?}"
Command output
gpg (GnuPG) 2.4.3; Copyright (C) 2023 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Secret key is available.
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
Now let’s select the Sign key (“usage: S”), of index 1
:
gpg> key 1
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb* ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
Notice the star (*
) after ssb
. This means the key is selected. We can then run keytocard
to move it to our card:
gpg> keytocard
Please select where to store the key:
(1) Signature key
(3) Authentication key
Your selection?
Tip
We could also use addcardkey
instead to keep the private keys locally after save
.
We want to move it to the “Signature” slot of the OpenPGP smartcard, so we choose 1
:
Your selection? 1
Command output
Your selection? 1
┌────────────────────────────────────────────────────────────────────── ─────────────────┐
│ Please enter your passphrase, so that the secret key can be unlocked for this session │
│ │
│ Passphrase: _________________________________________________________________________ │
│ │
│ <OK> <Cancel> │
└───────────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ Please enter the Admin PIN │
│ │
│ Number: 20 141 XXX │
│ Holder: Doe John │
│ │
│ Admin PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└────────────────────────────────────────────────────┘
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb* ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
Note: the local copy of the secret key will only be deleted with "save".
gpg>
Note the comment at the end:
Note: the local copy of the secret key will only be deleted with "save".
Let’s move the two other keys. Make sure to deselect the selected key before running keytocard
(it won’t work otherwise anyway), but other than that it is very straightforward:
gpg> key 1
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg> key 2
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb* cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg> keytocard
Please select where to store the key:
(2) Encryption key
Your selection? 2
[...]
Note: the local copy of the secret key will only be deleted with "save".
gpg> key 2
[...]
gpg> key 3
[...]
gpg> keytocard
Please select where to store the key:
(3) Authentication key
Your selection? 3
[...]
Note: the local copy of the secret key will only be deleted with "save".
gpg>
Now is the critical part: saving. Using Your YubiKey with OpenPGP – Yubico says you should quit without saving, but this leaves the private keys on the computer. Since we have backed up the private keys, we can safely save and delete the local copy. If you haven’t backed up your keys, you can always open a new terminal tab or quit
instead of save
to do it.
Double check that you backed up the keys correctly, as you will never be able to get them out of the YubiKey!
gpg> save
Checking that it worked
Let’s check that this worked.
We can first have a look at the OpenPGP smartcard details to see if the keys are configured:
gpg --card-status
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: D276000124010000000620141XXX0000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 20141XXX
Name of cardholder: Doe John
Language prefs ...: fr
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: ed25519 cv25519 ed25519
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 3
KDF setting ......: off
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: DBCC F949 18B2 4D1A 0053 AD43 A2E6 18E2 0456 625F
created ....: 2023-12-12 22:01:54
Encryption key....: B5FF 206E 44D0 20AE 6CC5 30C1 5247 7AF6 A631 E77F
created ....: 2023-12-12 22:18:06
Authentication key: 5706 1CDF 8DC3 15ED 81A4 1DCC 37A9 02F0 30B4 4E05
created ....: 2023-12-12 22:20:20
General key info..: sub ed25519/A2E618E20456625F 2023-12-12 John Doe (General purpose personal key) <john.doe@example.org>
sec ed25519/A6D3E049FD2A88C4 created: 2023-12-12 expires: never
ssb> ed25519/A2E618E20456625F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> cv25519/52477AF6A631E77F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> ed25519/37A902F030B44E05 created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
With the YubiKey plugged in, signing a file should work as expected:
gpg --local-user "${KEY_ID:?}" --armor --output - --sign <(echo 'Test')
┌──────────────────────────────────────────────┐
│ Please unlock the card │
│ │
│ Number: 20 141 XXX │
│ Holder: Doe John │
│ Counter: 0 │
│ │
│ PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────┘
-----BEGIN PGP MESSAGE-----
owGbwMvMwCW26JnEI5awpHjG07xJTIbGqZVMy0JSi0u4OkpZGMS4GGTFFFlun/np
KbHJV4oheK0zTDkrE0glAxenAExERojhn87f3kM6IS7PP4vektaYnLB9++LnCx6d
eLdxnY79OxP1WHFGhtOn/nPvdGLij/r7KW/hm7j932TX6294sNRp50n92V/ts3gB
=F13E
-----END PGP MESSAGE-----
It outputs a PGP message, which means it worked.
Now let’s unplug the YubiKey and try again:
gpg --local-user "${KEY_ID:?}" --armor --output - --sign <(echo 'Test')
gpg
can’t sign the payload as it can’t access the private key:
┌────────────────────────────────────────────┐
│ Please insert the card with serial number: │
│ │
│ 20 141 XXX │
│ │
│ │
│ <OK> <Cancel> │
└────────────────────────────────────────────┘
Now plug the YubiKey again and hit Enter (having <OK>
selected). It asks you for the card PIN:
┌──────────────────────────────────────────────┐
│ Please unlock the card │
│ │
│ Number: 20 141 XXX │
│ Holder: Doe John │
│ Counter: 2 │
│ │
│ PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────┘
-----BEGIN PGP MESSAGE-----
owGbwMvMwCW26JnEI5awpHjG07xJTIbGqZXMbCGpxSVcHaUsDGJcDLJiiiy3z/z0
lNjkK8UQvNYZppyVCaSSgYtTACby5jHD/5T4jjZj/rpOhjXxbEFdNkxBt+/n7jW2
DMm6eks2zemXCCPDSe1MqyMpsQtZ067PfNUf5n/thOiewj+GLkmdfUt8M+5wAgA=
=uDWm
-----END PGP MESSAGE-----
It outputs a PGP message, which means the YubiKey really is necessary to sign a payload.
I won’t go over encryption and authentication, but it’s the same.
Adding extra security
Requiring card taps
Having to plug and unlock the YubiKey is a secure requirement. However, what happens if a malicious background process tries to access a machine using SSH or more generally authenticate you somewhere with your authentication key for example? The same goes for signing and encryption, even though it’s not as good an example. Well, configured like it is right now, our YubiKey will authorize any operation as long as its timeout is not reached. Let’s change that.
One solution is to require “card taps” on every operation, which means you have to physically touch the YubiKey every time you sign, encrypt or authenticate. This can be configured via the “User Interaction Flag” (or “UIF”) using uif
2 in gpg --card-edit
:
gpg --card-edit
Command output
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: D276000124010000000620141XXX0000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 20141XXX
Name of cardholder: Doe John
Language prefs ...: fr
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: ed25519 cv25519 ed25519
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 3
KDF setting ......: off
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: DBCC F949 18B2 4D1A 0053 AD43 A2E6 18E2 0456 625F
created ....: 2023-12-12 22:01:54
Encryption key....: B5FF 206E 44D0 20AE 6CC5 30C1 5247 7AF6 A631 E77F
created ....: 2023-12-12 22:18:06
Authentication key: 5706 1CDF 8DC3 15ED 81A4 1DCC 37A9 02F0 30B4 4E05
created ....: 2023-12-12 22:20:20
General key info..:
sub ed25519/A2E618E20456625F 2023-12-12 John Doe (General purpose personal key) <john.doe@example.org>
sec ed25519/A6D3E049FD2A88C4 created: 2023-12-12 expires: never
ssb> ed25519/A2E618E20456625F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> cv25519/52477AF6A631E77F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> ed25519/37A902F030B44E05 created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
Here is how to use the uif
command:
gpg/card> admin
Admin commands are allowed
gpg/card> uif
usage: uif N [on|off|permanent]
1 <= N <= 3
gpg/card>
N
is the index of the key we want to update (Sign=1, Decrypt=2, Auth=3)on
means the card will require a tapoff
means the card will not require a tappermanent
is the same ason
but you won’t be able to change the flag anymore
I have no reason to keep one off, so let’s set the three to on
:
gpg/card> uif 1 on
┌────────────────────────────────────────────────────┐
│ Please enter the Admin PIN │
│ │
│ Number: 20 141 702 │
│ Holder: Doe John │
│ │
│ Admin PIN ________________________________________ │
│ │
│ <OK> <Cancel> │
└────────────────────────────────────────────────────┘
gpg/card> uif 2 on
gpg/card> uif 3 on
Checking that it worked
We can make sure this worked by first checking the card data:
gpg/card> list
[...]
UIF setting ......: Sign=on Decrypt=on Auth=on
[...]
gpg/card> quit
They are all set to on
which means it worked.
We can also check that signing a file requires a tap:
gpg --local-user "${KEY_ID:?}" --armor --output - --sign <(echo 'Test')
-
When tapping the card after running the command:
-----BEGIN PGP MESSAGE----- owGbwMvMwCW26JnEI5awpHjG07xJTIbGqZV8uiGpxSVcHaUsDGJcDLJiiiy3z/z0 lNjkK8UQvNYZppyVCaSSgYtTACaybzsjw6yg+as77Kv/ckR52Pu4XFDtapyhXcny RG6XjL7H6zlVBxgZrtq4/53QeHxFyDrH18sWb9odlsr15bqi/c4rTt1fJ3zxYwYA =D0sU -----END PGP MESSAGE-----
-
When not tapping the card:
gpg: signing failed: Timeout -----BEGIN PGP MESSAGE----- gpg: signing failed: Timeout
I’m suprised I even see -----BEGIN PGP MESSAGE-----
when not tapping, but it timed out so it worked anyway.
Note that having the card timeout also causes it to require a PIN the next time. This way, a background process can’t loop infinitely until you tap the card for something else. It will be blocked by a PINEntry. Also, if your card always asks for a PIN, then there might be something shady happening that timeouts the card.
Miscellaneous useful commands
List connected YubiKeys and get information about them
brew install ykman # YubiKey configuration manager
ykman list
YubiKey 5C Nano (5.4.3) [FIDO+CCID] Serial: 20141XXX
Reset the OpenPGP application on the YubiKey
See Resetting the OpenPGP Application on the YubiKey – Yubico.
gpg --card-edit
then admin
, factory-reset
and y
.
Example output
gpg --card-edit
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: D276000124010000000620141XXX0000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 20141XXX
Name of cardholder: Doe John
Language prefs ...: fr
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: ed25519 cv25519 ed25519
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 3
KDF setting ......: off
UIF setting ......: Sign=on Decrypt=on Auth=on
Signature key ....: DBCC F949 18B2 4D1A 0053 AD43 A2E6 18E2 0456 625F
created ....: 2023-12-12 22:01:54
Encryption key....: B5FF 206E 44D0 20AE 6CC5 30C1 5247 7AF6 A631 E77F
created ....: 2023-12-12 22:18:06
Authentication key: 5706 1CDF 8DC3 15ED 81A4 1DCC 37A9 02F0 30B4 4E05
created ....: 2023-12-12 22:20:20
General key info..: sub ed25519/A2E618E20456625F 2023-12-12 John Doe (General purpose personal key) <john.doe@example.org>
sec ed25519/A6D3E049FD2A88C4 created: 2023-12-12 expires: never
ssb> ed25519/A2E618E20456625F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> cv25519/52477AF6A631E77F created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
ssb> ed25519/37A902F030B44E05 created: 2023-12-12 expires: 2024-12-11
card-no: 0006 20141XXX
gpg/card> admin
Admin commands are allowed
gpg/card> factory-reset
gpg: OpenPGP card no. D276000124010000000620141XXX0000 detected
gpg: Note: This command destroys all keys stored on the card!
Continue? (y/N) y
Really do a factory reset? (enter "yes") yes
gpg/card> list
Reader ...........: Yubico YubiKey FIDO CCID
Application ID ...: D276000124010000000620141XXX0000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 20141XXX
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]
gpg/card>
References
To write this article, I used various sources:
- Using Your YubiKey with OpenPGP – Yubico
- Lists compatible devices and explains the core logic of the process described in this article
- drduh/YubiKey-Guide: Guide to using YubiKey for GPG and SSH
- Explains everything for every platform
- Setting up GnuPG + Yubikey on NixOS for SSH authentication
- Very complete, lots of good explanations, very close to this article finally (wish I had found it sooner)
and manuals:
-
From GPG Configuration Options:
↩︎--expert
,--no-expert
Allow the user to do certain nonsensical or “silly” things like signing an expired or revoked key, or certain potentially incompatible things like generating unusual key types. This also disables certain warning messages about potentially incompatible actions. As the name implies, this option is for experts only. If you don’t fully understand the implications of what it allows you to do, leave this off.--no-expert
disables this option. -
From gpg-card:
↩︎UIF N [on|off|permanent]
Change the User Interaction Flag. That flags tells whether the confirmation button of a token shall be used. n must in the range 1 to 3. “permanent” is the same as “on” but the flag can’t be changed anmore.