Crypto Island

image/svg+xml

How I generated my GPG keys

Posted 2021-10-01
Tags: security, gpg, education, encryption
Contents

Everyone agrees that GnuPG has a difficult interface, and therefore you need to follow various guides to get stuff done. But they’re often really detailed! So here’s a somewhat-less-detailed guide, intended to get you set up as quickly as possible without missing anything important.

But first, one request: I’m breaking with “best practices” a bit in that most people would advise you not to show the commands you used in case it helps an attacker. That smells of security through obscurity to me, and I think the value of sharing outweighs the risk. But if you do spot a mistake, please contact me so I can update this post rather than hacking me!

With that out of the way, here are the steps I used to set up and share my own keys. I found it easier to run through everything multiple times with new temporary --homedirs than to understand the options before trying them. If you decide you want more background reading though, start with these:

Generate keys

The key generation command is interactive. Everything after # is an answer to a prompt which should be clear in context.

$ mkdir -p offline-gpghome
$ alias GPG='gpg --homedir=offline-gpghome --keyid-format=long'
$ GPG --expert --full-generate-key # 8, s, e, q, 0, y, no password, name, email, no comment
$ GPG --expert --edit-key jefdaj
> addkey # 4, 4096, 1y, y, y, no password
> addkey # 6, 4096, 1y, y, y, no password
> addkey # 8, a, s, e, q, 4096, 1y, y, y, no password
> save

That created one master [C]ertification key and separate subkeys for [S]igning, [E]ncryption, and [A]uthentication:

$ GPG --list-keys
/tmp/generate-gpg-key/offline-gpghome/pubring.kbx
-------------------------------------------------
pub   rsa4096/E604517174B3D49E 2021-09-28 [C]
      54C195A345205DCABC2010EEE604517174B3D49E
uid                 [ultimate] EMAIL REDACTED TO REDUCE SPAM
sub   rsa4096/EF8F01E8B5D49300 2021-09-28 [S] [expires: 2022-09-28]
sub   rsa4096/73B356CD3CA9E12B 2021-09-28 [E] [expires: 2022-09-28]
sub   rsa4096/6E123E190F5FB8BD 2021-09-28 [A] [expires: 2022-09-28]

The certification key never expires, and will be treated like a cold wallet: I’ll have to dig it out of my offline backups once per year to extend or replace the other 3, or to certify anyone else’s public key (confusingly referred to as “key signing” even though you use the [C] key).

Make offline backups

Next I export backups of all the keys.

$ mkdir offline-backup
$ GPG --armor --export-secret-keys    > offline-backup/secret-keys.asc
$ GPG --armor --export-secret-subkeys > offline-backup/secret-subkeys.asc
$ GPG --armor --export                > offline-backup/public-keys.asc

These will be stored offline and encrypted.

I also generate revocation certificates. They will be backed up too, but more importantly I’ll keep copies on hand to import and upload to keyservers in case I get hacked.

$ for keyid in EF8F01E8B5D49300 73B356CD3CA9E12B 6E123E190F5FB8BD; do
>   GPG --output offline-backup/revoke-${keyid}.asc --gen-revoke ${keyid}
> done # answer for each: y, 1 (compromised), y

The master certification key probably won’t be hacked, but it should be revocable in case I lose my online backups. So I generate a certificate for that too. I’ll store it separately from the main backups.

$ GPG --output offline-backup/revoke-E604517174B3D49E.asc --gen-revoke E604517174B3D49E # y, 0 (no reason), y

Verify backups

First the public keys. We’re checking for pub in front of the master key, sub in front of each subkey, and that --list-secret-keys doesn’t list anything.

$ mkdir verify-public
$ gpg --homedir=verify-public --import offline-backup/public-keys.asc
gpg: keybox '/tmp/generate-gpg-key/verify-public/pubring.kbx' created
gpg: /tmp/generate-gpg-key/verify-public/trustdb.gpg: trustdb created
gpg: key E604517174B3D49E: public key "EMAIL REDACTED TO REDUCE SPAM" imported
gpg: Total number processed: 1
gpg:               imported: 1
$ gpg --homedir=verify-public --list-keys --keyid-format=long
/tmp/generate-gpg-key/verify-public/pubring.kbx
---------------------------------------------
pub   rsa4096/E604517174B3D49E 2021-09-28 [C]
      54C195A345205DCABC2010EEE604517174B3D49E
uid                 [ unknown] EMAIL REDACTED TO REDUCE SPAM
sub   rsa4096/EF8F01E8B5D49300 2021-09-28 [S] [expires: 2022-09-28]
sub   rsa4096/73B356CD3CA9E12B 2021-09-28 [E] [expires: 2022-09-28]
sub   rsa4096/6E123E190F5FB8BD 2021-09-28 [A] [expires: 2022-09-28]
$ gpg --homedir=verify-public --list-secret-keys --keyid-format=long

Now the secret subkeys. Look for sec# in front of the master key, meaning that you only have the public half available. You should have the private parts of the subkeys though, which is indicated with ssb.

$ mkdir verify-subkeys
$ gpg --homedir=verify-subkeys --import offline-backup/secret-subkeys.asc
gpg: keybox '/tmp/generate-gpg-key/verify-subkeys/pubring.kbx' created
gpg: /tmp/generate-gpg-key/verify-subkeys/trustdb.gpg: trustdb created
gpg: key E604517174B3D49E: public key "EMAIL REDACTED TO REDUCE SPAM" imported
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key E604517174B3D49E: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
$ gpg --homedir=verify-subkeys --list-secret-keys --keyid-format=long
/tmp/generate-gpg-key/verify-subkeys/pubring.kbx
----------------------------------------------
sec#  rsa4096/E604517174B3D49E 2021-09-28 [C]
      54C195A345205DCABC2010EEE604517174B3D49E
uid                 [ unknown] EMAIL REDACTED TO REDUCE SPAM
ssb   rsa4096/EF8F01E8B5D49300 2021-09-28 [S] [expires: 2022-09-28]
ssb   rsa4096/73B356CD3CA9E12B 2021-09-28 [E] [expires: 2022-09-28]
ssb   rsa4096/6E123E190F5FB8BD 2021-09-28 [A] [expires: 2022-09-28]

Finally the secret keys. Everything should look the same except # should be gone from sec on the master key:

$ mkdir verify-secret
$ gpg --homedir=verify-secret --import offline-backup/secret-keys.asc
gpg: keybox '/tmp/generate-gpg-key/verify-secret/pubring.kbx' created
gpg: /tmp/generate-gpg-key/verify-secret/trustdb.gpg: trustdb created
gpg: key E604517174B3D49E: public key "EMAIL REDACTED TO REDUCE SPAM" imported
gpg: key E604517174B3D49E: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
$ gpg --homedir=verify-secret --list-secret-keys --keyid-format=long
/tmp/generate-gpg-key/verify-secret/pubring.kbx
---------------------------------------------
sec   rsa4096/E604517174B3D49E 2021-09-28 [C]
      54C195A345205DCABC2010EEE604517174B3D49E
uid                 [ unknown] EMAIL REDACTED TO REDUCE SPAM
ssb   rsa4096/EF8F01E8B5D49300 2021-09-28 [S] [expires: 2022-09-28]
ssb   rsa4096/73B356CD3CA9E12B 2021-09-28 [E] [expires: 2022-09-28]
ssb   rsa4096/6E123E190F5FB8BD 2021-09-28 [A] [expires: 2022-09-28]

Make online subkeys

Next I set passwords to protect the subkeys in case they’re stolen from my online computer…

$ for keyid in EF8F01E8B5D49300 73B356CD3CA9E12B 6E123E190F5FB8BD; do
>   GPG --pinentry-mode loopback --passwd $keyid
> done # ignore error message, enter new passphrase twice

… and re-export the password-shielded versions. I also copy over the revocation certificates.

$ mkdir online-pc
$ GPG --armor --export-secret-subkeys > online-pc/secret-subkeys.asc
$ cp offline-backup/revoke-*.asc online-pc/
$ rm online-pc/revoke-E604517174B3D49E.asc

Note: I didn’t use passwords on the backups above because they will already be encrypted. Trying to set them here revealed a bug in pinentry’s handling of empty passwords! I worked around it using --pinentry-mode loopback as suggested here.

Final checks

After a bit of cleanup, these are the files I’ll be keeping:

$ rm -r offline-gpghome verify-*
$ tree
.
├── offline-backup
│   ├── public-keys.asc
│   ├── revoke-6E123E190F5FB8BD.asc
│   ├── revoke-73B356CD3CA9E12B.asc
│   ├── revoke-E604517174B3D49E.asc
│   ├── revoke-EF8F01E8B5D49300.asc
│   ├── secret-keys.asc
│   └── secret-subkeys.asc
└── online-pc
    ├── revoke-6E123E190F5FB8BD.asc
    ├── revoke-73B356CD3CA9E12B.asc
    ├── revoke-EF8F01E8B5D49300.asc
    └── secret-subkeys.asc

2 directories, 12 files

file reports that the pubkeys are public keys, the revocation certificates are signatures, and the secret keys are ASCII.

$ file */*
offline-backup/public-keys.asc:             PGP public key block Public-Key (old)
offline-backup/revoke-6E123E190F5FB8BD.asc: PGP public key block Signature (old)
offline-backup/revoke-73B356CD3CA9E12B.asc: PGP public key block Signature (old)
offline-backup/revoke-E604517174B3D49E.asc: PGP public key block Signature (old)
offline-backup/revoke-EF8F01E8B5D49300.asc: PGP public key block Signature (old)
offline-backup/secret-keys.asc:             ASCII text
offline-backup/secret-subkeys.asc:          ASCII text
online-pc/revoke-6E123E190F5FB8BD.asc:  PGP public key block Signature (old)
online-pc/revoke-73B356CD3CA9E12B.asc:  PGP public key block Signature (old)
online-pc/revoke-EF8F01E8B5D49300.asc:  PGP public key block Signature (old)
online-pc/secret-subkeys.asc:           ASCII text

Seems reasonable. I’m ready to back up the raw keys and move the shielded subkeys to an online computer!

Import and publish

I import the secret subkeys, which also include the corresponding public ones.

$ gpg --import online-pc/secret-subkeys.asc
$ gpg --list-secret-keys
/home/jefdaj/.gnupg/pubring.kbx
-------------------------------
sec#  rsa4096 2021-09-28 [C]
54C195A345205DCABC2010EEE604517174B3D49E
uid           [ unknown] EMAIL REDACTED TO REDUCE SPAM
ssb   rsa4096 2021-09-28 [S] [expires: 2022-09-28]
ssb   rsa4096 2021-09-28 [E] [expires: 2022-09-28]
ssb   rsa4096 2021-09-28 [A] [expires: 2022-09-28]

Just to be sure, I confirm the sec# and 3 ssbs again. I save the revocation certificates somewhere too.

There are lots of confusing options for where to publish a key these days. After reading this I decide to export to a file and upload it to keys.openpgp.org manually.

gpg --armor --export 73B356CD3CA9E12B > 73B356CD3CA9E12B.asc

I expected that would only export the [E]ncryption subkey, but turns out everything is bundled together. I’m OK with that. If you aren’t you could --edit-key to delete the ones you don’t want to publish first, then re-import them.

Once they’re uploaded (and my email is verified) I can search for them by key fingerprint or email, or fetch by fingerprint using gpg only:

gpg --keyserver keys.openpgp.org --recv-key 54C195A345205DCABC2010EEE604517174B3D49E