How to rotate your OpenPGP / GnuPG keys
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.
Links
This tutorial is based on information gathered from the following links: