In this blog post we will deep dive into creating PGP keys using our Yubikey. As always, a little bit of history about PGP (Pretty Good Privacy): it is a software which was originally written by Phil Zimmermann in 1991. PGP uses hashing, data compression, symmetric-key cryptography and public-key cryptography to encrypt and decrypt information. The most common usage of PGP is to encrypt emails, however, PGP can also be used to encrypt files, directories, disk partitions, file signing and document verification. We will go through each use case while creating our own PGP keys using Yubikey.

Requirements

  • Yubikey with OpenGPG capability
  • USB to boot Tails OS
  • Laptop (better two - one air-gapped and one with Internet connection)
  • SD or USB to backup GPG keys

What’s the difference between PGP and GPG?

Basically, over the years PGP has been developed, improved and updated. It became widely adopted, which then was necessary to create an open standard that allowed PGP to be used in software, typically free to the public. The OpenPGP was standardized by IETF (Internet Engineering Task Force) and definied by RFC4880. GPG which stands for Gnu Privacy Guard is the implementation of OpenPGP. From functionality prospective both PGP and GPG are identical, however they differ in the licensing.

Getting started

Before creating cyptographic keys it’s important to create a secure environment. In my case, I will use a dedicated air-gapped system with no WI-FI, bluetooth or disk where I will mount a live image from Tails and generate the masterkey and create subkeys which will be stored in the Yubikey. This scenario could be an overkill, however, it’s important to do so: if your keys are lost, hacked and not securely stored this could result in a a disaster.

For example this recent case where a bitcoin core developer lost 216 BTC due to ‘PGP compromise’:

Secure environment

Based on your threat model, setup a secure environment. I will be performing below steps in an air-gapped laptop. You don’t need to have a similar setup to follow along.

I have physically removed the WI-FI card, Bluetooth card and the SSD from this laptop. It will only run the bootable live image of Tails via the USB.

Let’s download the latest tails os. I will run the following commands in my laptop running Ubuntu.

wget https://download.tails.net/tails/stable/tails-amd64-5.8/tails-amd64-5.8.img

Now let’s download the public key and the PGP signature of the ISO image.

wget https://tails.boum.org/tails-signing.key  # Public key aka signing key
wget https://tails.boum.org/torrents/files/tails-amd64-5.8.img.sig  # PGP signature

Document verification

In this step, we will check if we have downloaded a genuine ISO image by verifying the PGP signature. The verification process is described in the below graph:

%%{ init: { 'theme': 'base', 'themeVariables': { 'primaryColor': '#512163', 'primaryTextColor': '#fff', 'primaryBorderColor': '#a812e0', 'tertiaryColor': '#fff' } } }%% sequenceDiagram participant Developer participant User Developer->>Developer: Creates the ISO image Developer->>Developer: Calculate hash of the ISO image Developer->>Developer: Encrypt hash with private key Developer->>User: Host the image on the official website and upload the digital signature User->>User: Decrypt digital signature with developer's public key User->>User: Calculate hash of received ISO image User->>User: Compare hash of received file with decrypted hash User->>User: Verify ISO image

To verify if we have downloaded a genuine ISO file, first let’s view the public key’s fingerprint.

gpg --show-keys tails-signing.key
pub   rsa4096 2015-01-18 [C] [expires: 2024-01-04]
      A490D0F4D311A4153E2BB7CADBB802B258ACD84F
uid                      Tails developers <tails@boum.org>
uid                      Tails developers (offline long-term identity key) <tails@boum.org>
sub   rsa4096 2015-01-18 [S] [expired: 2018-01-11]
sub   rsa4096 2015-01-18 [S] [expired: 2018-01-11]
sub   rsa4096 2015-01-18 [S] [revoked: 2015-10-29]
sub   rsa4096 2016-08-30 [S] [expired: 2018-01-11]
sub   rsa4096 2017-08-28 [S] [expires: 2024-01-04]
sub   rsa4096 2017-08-28 [S] [revoked: 2020-05-29]
sub   ed25519 2017-08-28 [S] [expires: 2024-01-04]
sub   rsa4096 2018-08-30 [S] [revoked: 2021-10-14]
sub   rsa4096 2021-10-14 [S] [expires: 2024-01-04]

Then we must import this key to our GPG public keyring:

gpg --import tails-signing.key
gpg: key DBB802B258ACD84F: 2172 signatures not checked due to missing keys
gpg: key DBB802B258ACD84F: "Tails developers <tails@boum.org>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

Finally, to verify the downloaded ISO image you can run:

gpg --verify tails-amd64-5.8.img.sig tails-amd64-5.8.img
gpg: Signature made lun 19 dic 2022, 11:28:38 CET
gpg:                using EDDSA key CD4D4351AFA6933F574A9AFB90B2B4BD7AED235F
gpg: Good signature 👈 from "Tails developers <tails@boum.org>" [unknown]
gpg:                 aka "Tails developers (offline long-term identity key) <tails@boum.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: A490 D0F4 D311 A415 3E2B  B7CA DBB8 02B2 58AC D84F
     Subkey fingerprint: CD4D 4351 AFA6 933F 574A  9AFB 90B2 B4BD 7AED 235F

Good signature 👍 means that the signature on the file was made by a private key of the person that claims to have created the file, and that the file has not been tampered with since it was signed.

what’s the difference between checking the checksum and verifying the pgp signature?

A checksum, aka hashsum, is a fixed-size string of characters that is the result of running a hashing algorithm on a file. It is used to verify that the contents of a file have not been modified by comparing the calculated hash on the downloaded file to the original hash provided by the developers. On the other hand, PGP signature is a digital signature created using the PGP encryption software. It is used to verify the authenticity and integrity of a file, as well as the identity of the entity who signed it. In summary, hashsum is used to check if the file is tampered or not, while PGP signature is used to verify the identity of the person who signed the file and its integrity.

Now that we have verified the ISO file, we can move forward to creating the bootable USB image with the Tails ISO file. Let’s insert the USB key where we want to flash Tails to our Ubuntu system, and list all USB devices attached:

lsblk

My USB is at /dev/sdd. We will use dd to create an exact copy of the ISO file into our USB device.

sudo dd if=tails-amd64-5.8.img of=/dev/sdd bs=4M status=progress ; sync

Note: If you copy the ISO image to a partition of the USB drive and not the USB drive itself, it could cause some issue during the booting process with GnuGrub. There are some work-arounds like adding an option into dd command or loading the correct partition to boot

Setup Yubikey

Let’s prepare our Yubikey and configure it using Yubikey manager in our Ubuntu computer connected to Internet.

sudo add-apt-repository ppa:yubico/stable && sudo apt-get update -y && sudo apt install yubikey-manager -y

Once you complete installing Yubikey Manager (ykman), insert your Yubikey into the system and run ykman info. You should be able to see details of your Yubikey:

ykman info
Device type: YubiKey 5 NFC
Serial number: 224*****
Firmware version: 5.***
Form factor: Keychain (USB-A)
Enabled USB interfaces: OTP, FIDO, CCID
NFC transport is enabled.

Applications    USB     NFC
OTP             Enabled Enabled
FIDO U2F        Enabled Enabled
FIDO2           Enabled Enabled
OATH            Enabled Enabled
PIV             Enabled Enabled
OpenPGP         Enabled Enabled
YubiHSM Auth    Enabled Enabled

In this article we want to configure openpgp, you can run ykman openpgp info and you will see details related to openpgp.

ykman openpgp info
OpenPGP version:            3.4
Application version:        5.4.3
PIN tries remaining:        3
Reset code tries remaining: 0
Admin PIN tries remaining:  3
Require PIN for signature:  Once
Touch policies:
  Signature key:      Off
  Encryption key:     Off
  Authentication key: Off
  Attestation key:    Off

Let’s set user interaction settings for signing, encryption and authentication operations. Each time you will perform the above operations, you will have to touch your Yubikey.

The default admin PIN is1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣.
The default user PIN is1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣.

We will reset these default values in the air-gapped Tails computer later.

ykman openpgp keys set-touch sig on --force
Enter Admin PIN: 1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣
ykman openpgp keys set-touch enc on --force
Enter Admin PIN: 1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣
ykman openpgp keys set-touch aut on --force
Enter Admin PIN: 1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣
ykman openpgp keys set-touch att on --force
Enter Admin PIN: 1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣
ykman openpgp info
OpenPGP version:            3.4
Application version:        5.4.3
PIN tries remaining:        3
Reset code tries remaining: 0
Admin PIN tries remaining:  3
Require PIN for signature:  Once
Touch policies:
  Signature key:      On
  Encryption key:     On
  Authentication key: On
  Attestation key:    On

Now we have our Yubikey configured to use touch policy for GPG and the Tails OS flashed into the USB device. Let’s attach this bootable USB into the air-gapped laptop and start creating our own PGP keys with Yubikey.

Once you boot Tails, you will receive the following welome screen. Setup tails as you prefer based on your threat model:

Generate master and sub keys

Let’s create a directory where you want to save the keys temporarly.

mkdir -p /home/amnesia/Desktop/keys/backup

Let’s start by generating a random string, which will be used as a password to generate GPG keys and save it in the directory.

gpg --gen-random --armor 0 24 > Desktop/keys/backup/password.txt

Copy the password into the clipboard, because during the PGP key generation we will be requested to insert a password.

gpg --homedir Desktop/keys/.gnupg --expert --full-generate-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory '/home/amnesia/Desktop/keys/.gnupg' created
gpg: keybox '/home/amnesia/Desktop/keys/.gnupg/pubring.kbx' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (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 and ECC
  (10) ECC (sign only)
  (11) ECC (set your own capabilities) 👈
  (13) Existing key
  (14) Existing key from card
Your selection? 1️⃣1️⃣

Possible actions for a ECDSA/EdDSA key: Sign Certify Authenticate
Current allowed actions: Sign Certify

   (S) Toggle the sign capability 👈
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? S 👈

Possible actions for a ECDSA/EdDSA key: Sign Certify Authenticate
Current allowed actions: Certify

   (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 👈
   (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? 1️⃣
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) 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:
You selected this USER-ID:
    "John Doe <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.
gpg: /home/amnesia/Desktop/keys/.gnupg/trustdb.gpg: trustdb created
gpg: key 494155FAE35A085A marked as ultimately trusted
gpg: directory '/home/amnesia/Desktop/keys/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/amnesia/Desktop/keys/.gnupg/openpgp-revocs.d/9B1BDD3CEC992CF833326964494155FAE35A085A.rev'
public and secret key created and signed.

Let’s generate subkeys from our master key:

gpg --homedir Desktop/keys/.gnupg --expert --edit-key john@doe.com
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
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:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
sec  ed25519/494155FAE35A085A
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
[ultimate] (1). John Doe (Cyber Security Professional) <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? 1️⃣0️⃣
Please select which elliptic curve you want:
   (1) Curve 25519 👈
   (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? 1️⃣
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) 2y 👈
Key expires at lun 20 gen 2025, 18:18:58 UTC
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/494155FAE35A085A
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/A0E21195BF613045
     created: 2023-01-21  expires: 2025-01-20  usage: S
[ultimate] (1). John Doe <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? 1️⃣2️⃣
Please select which elliptic curve you want:
   (1) Curve 25519 👈
   (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? 1️⃣
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) 2y 👈
Key expires at lun 20 gen 2025, 18:20:27 UTC
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/494155FAE35A085A
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/A0E21195BF613045
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/B33836B3D076973E
     created: 2023-01-21  expires: 2025-01-20  usage: E
[ultimate] (1). John Doe (Cyber Security Professional) <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? 1️⃣1️⃣

Possible actions for a ECDSA/EdDSA 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 a ECDSA/EdDSA key: Sign Authenticate
Current allowed actions:

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability 👈
   (Q) Finished

Your selection? A 👈

Possible actions for a ECDSA/EdDSA 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 👈
   (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? 1️⃣
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) 2y 👈
Key expires at lun 20 gen 2025, 18:21:55 UTC
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/494155FAE35A085A
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/A0E21195BF613045
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/B33836B3D076973E
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/44F1D1AB38A0991B
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> save 👈

Backup Keys

Now you can insert your backup storage device (SD card or USB device) to the air-gapped computer. Then use disks application to view the devices attached.

Note: Based on your threat model and use-cases, you can use VeraCrypt (an open source cross-platform tool to encrypt your backup storage device). It also has some additional features like hidden volumes which could add plausible deniability and support for wide range of encryption algorithms. In my case, I will use LUKS (Linux Unified Key Setup) to encrypt my storage device.

Remember to store the password for the backup storage in a secure way.

Let’s backup our keys:

gpg --homedir Desktop/keys/.gnupg --armor --export john@doe.com > Desktop/keys/backup/pub.key
gpg --homedir Desktop/keys/.gnupg --export-ssh-key john@doe.com > Desktop/keys/backup/ssh-pub.key
gpg --homedir Desktop/keys/.gnupg --armor --export-secret-keys john@doe.com > Desktop/keys/backup/master.key
gpg --homedir Desktop/keys/.gnupg --armor --export-secret-subkeys john@doe.com > Desktop/keys/backup/sub.key
gpg --homedir Desktop/keys/.gnupg --fingerprint --fingerprint john@doe.com > Desktop/keys/backup/fingerprint.txt
gpg --homedir Desktop/keys/.gnupg --gen-revoke john@doe.com > Desktop/keys/backup/revoke.asc
sec  ed25519/14853C33314E8B58 2023-01-21 John Doe <john@doe.com>

Create a revocation certificate for this key? (y/N) y 👈
Please select the reason for the revocation:
  0 = No reason specified 👈
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 0️⃣
Enter an optional description; end it with an empty line:
> 
Reason for revocation: No reason specified
(No description given)
Is this okay? (y/N) y 👈
ASCII armored output forced.
Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable.  But have some caution:  The print system of
your machine might store the data and make it available to others!

Note: To use a GPG revoke.asc file, you will first need to import it into your keychain using the command gpg --import revoke.asc. Once the key is imported, you can use the command gpg --gen-revoke [key-id] to generate the revocation certificate. This certificate can then be distributed to key servers, or manually shared with other people, so that they can also revoke the key.

Let’s create a tar.gz file from the backup directory and move the content into our encrypted backup storage device.

cd Desktop/keys

tar czf GPGdata.tar.gz backup

mv GPGdata.tar.gz /media/amnesia/Backup/

Now let’s restart our air-gapped Tails OS and restore the keys from the secured backup storage and push the subkeys to Yubikey.

Restore GPG keys from backup

Once your Tails system is rebooted, using file application open the LUKS encrypted backup storage. Copy the GPGdata.tar.gz into Desktop and extract the master.key and password.txt.

tar xzf GPGdata.tar.gz keys/backup/master.key
tar xzf GPGdata.tar.gz keys/backup/password.txt

Now copy the content of password.txt into clipboard:

cat password.txt

Let’s import the master.key into our keyring and finally push the subkeys to Yubikey.

gpg --import master.key 👈 paste the password when requested.

Note: Remember to alway push the GPG keys from a copy of the backup file as this process is one way only.

Now let’s connect our Yubikey to the air-gapped laptop and reset default Yubikey PIN values for Admin and User.

gpg --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240103040006224302300000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 22430230
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
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

gpg/card> admin 👈
Admin commands are allowed

gpg/card> passwd 👈
gpg: OpenPGP card no. D2760001240103040006224302300000 detected

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

Your selection? 3️⃣
PIN changed.

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

Your selection? 1️⃣
Error changing the PIN: Conditions of use not satisfied

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

Your selection? Q
gpg --edit-key john@doe.com
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
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/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> key 1 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb* ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

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

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb* ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> key 1 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335 👈
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> key 2 👈
sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb* cv25519/434B45A2E09EE335 👈
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

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

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb* cv25519/434B45A2E09EE335 👈
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> key 2 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C 👈
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> key 3 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb* ed25519/04E593B90548A50C 👈
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

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

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb* ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ultimate] (1). John Doe <john@doe.com>

gpg> save 👈

If you are having issue to run the above, try killing the gpg agent.

gpg-connect-agent /bye && gpgconf --kill gpg-agent
gpg --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240103040006224302300000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 22430230
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 ...: ed25519 cv25519 ed25519
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
Signature key ....: 63A4 4194 6758 69C4 AE2B  D956 041A A297 4D8B C23F
      created ....: 2023-01-21 18:29:21
Encryption key....: DB03 3A7A 987C AC24 6E1E  4956 434B 45A2 E09E E335
      created ....: 2023-01-21 18:29:48
Authentication key: C253 87D1 2D07 CE5D CDD8  F8FC 04E5 93B9 0548 A50C
      created ....: 2023-01-21 18:30:44
General key info..: sub  ed25519/041AA2974D8BC23F 2023-01-21 John Doe <john@doe.com>
sec   ed25519/14853C33314E8B58  created: 2023-01-21  expires: never
ssb>  ed25519/041AA2974D8BC23F  created: 2023-01-21  expires: 2025-01-20
                                card-no: 0006 22430230
ssb>  cv25519/434B45A2E09EE335  created: 2023-01-21  expires: 2025-01-20
                                card-no: 0006 22430230
ssb>  ed25519/04E593B90548A50C  created: 2023-01-21  expires: 2025-01-20
                                card-no: 0006 22430230
gpg --list-secret-keys joe@doe.com
/home/amnesia/Desktop/keys/.gnupg/pubring.kbx
----------------------------------------
sec   ed25519 2023-01-21 [C]
      36465A94CC6172AFD556D31B14853C33314E8B58
uid           [ultimate] John Doe <john@doe.com>
ssb▶  ed25519 2023-01-21 [S] [expires: 2025-01-20]
ssb▶  cv25519 2023-01-21 [E] [expires: 2025-01-20]
ssb▶  ed25519 2023-01-21 [A] [expires: 2025-01-20]

Finally you have your subkeys in the Yubikey.

(optional) Extend subkey expiry date

When your subkeys expire to extend the expiry date, first copy the GPGdata.tar.gz from the secured backup device to Desktop. Extract master.key and password.txt by running below:

tar xzf GPGdata.tar.gz keys/backup/master.key
tar xzf GPGdata.tar.gz keys/backup/password.txt

Import GPG master key:

gpg --import keys/backup/master.key 👈 insert the password when requested

Then edit the key by running:

gpg --edit-key john@doe.com
gpg (GnuPG) 2.2.27; Copyright (C) 2018 Free Software Foundation, Inc.
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/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: unknown       validity: unknown
ssb  ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ unknown] (1). John Doe john@doe.com

gpg> key 1 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb  cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ unknown] (1). John Doe <john@doe.com>

gpg> key 2 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb* cv25519/434B45A2E09EE335 👈
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb  ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ unknown] (1). John Doe <john@doe.com>

gpg> key 3 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/041AA2974D8BC23F 👈
     created: 2023-01-21  expires: 2025-01-20  usage: S
ssb* cv25519/434B45A2E09EE335 👈
     created: 2023-01-21  expires: 2025-01-20  usage: E
ssb* ed25519/04E593B90548A50C 👈
     created: 2023-01-21  expires: 2025-01-20  usage: A
[ unknown] (1). John Doe <john@doe.com>

gpg> expire 👈
Are you sure you want to change the expiration time for multiple subkeys? (y/N) y
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) 3y 👈
Key expires at Thu 21 Jan 2023 02:45:12 PM UTC
Is this correct? (y/N) y 👈

sec  ed25519/14853C33314E8B58
     created: 2023-01-21  expires: never       usage: C
     trust: unknown       validity: unknown
ssb* ed25519/041AA2974D8BC23F
     created: 2023-01-21  expires: 2026-01-21  usage: S
ssb* cv25519/434B45A2E09EE335
     created: 2023-01-21  expires: 2026-01-21  usage: E
ssb* ed25519/04E593B90548A50C
     created: 2023-01-21  expires: 2026-01-21  usage: A
[ unknown] (1). John Doe <john@doe.com>

gpg> save 👈

(optional) Yubikey factory-reset

If you forget your PIN numbers, you can always reset your Yubikey. Factory reset will permanently delete your store GPG keys.

gpg --card-edit
gpg/card> admin
gpg/card> factory-reset
gpg/card> y
gpg/card> yes

Additional references

  1. PGP Key Management Recommendations from NetBSD
  2. The German BSI - technical guideline: Cryptographic recommendations and Key Lengths