Backup your GPG keys

8 minute read

After creating your GPG keys from your air-gapped system, you might want to back them up to prevent the loss of data or the loss of access to your servers. To ensure that your private keys are not leaked, you should perform the backup operations from the air-gapped system.

Files to backup

It is advised to backup the following files :

  • gpg.conf
  • pubring.kbx
  • the private key
  • the revocation certificate

Public files

The following files are public and can be stored on a piece of paper, or a basic USB key:

  • gpg.conf
  • pubring.kbx

Export your public key with :

gpg2 --export --armor $KEYID > pubkey.asc

pubkey.asc is legible ASCII and will be later used to import your public GPG key.

And create the public gpg ring with:

gpg2 --export $KEYID > pubring.gpg

Private files

The following files should be stored in a very secure place. Giving access to these files to someone means compromising your key :

  • the private key
  • the revocation certificate
  • your private sub keys

To store these files securely we will back them up :

  • on an encrypted USB key
  • on paper
  • as a QR code, on paper

Backup on an encrypted USB

Source: drduh. To backup the keys on an encrypted USB drive we will use LUKS along with a basic USB drive. You can also use a Nitrokey which is fully open-source, or an IronKey which is not open-source.

Plug your USB

To create an encrypted USB drive, first attach it and check its label:

$ dmesg | tail
[ 7667.607011] scsi8 : usb-storage 2-1:1.0
[ 7667.608766] usbcore: registered new interface driver usb-storage
[ 7668.874016] scsi 8:0:0:0: USB 0: 0 ANSI: 6
[ 7668.874242] sd 8:0:0:0: Attached scsi generic sg4 type 0
[ 7668.874682] sd 8:0:0:0: [sde] 62980096 512-byte logical blocks: (32.2 GB/30.0 GiB)
[ 7668.875022] sd 8:0:0:0: [sde] Write Protect is off
[ 7668.875023] sd 8:0:0:0: [sde] Mode Sense: 43 00 00 00
[ 7668.877939]  sde: sde1
[ 7668.879514] sd 8:0:0:0: [sde] Attached SCSI removable disk

Check the size to make sure it’s the right drive:

$ sudo fdisk -l | grep /dev/sde
Disk /dev/sde: 30 GiB, 32245809152 bytes, 62980096 sectors
/dev/sde1        2048 62980095 62978048  30G  6 FAT16

Create a new partition table

Erase and create a new partition table:

$ sudo fdisk /dev/sde

Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): o
Created a new DOS disklabel with disk identifier 0xeac7ee35.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

Remove and reinsert the USB drive, then create a new partition, selecting defaults::

$ sudo fdisk /dev/sde

Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-62980095, default 2048):
Last sector, +sectors or +size{K,M,G,T,P} (2048-62980095, default 62980095):

Created a new partition 1 of type 'Linux' and of size 30 GiB.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

Encrypt the partition

Use LUKS to encrypt the new partition:

$ sudo cryptsetup luksFormat /dev/sde1

This will overwrite data on /dev/sde1 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase:
Verify passphrase:

Mount the partition

Mount the partition:

$ sudo cryptsetup luksOpen /dev/sde1 encrypted-usb
Enter passphrase for /dev/sde1:

Create a filesystem:

$ sudo mkfs.ext4 /dev/mapper/encrypted-usb -L encrypted-usb
mke2fs 1.42.12 (29-Aug-2014)
Creating filesystem with 7871744 4k blocks and 1970416 inodes
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

Mount the filesystem:

$ sudo mkdir /mnt/usb
$ sudo mount /dev/mapper/encrypted-usb /mnt/usb

Transfer your files

Finally, copy files to it:

$ sudo cp -avi $GNUPGHOME /mnt/usb
‘/tmp/tmp.aaiTTovYgo’ -> ‘/mnt/usb/tmp.aaiTTovYgo’
‘/tmp/tmp.aaiTTovYgo/revoke.txt’ -> ‘/mnt/usb/tmp.aaiTTovYgo/revoke.txt’
‘/tmp/tmp.aaiTTovYgo/gpg.conf’ -> ‘/mnt/usb/tmp.aaiTTovYgo/gpg.conf’
‘/tmp/tmp.aaiTTovYgo/trustdb.gpg’ -> ‘/mnt/usb/tmp.aaiTTovYgo/trustdb.gpg’
‘/tmp/tmp.aaiTTovYgo/random_seed’ -> ‘/mnt/usb/tmp.aaiTTovYgo/random_seed’
‘/tmp/tmp.aaiTTovYgo/master.key’ -> ‘/mnt/usb/tmp.aaiTTovYgo/master.key’
‘/tmp/tmp.aaiTTovYgo/secring.gpg’ -> ‘/mnt/usb/tmp.aaiTTovYgo/secring.gpg’
‘/tmp/tmp.aaiTTovYgo/mastersub.key’ -> ‘/mnt/usb/tmp.aaiTTovYgo/mastersub.key’
‘/tmp/tmp.aaiTTovYgo/sub.key’ -> ‘/mnt/usb/tmp.aaiTTovYgo/sub.key’
‘/tmp/tmp.aaiTTovYgo/pubring.gpg~’ -> ‘/mnt/usb/tmp.aaiTTovYgo/pubring.gpg~’
‘/tmp/tmp.aaiTTovYgo/pubring.gpg’ -> ‘/mnt/usb/tmp.aaiTTovYgo/pubring.gpg’

Make sure the correct files were copied, then unmount and disconnected the encrypted USB drive:

$ sudo umount /mnt/usb
$ sudo cryptsetup luksClose encrypted-usb

Backup with paperkey

In this example we backup the master key and its subkeys with paperkey.

List existing secret keys

First we can list the existing secret keys. Note in this example there is only the master key, but if you have already created subkeys, they will be backed up as well.

$ gpg2 --list-secret-keys
# Output
gpg: WARNING: unsafe permissions on homedir '/tmp/keyCreation'
sec   rsa4096/0x45B2745200A5D6B1 2017-10-03 [SC] [expires: 2020-10-02]
      Key fingerprint = 0B91 39BF 0D9F 3C96 F505  9B30 45B2 7452 00A5 D6B1
uid                   [ultimate] John Doe <>

Backup the master key

We create a directory that will hold the

mkdir backup

And we backup the master key and subkeys with paperkey

gpg2 --export-secret-keys | paperkey > backup/private.keys.paperkey

About gpg2 --export-secret-subkeys

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.

The name of the backup file starts with the key signature. Note that the exported key is still encrypted with your password, so make sure that you remember it later !

Now that you have the key exported as ASCII text, you can print it and store it in a secure place (a safe in your bank for instance). If you ever need to manage your subkeys, you will have to restore your master key on an air-gapped system.

Be careful with the printer you use, they often store in memory the files they were transmitted. Even if your file is encrypted, it is still a risk to acknowledge.

If you are afraid to forget your password, write it on the paper backup before storing it in your very secure place !

Backup as QR code

You should also have a QR code backup of your private keys. The advantages are :

  • it adds a redundancy : if your ASCII backup is damaged, you will have your QR code backup available.
  • it prevents human error : typing your master key by hand from your ASCII backup will be tedious. Using a QR code facilitates its retrieval.
  • it is damage resistant : QR codes have error correction capability to restore data if the code is damaged.

Export the keys

gpg2 --export-secret-keys --armor  > private.master.key.asc

The exported key is encrypted but readable in the ASCII format. This export does not contains correction checksums and is less readable than the paperkey export; however this lack of redundancy is exactly what we look for since the QR code will manage the redundancy itself.

The export should look like that :



Split the key

qrencode has a limit of 2953 characters :

qrencode is encoding your private GPG key as 8 bit (binary|utf-8), because the key is not pure alphanumeric. It contains special character. the alphanumeric mode only supports those special character .(%*+-./:). So the maximum GPG key can only be 2953 char long.

To circumvent this limit we will split the exported private key and generate multiple QR codes.

split -C 2500 private.keys.asc splitkey-

This creates two files :

  • splitkey-aa
  • splitkey-ab

Create the QR codes

We create the two QR codes with qrencode:

cat splitkey-aa | qrencode -o private.keys.qrcode.1.png
cat splitkey-ab | qrencode -o private.keys.qrcode.2.png

Here is a command to do it automatically :

i=0; for f in $(ls splitkey*); do echo "Encoding $f : file $i" \
&& cat $f | qrencode -o private.keys.qrcode.$i.png; i=$(($i+1)); done

Verify the QR codes

We can verify that the QR codes we created correctly by extracting the data from the QR codes with zbarimg:

zbarimg private.keys.qrcode.2.png

scanned 1 barcode symbols from 1 images in 0.09 seconds

Test your backups

Before storing your backups in a secure place you should always try to restore them. There is an article explaining how to restore your backups, so before doing anything else, test the restoration of your backup. A backup that you can’t restore is just garbage securely stored.

Backup other files

Don’t forget to backup the revocation in a secure place (in ASCII and QR code), you can find it in openpgp-revocs.d. If you haven’t done so, you will also need to backup your private keys. And don’t forget to backup the following public files :

  • gpg.conf
  • pubring.kbx