OpenPGP With Yubikey
By hernil
OpenPGP with Yubikey
I’ve recently been wanting to use my Yubikey(s) a bit more actively. Or rather, I’ve considered them a potential solution to storing encrypted data publically. Requiring a physical key to unencrypt would balance the fact that the potentially sensitive data could be available online - albeit in an encrypted state.
I could of course create a super-long password, write it down, put it it my drawer and call it a day but as I already had two Yubikeys in somewhat active use as my 2fa for my password manager login I figured it was time to explore an unused part of the Yubikeys.
Disclaimer: I am in no way a crypto expert. These are my notes and the steps I followed to accomplish what I wanted. There might be flaws or fallacies in every step.
The steps we’re going to follow are roughly as follows:
- Generate a set of keys
- Back them up
- Configure Yubikey(s)
- Install them on Yubikey(s)
- Secure our key backup
- Publish our key to a key server
Prerequisites
- One or more Yubikeys
- A computer - preferably brand new from store, air-gapped, bullet proof and dipped in holy water (depending on threat-model)
That’s more or less it. Here is some software you should make sure is installed (most of it is there out of the box on Ubuntu 24.04):
sudo apt install pcscd opensc
sudo apt install gnupg gpg gpg-agent dirmngr scdaemon
sudo apt install yubikey-manager
There seems to be a quirk with Ubuntu 24.04. If some of these commands later return something about not finding a Yubikey, try adding the following line to ~/.gnupg/scdaemin.conf
disable-ccid
Generating keys
I’m not going to claim to understand everything around OpenPGP and how the key-management works but here is a few observations I’ve made:
- A key can (and will) have subkeys - usually related to various actions it can perform
- The key types (or actions) are:
- Certify - for issuing new (sub)keys
- Signing - for signing documents or data. ie. validating it’s origin
- Authentication - I am me
- Encryption - for … encryption
- When putting a key on a physical Yubikey
gpgwill delete it from the local keyring. It’s amove, not acopyoperation
ECC/EC25519/ED25519/ecdsa
This subtitle is probably infuriating to someone that knows their crypto. As far as I can understand they’re somewhat different uses or applications of elliptic curves. I think. This is not legal advice !
In additions to key types based on actions there are various algorithms available. The most common one out there in tutorials is the use of RSA. It’s old, tried and tested, and highly available wherever you are likely to use your keys. It is however recommended to use a pretty substantial key-size for future proofing (4096 bits is commonly recommended at the time of writing). I don’t think there’s a lot of practical drawbacks to this so it might be what you want to go for.
For me I prefer to lean a bit more modern wherever I can and I settled on the EC25519 key type. It’s been around long enough that it’s usable mostly everywhere (f. you in particular Azure devops - glad I don’t use you anymore) and it requires a much shorter key length to achieve similar or better security than RSA.
It’s also the default in recent versions of gpg so that’s probably one of the better arguments for it.
Generation
gpg --expert --full-gen-key
Choose the defaults, fill in your information (note: I suggest using a valid email from a domain that you controll - we’ll come back to that) and it should look something like this:
See console interaction
➜ temp gpg --expert --full-generate-key
gpg (GnuPG) 2.4.7; Copyright (C) 2024 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.
Please select what kind of key you want:
(1) RSA and RSA
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(9) ECC (sign and encrypt) *default*
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(13) Existing key
(14) Existing key from card
Your selection?
Please select which elliptic curve you want:
(1) Curve 25519 *default*
(2) Curve 448
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(6) Brainpool P-256
(7) Brainpool P-384
(8) Brainpool P-512
(9) secp256k1
Your selection?
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: John Doe
Email address: john@doe.com
Comment: Yubikey
You selected this USER-ID:
"John Doe (Yubikey) <john@doe.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
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.
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: revocation certificate stored as '/home/johndoe/.gnupg/openpgp-revocs.d/703410BB4D0A8CD31361C6979DCB9F0519671463.rev'
public and secret key created and signed.
pub ed25519 2025-01-10 [SC]
703410BB4D0A8CD31361C6979DCB9F0519671463
uid John Doe (Yubikey) <john@doe.com>
sub cv25519 2025-01-10 [E]
We’re going to manipulate that key with that fingerprint a fair bit so let’s put it in a variable:
export KEYFP=703410BB4D0A8CD31361C6979DCB9F0519671463
Notice that there’s a key for Signing and Certify as well as a subkey for Encryption but nothing for Authentication. So we’ll go in and create that manually.
gpg --expert --edit-key $KEYFP
See console interaction
➜ temp gpg --expert --edit-key $KEYFP
gpg (GnuPG) 2.4.7; Copyright (C) 2024 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.
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 37 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 37u
sec ed25519/9DCB9F0519671463
created: 2025-01-10 expires: never usage: SC
trust: ultimate validity: ultimate
ssb cv25519/6CED9F74ABFF984E
created: 2025-01-10 expires: never usage: E
[ultimate] (1). John Doe (Yubikey) <john@doe.com>
gpg> addkey
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(12) ECC (encrypt only)
(13) Existing key
(14) Existing key from card
Your selection? 11
Possible actions for this ECC key: Sign Authenticate
Current allowed actions: Sign
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? s
Possible actions for this ECC key: Sign Authenticate
Current allowed actions:
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? a
Possible actions for this ECC key: Sign Authenticate
Current allowed actions: Authenticate
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? q
Please select which elliptic curve you want:
(1) Curve 25519 *default*
(2) Curve 448
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(6) Brainpool P-256
(7) Brainpool P-384
(8) Brainpool P-512
(9) secp256k1
Your selection?
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
Really create? (y/N) y
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.
sec ed25519/9DCB9F0519671463
created: 2025-01-10 expires: never usage: SC
trust: ultimate validity: ultimate
ssb cv25519/6CED9F74ABFF984E
created: 2025-01-10 expires: never usage: E
ssb ed25519/3B5E5CBD3AB2875A
created: 2025-01-10 expires: never usage: A
[ultimate] (1). John Doe (Yubikey) <john@doe.com>
gpg> save
Seeing as I will offload the keys to a Yubikey I also created a subkey for signing in the same way. Ending up with 4 subkeys.
Backing up keys to files
As mentioned, putting keys on a Yubikey is a move operation and I want to put them on two separate Yubikeys for backup purposes. Therefore I’ll make a backup of the keys first.
gpg --armor --export $KEYFP > public-keys.asc
gpg --armor --export-secret-keys $KEYFP > secret-keys.asc
Configure Yubikeys
You’ll want to edit the Yubikey pins from the defaults. There’s also a few other settings you can change if you want.
gpg --card-edit
The YubiKey’s default user pin is 123456, the default admin pin is 12345678, and the default reset PIN is unset. The minimum pin-length is 6 and 8 for the admin pin.
gpg/card> admin
Admin commands are allowed
gpg/card> passwd
The pin is what you will provide to perform actions with the keys stored on the Yubikey so use something you can remember as you’ll use that recularly. The admin pin is used to change settings like these ones and you can probably just put that in a password manager.
Move keys to Yubikeys
gpg --edit-key $KEYFP
Then move each key one by one. The pattern is
- select key
- move key
- unselect key
- repeat for next key
gpg> key 1
gpg> keytocard
Please select where to store the key:
(1) Signature key
(3) Authentication key
Your selection? 1
gpg> key 1
repeat for key 2 etc.
Then save
gpg> save
If you want to repeat this for another Yubikey it’s time to unplug the first one. Then delete references to the key from gpg and reimport it from scratch.
gpg --delete-secret-and-public-key $KEYFP
gpg --import secret-keys.asc
Then repeat the steps to move your keys to the second Yubikey.
Publishing the key
Publishing the public part of your keys to a keyserver allows others to find them so they can encrypt messages for you. Add the use of WKD - a way for a domain to specify which keyserver to look up keys on (article pending) and you have a pretty smooth mechanism for key-discovery.
gpg --export john@doe.com | curl -T - https://keys.openpgp.org
Then you should get a confirmation email or two and should be set.
Encrypt your key backup
If for some reason you’ll want to keep your exported keys for later (like for putting them on another future Yubikey) then you should probably encrypt them before storing. Then they can be decrypted using one of the existing Yubikeys you’ve set up.
gpg --encrypt --recipient john@doe.com secret-keys.asc > secret-keys.asc.gpg
Variations of this command can be used to encrypt messages from any machine with gpg installed as long as you set up WKD and published your key to the matching keyserver by the way. That one discovery was part of why I started this process in the first place!
Decrypt encrypted data
If it’s the first time using this Yubikey on a system you might have to execute this command to import the key reference from the keyserver.
gpg --locate-keys --auto-key-locate clear,nodefault,wkd john@doe.com
And if you’ve used another Yubikey on this system then gpg keeps a cache of “what key is on which Yubikey” and won’t look on your other one. So we need to tell it to not do that
gpg-connect-agent "scd serialno" "learn --force" /bye
Then you can decrypt your data using
gpg --decrypt secret-keys.asc.gpg
Future work
There should be a way to forward the gpg-agent to a remote system over ssh allowing for a Yubikey to decrypt data there. I’ll look into that soon.
I’d like to use this together with git to store sensitive files “publically” as well. More on that later I guess.
Sources
https://www.designed-cybersecurity.com/tutorials/openpgp-keys-on-yubikey/
https://blog.apdu.fr/posts/2024/04/gnupg-and-pcsc-conflicts-episode-2/