Skip to content

How to store a GPG key on a YubiKey

Posted on:November 15, 2023 at 06:00 AM

Table of contents

Open Table of contents

Introduction

I have been looking at integrating sops-nix into my upcoming NixOS config. One method of encryption is to use a GPG key on a YubiKey. My friend, sandbox sent me several links so I could educate myself:

There is a lot of information to digest. After reading through most of it, I asked sandbox if they would walk through it with me. It is nice having someone who knows what they are doing there in case things go awry.

Setup

You need, at the very least, 1 YubiKey. Note: You should have at least 2.

You’ve got a nice single point of failure here. It would be a shame if something happened to it.

You need a handful a packages. I won’t list them out here as drduh has done a great job documenting that for a variety of OSs.

Create your master keys

Open your favorite terminal emulator and run the following.

$ gpg --full-generate-key

You will be greated with several questions.

gpg (GnuPG) 2.2.19; Copyright (C) 2019 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.

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)
  (14) Existing key from card
Your selection? 

Type 1 and press Enter.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 

Type 4096 and press Enter.

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)

Type 0 and press Enter.

Note: Your key will never expire. Make sure you do this if you are sure you will never lose your key.

Key does not expire at all
Is this correct? (y/N)

Type Y and press Enter.

gpg will ask you for a name, email, and comment. Enter the requested information and press Enter. The commnet is solely for you and not necessary.

GnuPG needs to construct a user ID to identify your key.

Real name:
Email address:
Comment: 

I filled out my name as djacu and my email as dan@djacu.dev and left the comment empty. You should see something like the following but with your entered information.

You selected this USER-ID:
    "djacu <dan@djacu.dev>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?

If everything looks correct, type O and press Enter.

You will be asked to enter a password. This password is for your master signing key. Pick a good one. As you enter your password you should see the following.

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.

After your enter your password (twice), you should see something like the following.

gpg: key F02D533E35E86143 marked as ultimately trusted
gpg: revocation certificate stored as '<REDACTED>'
public and secret key created and signed.

pub   rsa4096 2023-11-15 [SC]
      B7F085B528CC38717CBA5848F02D533E35E86143
uid                      djacu <dan@djacu.dev>
sub   rsa4096 2023-11-15 [E]

Congrats! You have made your master key!

Now to create the rest. We are going to need your master key’s fingerprint for the following commands. In the example above, it is B7F085B528CC38717CBA5848F02D533E35E86143. Copy that into a text file somewhere so you can copy it easily. Wherever you see me refernce <fingerprint>, use your fingerprint. Also, because we set a password, we are going to need that as well so keep it handy.

Create your sub keys

Signing key

Run the following.

$ gpg --edit-key <fingerprint>

You should see something like the following.

gpg (GnuPG) 2.2.19; Copyright (C) 2019 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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

gpg> 

You are now in the gpg shell. Run the following.

gpg> addkey

You will be greeted with several question.

Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
  (14) Existing key from card
Your selection? 

Type 4 and press Enter.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)

Type 4096 and press Enter.

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) 

Type 0 and press Enter.

Key does not expire at all
Is this correct? (y/N) 

Type Y and press Enter.

Really create? (y/N) 

Type Y and press Enter.

You will be prompted to enter your master signing key password. After entering, you will see something like the following.

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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
[ultimate] (1). djacu <dan@djacu.dev>

There is a new entry for this fingerprint. You can see the difference if you compare it to the output from creating the master key. It is these two lines.

ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   

Run the following to save your new sub key.

gpg> save

You have created your signing key!

Authentication key

Creating the authentication key is a little different than before. We have to enter the gpg shell in expert mode and customize the key’s capabilities.

Run the following.

gpg --expert --edit-key <fingerprint>

You should see something like the following.

gpg (GnuPG) 2.2.19; Copyright (C) 2019 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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E                
ssb  rsa4096/B27561F7D73B8072                                                  
     created: 2023-11-15  expires: never       usage: S         
ssb  rsa4096/F20D4F38F42B8F6F                                                  
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

gpg>

Run the following.

gpg> addkey                                                                    

You will see a similar question from before but with more options.

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?

Type 8 and press Enter.

At this point the key is configured to Sign and Encrypt which you can see on the second line. We want it to Authenticate only.

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection?

To do so, we need to toggle all the options as follows. Type S and press Enter, then type E and press Enter, then type A and press Enter, and finally type Q and press Enter. You should see an output similar to below.

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Sign Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? S

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Encrypt 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? E

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? A

Possible actions for a RSA key: Sign Encrypt Authenticate 
Current allowed actions: Authenticate 

   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? Q

The following is the same as the previous two keys: choose a key length, specify a expiration date, and enter your master password. I have show the rest of the output below.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
[ultimate] (1). djacu <dan@djacu.dev>

gpg> save

Encryption key

An RSA key was already created when we made the master key, but we want a symmetric key that only exists on our YubiKey. The steps are very similar to the previous steps with 1 small difference. See the whole output below. After we run addkey, we select option 12 for the type of key and option 1 for the type of elliptic curve. Other than that the steps are the same.

gpg --expert --edit-key B7F085B528CC38717CBA5848F02D533E35E86143
gpg (GnuPG) 2.2.19; Copyright (C) 2019 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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
[ultimate] (1). djacu <dan@djacu.dev>

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? 12
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
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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
ssb  cv25519/AD088408E3F58ECB
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

gpg> save

You’ve done it! All the keys are created!

Backup the keys

The master key and sub-keys will be encrypted with your passphrase when exported. Run the following commands.

$ gpg --armor --export-secret-keys <fingerprint> > master-sub.key

$ gpg --armor --export-secret-subkeys <fingerprint> > sub.key

For completeness, we will also export our public keys and generate a revocation certificate.

$ gpg --armor --export <fingerprint> > pub.key

$ gpg --output revoke.asc --gen-revoke <fingerprint>

Store all these files somewhere very safe.

Store the key on a YubiKey

Managing the YubiKey

Plug in your YubiKey and run the following command.

gpg --card-edit                                                 

You should see something similar to the following.

Reader ...........: 1050:0406:X:0
Application ID ...: D2760001240103040006137968850000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 13796885
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> 

See the last several lines that say XXXXXXX key...: [none]? We are going to populate those with our newly created keys. But first we should configure our YubiKey.

Change the PINs

The default values for the PIN and admin PIN are 123456 and 12345678 respectively.

Run the following.

gpg/card> admin

You should see the following.

Admin commands are allowed

Run the following.

gpg/card> passwd
gpg: OpenPGP card no. D2760001240103040006137968850000 detected

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

Your selection? 

Type 1 and press Enter. You will be prompted to enter the PIN once and your new PIN twice. You will be shown the following again.

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

Your selection? 

Type 3 and press Enter. You will be prompted to enter the admin PIN once and your new admin PIN twice. You will be shown the following again.

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

Your selection? 

Type Q and press Enter.

Set your information

Run the following command to set your name. You may be prompted for the admin PIN.

gpg/card> name

You will be prompted for information. Type it and press Enter.

Cardholder's surname: Baker
Cardholder's given name: Daniel

Run the following command to set your language.

gpg/card> lang

You will be prompted for information. Type it and press Enter.

Language preferences: en

Run the following command to set your login.

gpg/card> login

You will be prompted for information. Type it and press Enter.

Login data (account name): <REDACTED>

Run the following.

gpg/card> list

You should see something similar to below but with your information.

Reader ...........: Yubico YubiKey FIDO CCID 00 00
Application ID ...: D2760001240103040006137968850000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 13796885
Name of cardholder: Daniel Baker
Language prefs ...: en
Salutation .......: 
URL of public key : [not set]
Login data .......: <REDACTED>
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]

Run the following to quit out and return to your regular shell.

gpg/card> quit

Transfer keys

Important Transferring keys to YubiKey using keytocard is a destructive, one-way operation only. Make sure you’ve made a backup before proceeding: keytocard converts the local, on-disk key into a stub, which means the on-disk copy is no longer usable to transfer to subsequent security key devices or mint additional keys.

Run the following command.

$ gpg --edit-key <fingerprint>

You should see something like the following.

gpg (GnuPG) 2.2.19; Copyright (C) 2019 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  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
ssb  cv25519/AD088408E3F58ECB
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

Run the following command.

gpg> key 2

You should see something like the following. See how the 3rd entry, ssb* has an asterisk now? That key is selected for transfer.

sec  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb* rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
ssb  cv25519/AD088408E3F58ECB
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

Run the following command.

gpg> keytocard

You should see something like the following. This is our signature key as denoted by the usage: S.

Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection?

Type 1 and press Enter.

You will be prompted for your master signing key password followed by the YubiKey admin PIN.

Run key 2 again to deselect and key 3 to select the next key.

gpg> key 2
gpg> key 3

You should see something like the following.

sec  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb* rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
ssb  cv25519/AD088408E3F58ECB
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

Run the following command.

gpg> keytocard

You should see something like the following. This is our authentication key as denoted by the usage: A.

Please select where to store the key:
   (3) Authentication key
Your selection?

Type 3 and press Enter.

You will be prompted for your master signing key password followed by the YubiKey admin PIN.

Run key 3 again to deselect and key 4 to select the next key.

gpg> key 3
gpg> key 4

You should see something like the following.

sec  rsa4096/F02D533E35E86143
     created: 2023-11-15  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa4096/193706112639C741
     created: 2023-11-15  expires: never       usage: E   
ssb  rsa4096/B27561F7D73B8072
     created: 2023-11-15  expires: never       usage: S   
ssb  rsa4096/D464D993C82BC321
     created: 2023-11-15  expires: never       usage: A   
ssb* cv25519/AD088408E3F58ECB
     created: 2023-11-15  expires: never       usage: E   
[ultimate] (1). djacu <dan@djacu.dev>

Run the following command.

gpg> keytocard

You should see something like the following. This is our encryption key as denoted by the usage: E.

Please select where to store the key:
   (2) Encryption key
Your selection?

Type 2 and press Enter.

You will be prompted for your master signing key password followed by the YubiKey admin PIN.

Run the following command.

gpg> save

Verify

Our keys should be stored now on the yubikey. Let’s check.

Run the following command.

$ gpg -K

You should see something like the following.

<REDACTED>
--------------------------------
sec   rsa4096 2023-11-15 [SC]
      B7F085B528CC38717CBA5848F02D533E35E86143
uid           [ultimate] djacu <dan@djacu.dev>
ssb   rsa4096 2023-11-15 [E]
ssb>  rsa4096 2023-11-15 [S]
ssb>  rsa4096 2023-11-15 [A]
ssb>  cv25519 2023-11-15 [E]

See how the last 3 ssb entries now have the > character next to them. They are stubs and have been moved to the YubiKey.

Run the following command.

$ gpg --card-status

You should see something like the following.

Reader ...........: Yubico YubiKey FIDO CCID 00 00
Application ID ...: D2760001240103040006137968850000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 13796885
Name of cardholder: Daniel Baker
Language prefs ...: en
Salutation .......: 
URL of public key : [not set]
Login data .......: bakerdn
Signature PIN ....: not forced
Key attributes ...: rsa4096 cv25519 rsa4096
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
Signature key ....: 0A24 1BDA E9E0 E057 699D  F661 B275 61F7 D73B 8072
      created ....: 2023-11-15 02:49:02
Encryption key....: 55C6 708C 76A0 ACF0 D849  2A10 AD08 8408 E3F5 8ECB
      created ....: 2023-11-15 03:53:05
Authentication key: DE56 B674 7EAE DFA2 3F63  63FA D464 D993 C82B C321
      created ....: 2023-11-15 03:32:35
General key info..: sub  rsa4096/B27561F7D73B8072 2023-11-15 djacu <dan@djacu.dev>
sec   rsa4096/F02D533E35E86143  created: 2023-11-15  expires: never     
ssb   rsa4096/193706112639C741  created: 2023-11-15  expires: never     
ssb>  rsa4096/B27561F7D73B8072  created: 2023-11-15  expires: never     
                                card-no: 0006 13796885
ssb>  rsa4096/D464D993C82BC321  created: 2023-11-15  expires: never     
                                card-no: 0006 13796885
ssb>  cv25519/AD088408E3F58ECB  created: 2023-11-15  expires: never     
                                card-no: 0006 13796885

Remember how before all the keys near the bottom said XXXXXXX key...: [none]? Now they are populated! We did it! 🎉

Multiple YubiKeys

This method might seem a little hacky (because it is) but this is similar to drduh’s method and it works. I will denote as the directory where the GPG keys are stored. For me, it is in ~/.gnupg/.

Run the following commands.

$ cd ~/.gnupg
$ mv private-keys-v1.d private-keys-v1.d.bak
$ mkdir private-keys-v1.d
$ gpg --import master-sub.key

If you run gpg -K you should notice that your private keys are no longer stubs and you can begin the process of transferring to your next YubiKey.

👋