GnuPG

It’s been a while (well, years) since I rotated my GPG keys, and to be honest, now that I know better how to handle a GPG key pair in order to avoid master key rotation, I think it’s the time to get a new pair.

This tutorial will show you the steps I followed with explanations on what we are achieving in every step.

Environment

This is the GnuPG version used in this tutorial (if you are using a different version, probably not every command would work the same, but I wouldn’t expect for the concept to change that much):

$ gpg --version
gpg (GnuPG) 2.2.14
libgcrypt 1.8.4
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /Users/dpecos/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

If you’re using MacOSX, you will need this application to handle password prompts:

brew install pinentry-mac

Create a new key pair

Let’s start generating the new key pair, without expiration date (we will be able to set one if needed in the future):

$ gpg --full-generate-key
gpg (GnuPG) 2.2.14; 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)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 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) 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: Daniel Pecos Martinez
Email address: me@danielpecos.com
Comment:
You selected this USER-ID:
    "Daniel Pecos Martinez <me@danielpecos.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.
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: key E881015C8A55678B marked as ultimately trusted
gpg: revocation certificate stored as '/Users/dpecos/.gnupg/openpgp-revocs.d/31EFB482E969EB74399DBBC5E881015C8A55678B.rev'
public and secret key created and signed.

pub   rsa4096 2019-03-27 [SC]
      31EFB482E969EB74399DBBC5E881015C8A55678B
uid                      Daniel Pecos Martinez <me@danielpecos.com>
sub   rsa4096 2019-03-27 [E]

We got the key ID 31EFB482E969EB74399DBBC5E881015C8A55678B, which we’ll be using to reference the key whenever needed (like editing the key, adding signatures, …).

Note that a revocation certificate has already been created, so we don’t need to create a new one if we don’t want:

gpg: revocation certificate stored as '/Users/dpecos/.gnupg/openpgp-revocs.d/31EFB482E969EB74399DBBC5E881015C8A55678B.rev'

Add UIDs

Now let’s add any extra ID that we would want to use to the key pair. This step is optional and should be repeated as many times as needed. In my case I’d like to add an extra UID:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>

gpg> adduid
Real name: Daniel Pecos Martinez
Email address: dani@dplabs.io
Comment:
You selected this USER-ID:
    "Daniel Pecos Martinez <dani@dplabs.io>"

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

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1)  Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2). Daniel Pecos Martinez <dani@dplabs.io>

gpg> uid 2

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1)  Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)* Daniel Pecos Martinez <dani@dplabs.io>

gpg> trust
sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1)  Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)* Daniel Pecos Martinez <dani@dplabs.io>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1)  Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)* Daniel Pecos Martinez <dani@dplabs.io>

gpg> save

Now that we have multiple UIDs within the same key, it’s important to check that the primary UID is properly set. If not, we have to change it:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1). Daniel Pecos Martinez <dani@dplabs.io>
[ultimate] (2)  Daniel Pecos Martinez <me@danielpecos.com>

gpg> uid 2

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1). Daniel Pecos Martinez <dani@dplabs.io>
[ultimate] (2)* Daniel Pecos Martinez <me@danielpecos.com>

gpg> primary

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1)  Daniel Pecos Martinez <dani@dplabs.io>
[ultimate] (2)* Daniel Pecos Martinez <me@danielpecos.com>

gpg> save

Create subkeys

At this moment, we have a key pair generated and containing the UIDs we want to use, and we could be good to go, but, what would happen if we lost the laptop where this key pair is stored? We would have to revoke them and generate a new key pair, loosing all its signatures and the trust gathered with time.

In order to avoid this situation is a common (and good) practice to generate what is known as laptop keys. These keys are disposable keys that are linked to your master key and that you would copy to the device where you would use them as usual, keeping the master key safe offline.

If by any eventuality you’d loose access to those laptop keys you could easily revoke them with the master key and generate a new set to replace them, but the master key pair (and thus your PGP/GPG ID) would remain the same.

Sounds good? Let’s generate a new subkey for signing. If you’ve paid attention to the process you’d have notice that a subkey for encrypting was already generated for your master key (ssb rsa4096/89BF354A17A61CC5 with usage: E), so no need to generate an extra key for that, as we already got one:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

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)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 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) 5y
Key expires at Mon Mar 25 15:42:11 2024 CET
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/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

gpg> save

Note that I’ve set up an expire date for this subkey, as I want to rotate them from time to time. We would probably also want to set an expire date for the previously created encryption subkey:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

gpg> key 1

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb* rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: never       usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

gpg> expire
Changing expiration time for a subkey.
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) 5y
Key expires at Mon Mar 25 15:44:22 2024 CET
Is this correct? (y/N) y

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb* rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

gpg> save

Rotate your previous key pair

This is the tricky and painful part of the process, as you are effectively decomissioning your previous PGP/GPG ID, loosing the created web of trust around it.

In order to let the world know that your new key pair actually belongs to you without having to meet in person again, the standard procedure is to sign the new key with the old one and the other way around, establishing a hard trust link between them (this action could not have been done without access to the old secret key, so as far as anyone can tell, this two-way signature could only have been done by you).

Signing the new and old public keys

First of all, let’s check that both secret keys are available in our keyring:

$ gpg --list-secret-keys
/Users/dpecos/.gnupg/pubring.kbx
--------------------------------
sec   dsa1024 2002-11-11 [SC]
      F6D13162F2BEF65838F6C8E6BF3B5AFCD4480E60
uid           [  full  ] Daniel Pecos Martinez
uid           [  full  ] Daniel Pecos Martinez <dpecos@gmail.com>
uid           [  full  ] Daniel Pecos Martinez <me@danielpecos.com>
ssb   elg4096 2009-11-19 [E]

sec   rsa4096 2019-03-27 [SC]
      31EFB482E969EB74399DBBC5E881015C8A55678B
uid           [ultimate] Daniel Pecos Martinez <me@danielpecos.com>
uid           [ultimate] Daniel Pecos Martinez <dani@dplabs.io>
ssb   rsa4096 2019-03-27 [E] [expires: 2024-03-25]
ssb   rsa4096 2019-03-27 [S] [expires: 2024-03-25]

Now in order to establish trust between the new and the old keys, we have to sign each other. First let’s sign the new key with old one:

$ gpg --default-key F6D13162F2BEF65838F6C8E6BF3B5AFCD4480E60 --sign-key 31EFB482E969EB74399DBBC5E881015C8A55678B

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

Really sign all user IDs? (y/N) y
gpg: using "F6D13162F2BEF65838F6C8E6BF3B5AFCD4480E60" as default secret key for signing

sec  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
Primary key fingerprint: 31EF B482 E969 EB74 399D  BBC5 E881 015C 8A55 678B

    Daniel Pecos Martinez <me@danielpecos.com>
    Daniel Pecos Martinez <dani@dplabs.io>

Are you sure that you want to sign this key with your
key "Daniel Pecos Martinez" (BF3B5AFCD4480E60)

Really sign? (y/N) y

And then the other way around:

$ gpg --default-key 31EFB482E969EB74399DBBC5E881015C8A55678B --sign-key F6D13162F2BEF65838F6C8E6BF3B5AFCD4480E60

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   2  signed:   1  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: depth: 1  valid:   1  signed:   0  trust: 1-, 0q, 0n, 0m, 0f, 0u
sec  dsa1024/BF3B5AFCD4480E60
    created: 2002-11-11  expires: never       usage: SC
    trust: unknown       validity: full
The following key was revoked on 2009-11-19 by DSA key BF3B5AFCD4480E60 Daniel Pecos Martinez
ssb  elg2048/3FBD37D1977AB8A4
    created: 2002-11-11  revoked: 2009-11-19  usage: E
ssb  elg4096/0DE841998B4ADD00
    created: 2009-11-19  expires: never       usage: E
[  full  ] (1). Daniel Pecos Martinez
[  full  ] (2)  Daniel Pecos Martinez <dpecos@gmail.com>
[ revoked] (3)  Daniel Pecos Martinez <dani@netpecos.org>
[  full  ] (4)  Daniel Pecos Martinez <me@danielpecos.com>

Really sign all user IDs? (y/N) y
gpg: using "31EFB482E969EB74399DBBC5E881015C8A55678B" as default secret key for signing
User ID "Daniel Pecos Martinez <dani@netpecos.org>" is revoked.  Unable to sign.

sec  dsa1024/BF3B5AFCD4480E60
    created: 2002-11-11  expires: never       usage: SC
    trust: unknown       validity: full
Primary key fingerprint: F6D1 3162 F2BE F658 38F6  C8E6 BF3B 5AFC D448 0E60

    Daniel Pecos Martinez
    Daniel Pecos Martinez <dpecos@gmail.com>
    Daniel Pecos Martinez <me@danielpecos.com>

Are you sure that you want to sign this key with your
key "Daniel Pecos Martinez <me@danielpecos.com>" (E881015C8A55678B)

Really sign? (y/N) y

Let’s check that the new key has been properly signed:

$ gpg --list-sigs E881015C8A55678B
pub   rsa4096 2019-03-27 [SC]
      31EFB482E969EB74399DBBC5E881015C8A55678B
uid           [ultimate] Daniel Pecos Martinez <me@danielpecos.com>
sig 3        E881015C8A55678B 2019-03-27  Daniel Pecos Martinez <me@danielpecos.com>
sig          BF3B5AFCD4480E60 2019-03-27  Daniel Pecos Martinez
uid           [ultimate] Daniel Pecos Martinez <dani@dplabs.io>
sig 3        E881015C8A55678B 2019-03-27  Daniel Pecos Martinez <me@danielpecos.com>
sig          BF3B5AFCD4480E60 2019-03-27  Daniel Pecos Martinez
sub   rsa4096 2019-03-27 [E] [expires: 2024-03-25]
sig          E881015C8A55678B 2019-03-27  Daniel Pecos Martinez <me@danielpecos.com>
sub   rsa4096 2019-03-27 [S] [expires: 2024-03-25]
sig          E881015C8A55678B 2019-03-27  Daniel Pecos Martinez <me@danielpecos.com>

The lines containing:

sig          BF3B5AFCD4480E60 2019-03-27  Daniel Pecos Martinez

reflect a signature on a UID. BF3B5AFCD4480E60 is the sort ID of my old key.

Revoke old key pair

Now we have to effectively decomission the old key pair. For that, we have to generate a revocation certificate for it:

$ gpg -a --gen-revoke BF3B5AFCD4480E60 > BF3B5AFCD4480E60.rev

sec  dsa1024/BF3B5AFCD4480E60 2002-11-11 Daniel Pecos Martinez

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? 2
Enter an optional description; end it with an empty line:
> Superseeded by E881015C8A55678B
>
Reason for revocation: Key is superseded
Superseeded by E881015C8A55678B
Is this okay? (y/N) y
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!

And then import it to our keyring to actually revoke the key:

$ gpg --import BF3B5AFCD4480E60.rev
gpg: key BF3B5AFCD4480E60: "Daniel Pecos Martinez" revocation certificate imported
gpg: Total number processed: 1
gpg:    new key revocations: 1
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u

$ gpg --list-secret-keys
/Users/dpecos/.gnupg/pubring.kbx
--------------------------------
sec   dsa1024 2002-11-11 [SC] [revoked: 2019-03-27]
      F6D13162F2BEF65838F6C8E6BF3B5AFCD4480E60
uid           [ revoked] Daniel Pecos Martinez
uid           [ revoked] Daniel Pecos Martinez <dpecos@gmail.com>
uid           [ revoked] Daniel Pecos Martinez <me@danielpecos.com>

sec   rsa4096 2019-03-27 [SC]
      31EFB482E969EB74399DBBC5E881015C8A55678B
uid           [ultimate] Daniel Pecos Martinez <me@danielpecos.com>
uid           [ultimate] Daniel Pecos Martinez <dani@dplabs.io>
ssb   rsa4096 2019-03-27 [E] [expires: 2024-03-25]
ssb   rsa4096 2019-03-27 [S] [expires: 2024-03-25]

And we’re done! Next step is to publish our changes to the world.

Publish to PGP servers

We have to publish our new key pair and also the revoked old key pair. I’ve published them to two separate PGP servers in order to speed up the spreading of the changes, but that’s not strictly necessary as the key servers sync between them from time to time.

$ gpg --keyserver hkp://pgp.mit.edu --send-keys BF3B5AFCD4480E60 E881015C8A55678B
gpg: sending key BF3B5AFCD4480E60 to hkp://pgp.mit.edu
gpg: sending key E881015C8A55678B to hkp://pgp.mit.edu

$ gpg --keyserver hkp://pgp.surfnet.nl --send-keys BF3B5AFCD4480E60 E881015C8A55678B
gpg: sending key BF3B5AFCD4480E60 to hkp://pgp.surfnet.nl
gpg: sending key E881015C8A55678B to hkp://pgp.surfnet.nl

Publish to Keybase

If you are a Keybase user, you should also publish your new key there:

$ keybase pgp select
You are selecting a PGP key from your local GnuPG keychain, and
will publish a statement signed with this key to make it part of
your Keybase.io identity.

Note that GnuPG will prompt you to perform this signature.

You can also import the secret key to *local*, *encrypted* Keybase
keyring, enabling decryption and signing with the Keybase client.
To do that, use "--import" flag.

Learn more: keybase pgp help select

#    Algo    Key Id             Created   UserId
=    ====    ======             =======   ======
1    4096R   E881015C8A55678B             Daniel Pecos Martinez <me@danielpecos.com>, Daniel Pecos Martinez <dani@dplabs.io>
Choose a key: 1
▶ INFO Generated new PGP key:
▶ INFO   user: Daniel Pecos Martinez <me@danielpecos.com>
▶ INFO   4096-bit RSA key, ID E881015C8A55678B, created 2019-03-27

Backup everything

Last step is to make a backup of our new key pair (and the old one if we didn’t already). We will encrypt the private key files before storing them, adding an extra layer of security in case these files are uploaded to a cloud storage or somewhere else not 100% under our control.

First the old key pair (public & secret):

gpg --export --armor BF3B5AFCD4480E60 > BF3B5AFCD4480E60.pub
gpg --export-secret-keys --armor BF3B5AFCD4480E60 | gpg --symmetric --armor > BF3B5AFCD4480E60.sec

Latest command will ask you for a password 3 times: the first two correspond to the symmetric encryption (gpg --symmetric --armor) and the password has to be exactly the same both times; third time corresponds to the key pair’s password (gpg --export-secret-keys --armor, in this case, your old key pair’s password).

Now let’s backup the new key pair (also public & secret):

gpg --export --armor E881015C8A55678B > E881015C8A55678B.pub
gpg --export-secret-keys --armor E881015C8A55678B | gpg --symmetric --armor > E881015C8A55678B.sec

And also the revocation certificate that was generate during key creation:

cat ~/.gnupg/openpgp-revocs.d/31EFB482E969EB74399DBBC5E881015C8A55678B.rev |  gpg --symmetric --armor > E881015C8A55678B.rev
rm ~/.gnupg/openpgp-revocs.d/31EFB482E969EB74399DBBC5E881015C8A55678B.rev

When exporting and encrypting the secret keys, we must use a different password for the backup encryption than the key’s password, otherwise this extra layer won’t provide any security in case of a 3rd party trying to break the password.

Store your master key pair offline and remove it from your keyring - Laptop keys

Now that we have everything backed up, we should remove our master key from the day-to-day device and use only the previously generated subkeys. The process is a little bit tricky, but not really hard. First of all, let’s export the subkeys to a (encrypted) file:

gpg --export-secret-subkeys --armor E881015C8A55678B | gpg --symmetric --armor > E881015C8A55678B-subkeys.sec

Now we delete the master key from our keyring (public & private keys):

$ gpg --delete-secret-key E881015C8A55678B

gpg (GnuPG) 2.2.14; 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.


sec  rsa4096/E881015C8A55678B 2019-03-27 Daniel Pecos Martinez <me@danielpecos.com>

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

$ gpg --delete-key E881015C8A55678B

gpg (GnuPG) 2.2.14; 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.


pub  rsa4096/E881015C8A55678B 2019-03-27 Daniel Pecos Martinez <me@danielpecos.com>

Delete this key from the keyring? (y/N) y

And finally we import back just the subkeys. First the secret part:

$ cat E881015C8A55678B-subkeys.sec | gpg --decrypt | gpg --import
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
gpg: key E881015C8A55678B: public key "Daniel Pecos Martinez <me@danielpecos.com>" imported
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key E881015C8A55678B: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
gpg: no ultimately trusted keys found

and then the public:

$ cat E881015C8A55678B.pub| gpg --import
gpg: key E881015C8A55678B: "Daniel Pecos Martinez <me@danielpecos.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

Lastly we have to reset trust to ultimate for the imported subkeys:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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 subkeys are available.

pub  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: unknown       validity: unknown
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ unknown] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)  Daniel Pecos Martinez <dani@dplabs.io>

gpg> trust
pub  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: unknown       validity: unknown
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ unknown] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)  Daniel Pecos Martinez <dani@dplabs.io>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

pub  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: unknown
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ unknown] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ unknown] (2)  Daniel Pecos Martinez <dani@dplabs.io>
Please note that the shown key validity is not necessarily correct
unless you restart the program.

gpg> save
Key not changed so no update needed.

Let’s double check that we don’t have the master key available in our keyring, just the subkeys. If we try to edit the master key:

$ gpg --edit-key 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg (GnuPG) 2.2.14; 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 subkeys are available.

pub  rsa4096/E881015C8A55678B
    created: 2019-03-27  expires: never       usage: SC
    trust: ultimate      validity: ultimate
ssb  rsa4096/89BF354A17A61CC5
    created: 2019-03-27  expires: 2024-03-25  usage: E
ssb  rsa4096/665E29633F97EF11
    created: 2019-03-27  expires: 2024-03-25  usage: S
[ultimate] (1). Daniel Pecos Martinez <me@danielpecos.com>
[ultimate] (2)  Daniel Pecos Martinez <dani@dplabs.io>

We’ll see the message:

Secret subkeys are available.

Meaning that we won’t be able to create new subkeys, revoke, …:

gpg> addkey
Need the secret key to do this.

$ gpg --gen-revoke 31EFB482E969EB74399DBBC5E881015C8A55678B
gpg: secret key "31EFB482E969EB74399DBBC5E881015C8A55678B" not found: No secret key

For those critical operations we will have to import the backed up master key. For business as usual operations, subkeys are good enough, and if we loose them, we can restore the master key, revoke the subkeys and generate a new set, without our changing PGP/GPG ID and thus losing the established trust.

At this point you could delete the backups for the old keys, but if you are like me, and given how little disk space they take, you’d keep them around.

This tutorial is based on information gathered from the following links: