Yubikey GPG

Setup GPG with Yubikey

Intro

This guide is based on drduh/YubiKey-Guide and how I setup my YubiKeys. I differ on some parts for example I use ed25519.

Prerequisites

Previous articles:

Setup

Insert and boot the USB (air-gapped system without network capabilities)

Identity

export IDENTITY="dohjon <[email protected]>"

Expiration

export EXPIRATION=2y

Passphrase

Write down the passphrase L4QC-272D-JHZZ-G4B6-VH83-62YC

export CERTIFY_PASS=$(LC_ALL=C tr -dc 'A-Z1-9' < /dev/urandom | \
  tr -d "1IOS5U" | fold -w 30 | sed "-es/./ /"{1..26..5} | \
  cut -c2- | tr " " "-" | head -1) ; echo "\n$CERTIFY_PASS\n"

Create Certify key (PRIVATE KEY)

gpg --batch --passphrase "$CERTIFY_PASS" --quick-generate-key "$IDENTITY" ed25519 cert never
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 }')
 
# save and view private key (Certify key) identifier and fingerprint for use later
printf "\nKey ID: %40s\nKey FP: %40s\n\n" "$KEYID" "$KEYFP"

Create subkeys for signing, encryption, and authentication

gpg --batch --pinentry-mode=loopback --passphrase "$CERTIFY_PASS" --quick-add-key $KEYFP ed25519 sign 2y
gpg --batch --pinentry-mode=loopback --passphrase "$CERTIFY_PASS" --quick-add-key $KEYFP cv25519 encr 2y
gpg --batch --pinentry-mode=loopback --passphrase "$CERTIFY_PASS" --quick-add-key $KEYFP ed25519 auth 2y
 
# List available secret keys
gpg --list-secret-keys

Backup keys

Save a copy of the Certify key, Subkeys and public key:

gpg --output $GNUPGHOME/$KEYID-Certify.key \
    --batch --pinentry-mode=loopback --passphrase "$CERTIFY_PASS" \
    --armor --export-secret-keys $KEYID
 
gpg --output $GNUPGHOME/$KEYID-Subkeys.key \
    --batch --pinentry-mode=loopback --passphrase "$CERTIFY_PASS" \
    --armor --export-secret-subkeys $KEYID
 
gpg --output $GNUPGHOME/$KEYID-$(date +%F).asc \
    --armor --export $KEYID

Insert USB and decrypt and mount the encrypted LUKS volume(s). Then copy from live usb to backup usb

lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# Copy from live usb to backup usb
sudo cp -av $GNUPGHOME /mnt/encrypted-storage/
 
# Unmount and close the encrypted volume
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets

Transfer Subkeys

# transfer Signature key
gpg --edit-key $KEYID
key 1 # Usage: S
keytocard
1
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
# transfer Authentication key
gpg --edit-key $KEYID
key 2 # Usage: A
keytocard
3
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
 
# transfer Encryption key
gpg --edit-key $KEYID
key 3 # Usage: E
keytocard
2
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
 
# Verify
gpg --list-secret-keys
 
# The `>` after a tag indicates the key is stored on a smart card.
 
$ sec   rsa4096/0xF0F2CFEB04341FB5 2024-01-01 [C]
$       Key fingerprint = 4E2C 1FA3 372C BA96 A06A  C34A F0F2 CFEB 0434 1FB5
$ uid                   [ultimate] YubiKey User <yubikey@example>
$ ssb>  rsa4096/0xB3CD10E502E19637 2024-01-01 [S] [expires: 2026-05-01]
$ ssb>  rsa4096/0x30CBE8C4B085B9F7 2024-01-01 [E] [expires: 2026-05-01]
$ ssb>  rsa4096/0xAD9E24E1B8CB9600 2024-01-01 [A] [expires: 2026-05-01]

Configure touch

Finally, configure the Yubikey to require a touch to sign or decrypt anything. This avoids any random program from using the key without manual approval.

# help
ykman openpgp keys set-touch -h
 
# authentication
ykman openpgp keys set-touch aut on
# signature
ykman openpgp keys set-touch sig on
# encryption
ykman openpgp keys set-touch dec on

Upload to public server

After on laptop with internet connection I inserted yubikey and uploaded public key to keyserver keys.openpgp.org

gpg --export [email protected] | curl -T - https://keys.openpgp.org
 
# after adding key to public server we can add the public key URL to the yubikey itself

Add GPG key to backup yubikey(s)

Insert drduh isolated live USB.

Importing to keys to the yubikey will delete the PGP keys and only leave a stub so we need to redo the process in combination with our backup.

It is now time to repeat some steps.

# Insert USB and decrypt and mount the encrypted LUKS volume(s).
lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# create tmp dir and copy from backup usb to tmp dir
export GNUPGHOME=$(mktemp -d -t gnupg-$(date +%Y-%m-%d)-XXXX)
cd $GNUPGHOME
cp -avi /mnt/encrypted-storage/gnupg/* $GNUPGHOME
 
# Unmount and close the encrypted volume
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets
# remove backup usb
 
# Verify
gpg --list-secret-keys
 
# Assign needed variables again
export IDENTITY="dohjon <[email protected]>"
export CERTIFY_PASS="<REDACTED>"
export ADMIN_PIN="<REDACTED>"
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 }')
echo $KEYID $KEYFP
 
 
# Insert backup yubikey and transfer subkeys
gpg --list-secret-keys
 
# transfer Signature key
gpg --edit-key $KEYID
key 1 # Usage: S
keytocard
1
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
# transfer Authentication key
gpg --edit-key $KEYID
key 2 # Usage: A
keytocard
3
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
 
# transfer Encryption key
gpg --edit-key $KEYID
key 3 # Usage: E
keytocard
2
# follow prompt enter CERTIFY_PASS and ADMIN_PIN
save
 
# Verify
gpg --list-secret-keys
 
# The `>` after a tag indicates the key is stored on a smart card.
 
$ sec   rsa4096/0xF0F2CFEB04341FB5 2024-01-01 [C]
$       Key fingerprint = 4E2C 1FA3 372C BA96 A06A  C34A F0F2 CFEB 0434 1FB5
$ uid                   [ultimate] YubiKey User <yubikey@example>
$ ssb>  rsa4096/0xB3CD10E502E19637 2024-01-01 [S] [expires: 2026-05-01]
$ ssb>  rsa4096/0x30CBE8C4B085B9F7 2024-01-01 [E] [expires: 2026-05-01]
$ ssb>  rsa4096/0xAD9E24E1B8CB9600 2024-01-01 [A] [expires: 2026-05-01]
 
Done, remove yubikey and reboot

Renewing subkeys by updating expiration

To update the expiration of the subkeys we need the offline certify key.

Insert USB and decrypt and mount the encrypted LUKS volume(s)

lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# create tmp dir and copy from backup usb to tmp dir
export GNUPGHOME=$(mktemp -d -t gnupg-$(date +%Y-%m-%d)-XXXX)
cd $GNUPGHOME
cp -avi /mnt/encrypted-storage/gnupg/* $GNUPGHOME
 
# Confirm the identify is available
gpg --list-secret-keys
 
# export required values
export IDENTITY="dohjon <[email protected]>"
export EXPIRATION=2y
export CERTIFY_PASS=<REDACTED>
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 }')
 
# renew the subkeys
echo "$CERTIFY_PASS" | gpg --batch --pinentry-mode=loopback \
  --passphrase-fd 0 --quick-set-expire "$KEYFP" "$EXPIRATION" \
  $(gpg -K --with-colons | awk -F: '/^fpr:/ { print $10 }' | tail -n "+2" | tr "\n" " ")
 
# export the updated public key to backup usb
gpg --output /mnt/encrypted-storage/gnupg/$KEYID-$(date +%F).asc --armor --export $KEYID
# (optional) remove the previous public key
 
# Unmount and close the encrypted volume
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets
 
# Insert the second backup USB and copy the updated public key to it as well
lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# export the updated public key to backup usb
gpg --output /mnt/encrypted-storage/gnupg/$KEYID-$(date +%F).asc --armor --export $KEYID
# (optional) remove the previous public key
 
# Unmount and close the encrypted volume
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets

Insert

Extende subkeys expiration

# Create new empty tmp folder and point GNUPGHOME to it
export GNUPGHOME=$(mktemp -d -t $(date +%Y.%m.%d)-XXXX)
 
# Insert USB backup device 1 and decrypt then mount
lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# Copy everything from USB to tmp folder
cp -avi /mnt/encrypted-storage/gnupg/* $GNUPGHOME/
cd $GNUPGHOME
 
# Insert yubikey
gpg --card-status
gpg --fingerprint
 
# Export required variables to avoid typing and for use in scripting
export IDENTITY="doh jon <[email protected]>"
export EXPIRATION="2026-07-29"
export CERTIFY_PASS="<REDACTED>"
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 }')
 
# Update expiration date on subkeys
echo "$CERTIFY_PASS" | gpg --batch --pinentry-mode=loopback \
  --passphrase-fd 0 --quick-set-expire "$KEYFP" "$EXPIRATION" \
  $(gpg -K --with-colons | awk -F: '/^fpr:/ { print $10 }' | tail -n "+2" | tr "\n" " ")
 
# Export the updated public key
gpg --armor --export $KEYID | sudo tee /mnt/encrypted-storage/gnupg/$KEYID-$(date +%F).asc
 
# Unmount and close the encrypted volume then remove USB backup device 1
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets
 
# Insert USB backup device 2 and decrypt and mount
lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
 
# Export the updated public key
gpg --armor --export $KEYID | sudo tee /mnt/encrypted-storage/gnupg/$KEYID-$(date +%F).asc
 
# Unmount and close the encrypted volume then remove USB backup device 2
sudo umount /mnt/encrypted-storage
sudo cryptsetup luksClose gnupg-secrets

Upload to keyserver

I use keys.openpgp.org as keyserver usage

To configure GnuPG to use keys.openpgp.org as keyserver, add this line to your .gnupg/gpg.conf file:

keyserver hkps://keys.openpgp.org

Uploading your key

You can try this shortcut for uploading your key, which outputs a direct link to the verification page:

lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
cat /mnt/encrypted-storage/gnupg/<public-key> | curl -T - https://keys.openpgp.org

Alternatively, visit https://keys.openpgp.org/upload and upload public key manually

lsblk # sudo dmesg | tail
sudo cryptsetup luksOpen /dev/sdb1 gnupg-secrets
sudo mkdir -p /mnt/encrypted-storage
sudo mount /dev/mapper/gnupg-secrets /mnt/encrypted-storage
cat /mnt/encrypted-storage/gnupg/<public-key>

Retrieving keys

To locate the key of a user, by email address:

gpg --auto-key-locate keyserver --locate-keys [email protected]

To refresh all your keys (e.g. new revocation certificates and subkeys):

gpg --refresh-keys