216 lines
6.9 KiB
Markdown
216 lines
6.9 KiB
Markdown
# GPG Keys with Multiple Yubikey
|
|
- - -
|
|
_"One yubikey is no yubikey"_ (an old proverb).
|
|
- - -
|
|
|
|
|
|
## TL;DR:
|
|
|
|
I use this key as of March 2026. My key Key ID/Fingerprint: 74FEDBB2 or [01B157D574FEDBB2](./01B157D574FEDBB2-2026-03-10.asc) or 645FA10143EFBE17108030F801B157D574FEDBB2
|
|
|
|
|
|
## Who is this for
|
|
|
|
Mostly myself, so I remember what I did.
|
|
|
|
## What are we trying to solve.
|
|
|
|
I recently bought 3 yubikeys, mainly to use with passphrases. But I also decided to store my "GPG key" on them. The problem I am trying to solve is that I want to be able to use 3 yubikeys for redundancy. One to always keep at home.
|
|
|
|
Two that I take with me for travel so that, if I loose one I can always continue doing business on the trip. I also want to make sure that when I loose a key, I do not have to revoke my master Identity.
|
|
|
|
|
|
## GPG and subkeys
|
|
|
|
When you generate a PGP key it will by default create an a 'Signing and Certification' [SC] and a 'Encryption' [E].
|
|
|
|
Idealy one would want to generate multiple Encryption subkeys, one for each yubikey. But, that will lead to drama because if Alice wants to send you something encrypted she has to know wich encryption subkey you have available at the time. So we equip all our yubikeys with the same encryption key. Which makes that if you loose one, you still have to revoke the whole lot.
|
|
|
|
|
|
|
|
## The workflow
|
|
|
|
### generate keys
|
|
|
|
We follow the recipee [here](https://github.com/drduh/YubiKey-Guide?tab=readme-ov-file) that creates a certiv
|
|
|
|
Generate a GPG key. We do this not on the yubikey but on a machine. We want to be able to backup the generated key.
|
|
|
|
```
|
|
export KEY_TYPE=Ed25519 # while we are at it we use a modern curve.
|
|
export IDENTITY="Olaf M. Kolkman <olaf@xolx.nl>"
|
|
export CERTIFY_PASS="<redacted-certfication-password>"
|
|
export EXPIRATION=9y # I have my reasons
|
|
```
|
|
|
|
Generate the Certificate:
|
|
|
|
```
|
|
echo "$CERTIFY_PASS" | \
|
|
gpg --batch --passphrase-fd 0 \
|
|
--quick-generate-key "$IDENTITY" "$KEY_TYPE" cert never
|
|
```
|
|
|
|
Make note of the stored revocation certificate. follow the recipee to set and view the fingerprints and keyid
|
|
|
|
|
|
```
|
|
export KEYID=$(gpg -k --with-colons "$IDENTITY" | \
|
|
awk -F: '/^pub:/ { print $5; exit }')
|
|
|
|
export KEYFP=$(gpg -k --with-colons "$IDENTITY" | \
|
|
awk -F: '/^fpr:/ { print $10; exit }')
|
|
|
|
printf "\nKey ID/Fingerprint: %20s\n%s\n\n" "$KEYID" "$KEYFP"
|
|
|
|
Key ID/Fingerprint: 01B157D574FEDBB2
|
|
645FA10143EFBE17108030F801B157D574FEDBB2
|
|
|
|
```
|
|
|
|
Now first eddit the key to add additional Identifiers and create an encryption ey
|
|
|
|
```
|
|
gpg --expert --edit-key 01B157D574FEDBB2
|
|
...
|
|
gpg> adduid
|
|
...
|
|
gpg> sign
|
|
gpg> addkey
|
|
<select ECC encrypt only (12)>
|
|
|
|
```
|
|
|
|
|
|
|
|
And generate 3 sets of signing and authentication subkeys
|
|
|
|
```
|
|
for i in {0..2}; do
|
|
|
|
echo creating signing key $i:
|
|
|
|
echo "$CERTIFY_PASS" | \
|
|
gpg --batch --pinentry-mode=loopback --passphrase-fd 0 \
|
|
--quick-add-key "$KEYFP" "${KEYTYPE}" sign "$EXPIRATION"
|
|
|
|
echo creating authentication key $i:
|
|
|
|
echo "$CERTIFY_PASS" | \
|
|
gpg --batch --pinentry-mode=loopback --passphrase-fd 0 \
|
|
--quick-add-key "$KEYFP" "${KEYTYPE}" auth "$EXPIRATION"
|
|
|
|
done
|
|
|
|
|
|
```
|
|
|
|
Now check what we generated
|
|
|
|
```
|
|
> gpg -k ${KEYFP}
|
|
|
|
pub ed25519 2026-03-10 [C]
|
|
645FA10143EFBE17108030F801B157D574FEDBB2
|
|
uid [ultimate] Olaf M. Kolkman <kolkman@isoc.org>
|
|
uid [ultimate] Olaf M. Kolkman <olaf@xolx.nl>
|
|
uid [ultimate] Olaf M. Kolkman <olaf@dacht.net>
|
|
sub cv25519 2026-03-10 [E] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [S] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [A] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [S] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [A] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [S] [expires: 2035-03-08]
|
|
sub ed25519 2026-03-10 [A] [expires: 2035-03-08]
|
|
```
|
|
|
|
|
|
|
|
|
|
Backup the key:
|
|
|
|
```
|
|
gpg --export-secret-key --armor 645FA10143EFBE17108030F801B157D574FEDBB2
|
|
```
|
|
store output safely.
|
|
|
|
|
|
### Transfer to the yubikey
|
|
|
|
You will need the `ykman` cli tool for this.
|
|
|
|
First make sure your yubikey is set up. For each of your yubikey do the following. (Note on macOSX if the tool doesn't respond to return, use ctr-j as return char)
|
|
|
|
```
|
|
ykman openpgp reset
|
|
...
|
|
ykman openpgp access change-admin-pin
|
|
...
|
|
ykman openpgp access change-pin
|
|
...
|
|
ykman openpgp access set-retries 5 5 5
|
|
```
|
|
|
|
After preparing the yubikeys copy the encryption key [E] to all yubikeys and one unique signing [S] and Authorization keys [A] to each yubikey. So we do now follow the yubikey recipee to the letter, since we do not export the primary keys signature key.
|
|
|
|
Carfully select the appropriate key by *toggling* `key <keyno>`. And choose the appropriate slots.
|
|
|
|
Validate with `ykman openpgp info` if you transfered the correct keys.
|
|
|
|
|
|
|
|
The resulting keys have been transfered like so:
|
|
|
|
```
|
|
Yubikey type SUBKey IO
|
|
--------------------------------------------------------
|
|
35775013 S ECCE B28A 6E5F EE99 D17A 8663 A20F 6458 99FA 4923
|
|
35775013 E 85B2 FBDA E6DC E92A 0676 07D7 9C82 2115 2E48 E8BE
|
|
35775013 A 3FC6 049C FC4C A286 C919 86B1 1B7F 003F 881B 9F3B
|
|
|
|
36431751 S E540 031A 32A2 B74A 45D0 7AFC FA69 0576 F726 C4C7
|
|
36431751 E 85B2 FBDA E6DC E92A 0676 07D7 9C82 2115 2E48 E8BE
|
|
36431751 A D464 A0DE 003C 561B 6677 6665 085A 93EB 5D85 EE54
|
|
|
|
36431811 S 80E1 9313 13F8 734F 10A4 92B7 D426 4719 8122 395F
|
|
36431811 E 85B2 FBDA E6DC E92A 0676 07D7 9C82 2115 2E48 E8BE
|
|
36431811 A B395 CE5B C4C3 759D C859 B68A EDFF 356F B975 5D1A
|
|
--------------------------------------------------------
|
|
```
|
|
|
|
|
|
### Transfer from the Yubikey
|
|
|
|
|
|
|
|
Now comes the scary bit: Delete the secret key. And immediatly reimport it from the yubikey:
|
|
```
|
|
gpg --delete-secret-keys ${KEYID}
|
|
gpg --card-edit fetch
|
|
```
|
|
|
|
That results in a table where one can see which keys are now available.
|
|
|
|
```
|
|
sub ed25519/A20F645899FA4923 2026-03-10 Olaf M. Kolkman <kolkman@isoc.org>
|
|
sec# ed25519/01B157D574FEDBB2 created: 2026-03-10 expires: never
|
|
ssb> cv25519/9C8221152E48E8BE created: 2026-03-10 expires: 2035-03-08
|
|
card-no: 0006 35775013
|
|
ssb# ed25519/D42647198122395F created: 2026-03-10 expires: 2035-03-08
|
|
ssb# ed25519/EDFF356FB9755D1A created: 2026-03-10 expires: 2035-03-08
|
|
ssb> ed25519/A20F645899FA4923 created: 2026-03-10 expires: 2035-03-08
|
|
card-no: 0006 35775013
|
|
ssb> ed25519/1B7F003F881B9F3B created: 2026-03-10 expires: 2035-03-08
|
|
card-no: 0006 35775013
|
|
ssb# ed25519/FA690576F726C4C7 created: 2026-03-10 expires: 2035-03-08
|
|
ssb# ed25519/085A93EB5D85EE54 created: 2026-03-10 expires: 2035-03-08
|
|
|
|
```
|
|
|
|
Any time you change the yubikey you use you have to delete (`gpg --delete-key`) and refetch the key (`gpg --card-edit fetch`).
|
|
|
|
|
|
|
|
|
|
|