Info
I haven’t finished writing this blog post, but I published it so the already-written information can be read by you. I will improve this post one day™.
Info
If you’re not entirely familiar with all concepts surrouding GPG, you should read my article “What are GPG keys?” first. It will give you all the necessary concepts and vocabulary you will need to understand this article.
Why all this?
The first reason is that I just started as a freelance developer and I created a new session especially for it.
- I had too many SSH keys
- I changed my email address
- GPG keys contain identity information while SSH keys do not
What I want
- A GPG key
- With a primary key pair that can only Certify
- With my personal contact info as the primary identity
- With a subkey pair that can only Sign, valid for 1 year
- With a subkey pair that can only Encrypt, valid for 1 year
- With a subkey pair that can only Authenticate, valid for 1 year
- A backup of the private keys (primary key pair and subkey pairs)
- My YubiKey configured so my name appears on it and with a custom PIN / admin PIN
- No private key on my computer
- The 3 private subkeys on my YubiKey
- My GPG keys used for SSH authentication and git signing
- Require a “card tap” on every SSH authentication
- Require a “card tap” on every git signing operation
My YubiKey supports it, and I don’t use any tool that doesn’t so I’m going to use the state-of-the-art ed25519 signature scheme for my keys instead of heavier 4096 bits RSA keys.
The whole process
Installing required tools
On macOS, run:
brew install gnupg
Otherwise, go to GnuPG - Download.
Info
This article has been written using gnupg: stable 2.4.3
.
Creating the primary key pair
TL;DR
gpg --full-gen-key --expert
Your selection? 11
Your selection? S
Your selection? Q
Your selection? 1
Key is valid for? (0) 0
Is this correct? (y/N) y
Real name: John Doe
Email address: john.doe@example.org
Comment: General purpose personal key
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
KEY_ID='DFF5C72AE657A8361A57D937A6D3E049FD2A88C4' # (Insert your key)
To create a GnuPG key, you need to run gpg --full-gen-key
. However, it only shows a limited number of options, which do not provide the opportunity to create a Certify-only primary key pair:
The options available by default
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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)
(9) ECC (sign and encrypt) *default*
(10) ECC (sign only)
(14) Existing key from card
Your selection?
For this reason, we will have to use the --expert
flag1:
gpg --full-gen-key --expert
Command output
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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?
We want to set our own capabilities to choose Certify only and I want to use ed25519 so when prompted what kind of key I want I’m going to choose 11
(“ECC” means “Elliptic Curve Cryptography”):
Terminal output
Your selection? 11
Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection?
We want Certify only, so let’s choose S
(s
works too) and then Q
:
Terminal output
Your selection? S
Possible actions for this ECC 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 *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?
We want to use the “Curve 25519” signature scheme so let’s choose 1
(or just hit Enter as it’s the default):
Terminal output
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)
The primary key pair will be kept somewhere safe and we will delete it locally so we can make it never expire:
Terminal output
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:
Now is the moment where we create the primary identity. For example purposes and to avoid you copy-pasting my information, I’m going to call myself John Doe. You should put your real name, not a nickname.
Terminal output
Real name: John Doe
Email address: john.doe@example.org
Comment: General purpose personal key
You selected this USER-ID:
"John Doe (General purpose personal key) <john.doe@example.org>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
Now gpg
will ask you for a passphrase. You should make it very complex and store it very safely as it is what will ensure your key is never misused. gpg
uses a PINEntry dialog to ask you for the passphrase, making it more secure than asking it inline.
What the PINEntries look like
┌──────────────────────────────────────────────────────┐
│ Please enter the passphrase to │
│ protect your new key │
│ │
│ Passphrase: ________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Please re-enter this passphrase │
│ │
│ Passphrase: ________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────────────┘
Once those credentials entered, gpg
will show this message:
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.
On my machine the key generation is very fast so you could start streaming a video or something like that in the background to ensure there is enough entropy.
After key generation succeeds, gpg
prints different information:
Terminal output
gpg: revocation certificate stored as '/Users/johndoe/.gnupg/openpgp-revocs.d/DFF5C72AE657A8361A57D937A6D3E049FD2A88C4.rev'
public and secret key created and signed.
pub ed25519 2023-12-12 [C]
DFF5C72AE657A8361A57D937A6D3E049FD2A88C4
uid John Doe (General purpose personal key) <john.doe@example.org>
-
First, it informs that there is a revocation certificate (stored in
/Users/johndoe/.gnupg/openpgp-revocs.d/DFF5C72AE657A8361A57D937A6D3E049FD2A88C4.rev
here).The content of this certificate
This is a revocation certificate for the OpenPGP key: pub ed25519 2023-12-12 [] DFF5C72AE657A8361A57D937A6D3E049FD2A88C4 uid John Doe (General purpose personal key) <john.doe@example.org> A revocation certificate is a kind of "kill switch" to publicly declare that a key shall not anymore be used. It is not possible to retract such a revocation certificate once it has been published. Use it to revoke this key in case of a compromise or loss of the secret key. However, if the secret key is still accessible, it is better to generate a new revocation certificate and give a reason for the revocation. For details see the description of of the gpg command "--generate-revocation" in the GnuPG manual. To avoid an accidental use of this file, a colon has been inserted before the 5 dashes below. Remove this colon with a text editor before importing and publishing this revocation certificate. :-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: This is a revocation certificate iHgEIBYKACAWIQTf9ccq5leoNhpX2Tem0+BJ/SqIxAUCZXixhgIdAAAKCRCm0+BJ /SqIxIzvAQDtNTas4P/meRRwX9Cl4gdHGsEQWvFVuEeiDKhSn8AR1AEA/m53PfeR wv2JWrZuQy8DRSjsSRGqYVb1fWODFZ8MDAs= =zdjH -----END PGP PUBLIC KEY BLOCK-----
Important
You should save it somewhere safe, in case you need to revoke the entire GPG key someday.
-
It also prints information about the key:
pub ed25519 2023-12-12 [C] DFF5C72AE657A8361A57D937A6D3E049FD2A88C4 uid John Doe (General purpose personal key) <john.doe@example.org>
All of this information is public, and I broke down what it means in After generating a key.
To make things easier later, let’s save the key fingerprint in an environment variable:
KEY_ID='DFF5C72AE657A8361A57D937A6D3E049FD2A88C4'
This value can then be used for every gpg
operation to identify the key.
Tip
You could also use the email address or even just part of the name (like “John”) if it is unique. I prefer using the fingerprint to ensure uniqueness.
Creating the subkey pairs
TL;DR
gpg --edit-key --expert "${KEY_ID:?}"
Sign subkey:
gpg> addkey
Your selection? 10
Your selection? 1
Key is valid for? (0) 1y
Is this correct? (y/N) y
Really create? (y/N) y
Encrypt subkey:
gpg> addkey
Your selection? 12
Your selection? 1
Key is valid for? (0) 1y
Is this correct? (y/N) y
Really create? (y/N) y
Authenticate subkey:
gpg> addkey
Your selection? 11
Your selection? S
Your selection? A
Your selection? Q
Your selection? 1
Key is valid for? (0) 1y
Is this correct? (y/N) y
Really create? (y/N) y
gpg> save
Now that we have a primary key pair, we can derive the three subkeys from it. For that, we use gpg --edit-key
with the --expert
flag to be sure we can select the desired capabilities:
gpg --edit-key --expert "${KEY_ID:?}"
Tip
:?
after the environment variable name ensures you didn’t forget to set it 😉 If you did, it will abort saying “KEY_ID: parameter not set”.
We now arrive in a console-like environment where we can interact with the GPG key.
What the gpg
console looks like
gpg (GnuPG) 2.4.3; Copyright (C) 2023 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.
sec ed25519/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
Tip
You can find all operations available by running help
or via edit-key · The GNU Privacy Handbook.
Since we want to add a new subkey, we will use addkey
2:
Terminal output
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?
For this first key, we want the Sign capability only, so let’s enter 10
and then 1
for “Curve 25519”:
Terminal output
Your selection? 10
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? 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)
Having keys that expire is a good thing because it reduces the chance of having a leaked private key be used for malicious purposes. However, constantly changing (or “rotating”) the keys is quite tedious so I think having keys valid for 1 year is a good in-between. Let’s enter 1y
then:
Key is valid for? (0) 1y
Key expires at Mer 11 déc 16:02:05 2024 CST
Is this correct? (y/N) y
Really create? (y/N) y
gpg
now asks for the passphrase so it can read the private key and derive a subkey from it.
What the PINEntry looks like
┌──────────────────────────────────────────────────────────────────┐
│ Please enter the passphrase to unlock the OpenPGP secret key: │
│ "John Doe (General purpose personal key) <john.doe@example.org>" │
│ 255-bit EDDSA key, ID A6D3E049FD2A88C4, │
│ created 2023-12-12. │
│ │
│ │
│ Passphrase: ____________________________________________________ │
│ │
│ <OK> <Cancel> │
└──────────────────────────────────────────────────────────────────┘
After that, gpg
generates a new key and prints this time information about the private keys (sec
, ssb
).
Terminal output
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/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
Now that we have the Sign (S
) subkey, we can add an Encrypt subkey by doing the same but choosing 12
(“ECC (encrypt only)”) as the kind:
Terminal output
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 *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? 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) 1y
Key expires at Mer 11 déc 16:18:15 2024 CST
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/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
We now see a second secret subkey (ssb
) with the Encrypt capability (E
).
Finally, let’s create the third and last subkey, with Authenticate capability:
Terminal output
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?
As you can see, there is no “ECC (authenticate only)” option, so we will need to choose 11
(“ECC (set your own capabilities)”) and toggle the capabilities as we did when Creating the primary key pair:
Terminal output
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? 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) 1y
Key expires at Mer 11 déc 16:22:35 2024 CST
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/A6D3E049FD2A88C4
created: 2023-12-12 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/A2E618E20456625F
created: 2023-12-12 expires: 2024-12-11 usage: S
ssb cv25519/52477AF6A631E77F
created: 2023-12-12 expires: 2024-12-11 usage: E
ssb ed25519/37A902F030B44E05
created: 2023-12-12 expires: 2024-12-11 usage: A
[ultimate] (1). John Doe (General purpose personal key) <john.doe@example.org>
gpg>
We can see that we successfully created the Sign, Encrypt and Authenticate keys, in addiction to the primary Certify key. We now have to save:
gpg> save
This saves the changes (which would not be saved on quit
or Ctrl+C) and gets us back to the terminal.
Backing up the keys
Now we want to create backups of the secret keys before we delete them. This can be done using --export-secret-*
3 commands. By default, gpg
exports in binary OpenPGP format so we use the --armor
flag to generate an ASCII-armored output. You can find more information about this format in What is ASCII-armored format?, but basically its a format that uses only printable ASCII characters. To avoid having a trace of the output of this command stored on my computer4, I’m piping the output to pbcopy
which copies the sensitive information to my clipboard:
gpg --export-secret-keys --armor "${KEY_ID}" | pbcopy
Now we can save this backup in a very safe place, like an external drive then kept offline or a password manager for convenience (even though it’s not as safe).
We can also generate a less sensitive backup with only the private keys of the subkey pairs, and a placeholder for the primary key pair private key:
gpg --export-secret-subkeys --armor "${KEY_ID}" | pbcopy
Caution
Keep in mind that this does not replace the --export-secret-keys
backup, as the primary key pair private key is vital!
Q&A
What is ASCII-armored format?
GPT-3.5, 2023-12-10
ASCII-armored (also known as ASCII-armoured or ASCII-armour) refers to a method of representing binary data (such as cryptographic keys) using only printable ASCII characters. It’s a way to encode binary data into a text format that can be easily transported via text-based communication channels like email or plaintext files without corruption or loss of information.
In the context of GnuPG (GPG) or PGP (Pretty Good Privacy), ASCII-armored format is commonly used to export and share keys. When you export a GPG key in ASCII-armored format, the binary data of the key (which includes your public or private key) is converted into a text-based representation. This representation contains only printable ASCII characters (letters, numbers, and symbols commonly found in plain text) that can be safely transmitted or stored without corruption.
ASCII-armored files typically have file extensions like .asc
or .gpg
, and they contain the encoded key data that can be imported back into GPG or PGP systems when needed.
For example, when you export a GPG key to an ASCII-armored file, the resulting file will look something like this:
-----BEGIN PGP PRIVATE KEY BLOCK-----
mQENBF5QwYwBCADm36ZLDl4JcnJ0Iyv/xTXbweAbCf09gODTk/8E+JZrG9zSyF1N
...
...
...
wXd1xUDquOqwDe8q/QFwUBoPqQ==
=B1g3
-----END PGP PRIVATE KEY BLOCK-----
The -----BEGIN PGP...-----
and -----END PGP...-----
delimiters mark the beginning and end of the ASCII-armored data, respectively.
ASCII-armored keys are human-readable and can be copied and pasted, transmitted via email, or stored in text-based files while retaining their integrity and allowing for easy sharing and importation into GPG or PGP software.
What do gpg
outputs mean?
After generating a key
After generating a key, gpg
prints the same information as gpg --list-keys <KEY_ID>
:
pub ed25519 2023-12-12 [C]
DFF5C72AE657A8361A57D937A6D3E049FD2A88C4
uid John Doe (General purpose personal key) <john.doe@example.org>
Here is a breakdown of what it means:
pub
introduces the public key of the primary key paired25519
is the scheme2023-12-12
is the creation date[C]
means this key has Certify capabilities onlyDFF5C72AE657A8361A57D937A6D3E049FD2A88C4
is the key fingerprint
uid
introduces the primary identityJohn Doe
is the real nameGeneral purpose personal key
is the commentjohn.doe@example.org
is the email address
Common issues
I don’t see the “set your own capabilities” options
If you don’t see the “set your own capabilities” options, you probably forgot to add the --expert
flag1 to gpg --edit-key
.
Miscellaneous useful commands
Delete a subkey pair
To delete a [subkey pair] from a GPG key, you need to edit the key:
gpg --edit-key <KEY_ID>
(replacing <KEY_ID>
by an unique identifier for your GPG key (fingerprint, key ID, email, name…))
Then select, a [subkey pair] by its index (<n>
, starting from 0
) and then run delkey
:
gpg> key <n>
gpg> delkey
References
To write this article, I used various sources:
- Using Your YubiKey with OpenPGP – Yubico
- Lists compatible devices and explains the core logic of the process described in this article
- drduh/YubiKey-Guide: Guide to using YubiKey for GPG and SSH
- Explains everything for every platform
- Setting up GnuPG + Yubikey on NixOS for SSH authentication
- Very complete, lots of good explanations, very close to this article finally (wish I had found it sooner)
- GPG Cheat Sheet | Andy Gock
- Haven’t used but might be interesting to keep around
and manuals:
-
From GPG Configuration Options:
↩︎ ↩︎--expert
,--no-expert
Allow the user to do certain nonsensical or “silly” things like signing an expired or revoked key, or certain potentially incompatible things like generating unusual key types. This also disables certain warning messages about potentially incompatible actions. As the name implies, this option is for experts only. If you don’t fully understand the implications of what it allows you to do, leave this off.--no-expert
disables this option. -
From edit-key · The GNU Privacy Handbook:
↩︎addkey
Add a new subkey to the current key -
From
man gpg
:
↩︎--export-secret-keys
,--export-secret-subkeys
Same as--export
, but exports the secret keys instead. The exported keys are written to STDOUT or to the file given with option--output
. This command is often used along with the option--armor
to allow for easy printing of the key for paper backup; however the external toolpaperkey
does a better job of creating backups on paper. Note that exporting a secret key can be a security risk if the exported keys are sent over an insecure channel.
The second form of the command has the special property to render the secret part of the primary key useless; this is a GNU extension to OpenPGP and other implementations can not be expected to successfully import such a key. Its intended use is in generating a full key with an additional signing subkey on a dedicated machine. This command then exports the key without the primary key to the main machine.
GnuPG may ask you to enter the passphrase for the key. This is required, because the internal protection method of the secret key is different from the one specified by the OpenPGP protocol. -
I know there are some clipboard managers which keep a history of copied text, but I don’t think this is happening with Apple’s clipboard and it’s always better than a regular file. ↩︎