Set up and manage PGP keys

In this page, we outline how to set up and use PGP keys to fulfil the policies and goals outlined in PGP key recommendations for usage in the Ubuntu project.

Outcome

The high level goal is to keep private key material off disk and use hardware keys for day-to-day operations.

  1. One long-term primary key (C), used only to certify subkeys and locked away in a safe place

  2. Multiple signing subkeys (S) used for signing (one per YubiKey)

  3. One encryption subkey (E) used for encryption on all YubiKeys

  4. Subkeys only stored on a YubiKey (smartcard) – not on disk

  5. A fallback plan in case one YubiKey is lost, expired or compromised

  6. Works with Launchpad/Ubuntu uploads, signed git commits and encryption

Prerequisites

Hardware

You need one YubiKey per subkey. The YubiKey(s) must support OpenPGP.

Packages

Install the following packages:

sudo apt update
sudo apt install -y gnupg scdaemon pcscd
sudo systemctl enable --now pcscd.socket

GnuPG

GnuPG is an encryption tool that helps manage your encryption keys. You’ll need it later to be able to add a signature to each upload.

Eventually the private key will represent your identity and therefore must be kept safe and out of reach of other entities.

It is best practice to generate the primary key on an offline machine (or at least an offline session) and only use it to create/rotate subkeys. Using a Ubuntu Live image is also a great option as the system is ephemeral and no key will remain on disk once this process is completed.

If that’s too heavy, you can still do this on your main machine, but make sure to clear all keys from your hard drive before you get back online and treat the backup step as non-negotiable.

Step by Step instructions

The following sections will guide you through creating a primary key and putting the associated subkeys onto hardware keys. After these steps follows a section about Day 2 operations.

Step 1 — Create the primary key

If you already have a primary key you can skip this step.

gpg --expert --full-generate-key

Pick your key type:

  • Type: (9) ECC (sign and encrypt) (or RSA 4096 if you have strict compatibility needs)

  • For ECC: Curve 25519 is a common modern choice

Now you must:

  • Set an expiration (e.g., 1–2 years) — you can extend this later

  • Set a passphrase to protect your primary key

List your keys:

>gpg --list-secret-keys --keyid-format=long
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2031-02-02
------------------------
sec   ed25519/1234567890ABCDEF 2026-02-03 [SC] [expires: 2031-02-02]
      1234567890ABCDEF1234567890ABCDEF123456
uid                 [ultimate] Foo Bar <foo.bar@company.com>
uid                 [ultimate] Some One <some.1@gmail.com>
ssb   cv25519/DEADBEEFDEADBEEF 2026-02-03 [E] [expires: 2031-02-02]

For the further steps in this how-to export the fingerprint into an environment variable (replace with yours):

export KEYFPR="YOUR_PRIMARY_KEY_FINGERPRINT"

The primary key fingerprint in the previous example is: C272017B1AC7539AFC0E6DBCAA7EED1BC821DF7D

Important

If - for now - you only wanted to create the software key and do not intend to use individual subkeys and hardware keys you may jump to section Step 3 - Make your new key(s) known and then be done for now.

Step 2 — Add subkeys

This section shows how to create Signing and Encryption subkeys using the gpg tool:

2.1 Add a signing subkey

gpg --expert --edit-key "$KEYFPR"

Inside the gpg> prompt, add your subkey using:

gpg> addkey

If you want to set up multiple redundant YubiKeys (allow different expiration, allow individual revocation), add another signing key in the same way.

2.2 Add an encryption subkey

You can use the same addkey command to create an encryption subkey.

2.3 Save and verify subkeys

gpg> save

Then verify – ensure you have S, C and E entries:

gpg --list-secret-keys --with-subkey-fingerprint --keyid-format=long "$KEYFPR"

This output shows something like:

sec   ed25519/1234567890ABCDEF 2026-02-03 [SC] [expires: 2031-02-02]
      1234567890ABCDEF1234567890ABCDEF123456
uid                 [ultimate] Foo Bar <foo.bar@company.com>
uid                 [ultimate] Some One <some.1@gmail.com>
ssb   cv25519/DEADBEEFDEADBEEF 2026-02-03 [E] [expires: 2031-02-02]
      DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE
ssb   ed25519/CAFEBABECAFEBABE 2026-02-03 [S] [expires: 2029-02-02]
      CAFEBABECAFEBABECAFEBABECAFEBABECAFEBA
ssb   ed25519/FAFABABAB0B0B0B0 2026-02-03 [S] [expires: 2028-02-02]
      FAFABABAB0B0B0B0FAFABABAB0B0B0B0FAFABA

Which includes:

  • A primary key that can be used for signing or certifying the identity of the key chain (SC)

  • One subkey for encryption [E]

  • And two additional subkeys for signing [S]

Step 3 - Make your new key(s) known

If your key is entirely new, or you hereby added additional keys after already uploading the key to the Ubuntu keyserver in the past – you will need to re-upload, otherwise Launchpad will silently reject uploads signed with the (sub)keys.

Use the following command to ensure your public key is uploaded to the Ubuntu keyserver.

gpg --keyserver hkps://keyserver.ubuntu.com --send-keys "$KEYFPR"

Step 4 — Backup

Important

Do this before touching the YubiKey!

This is the part that saves you later.

Export your public key:

gpg --armor --export "$KEYFPR" > /path/to/your/backup/public-key.asc

Export your secret keys (primary + subkeys):

gpg --armor --export-secret-keys "$KEYFPR" > /path/to/your/backup/secret-key.asc

Generate a revocation certificate:

gpg --output /path/to/your/backup/revocation.asc --gen-revoke "$KEYFPR"

Finally, store these backups offline and encrypted and remove them from your system. We acknowledge that this leaves the details of that backup setup intentionally under-specified. Having any truly offline and encrypted setup will, in most places, be better than what has been used before. Here variety is ok and there is more than “one right setup”. Over time we might add more examples here for your inspiration:

  • Multiple (fail-safe for HW issues, stored in two places) USB-Stick (offline), using cryptsetup(8) (encrypted) to be mounted

Step 5 — Prepare the YubiKey

Insert the YubiKey and verify it is visible. Make sure you have only one YubiKey inserted at a time.

Check if GPG recognizes the YubiKey:

gpg --card-status

Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240100000006349698960000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 12345678
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
KDF setting ......: off
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

If the output says “No such device”, try restarting gpg-agent:

gpgconf --kill gpg-agent

Set OpenPGP user info (name/login/url) via:

gpg --edit-card

In the gpg\> prompt you should use the following commands in this order:

admin    - Toggle the usability of privileged commands in this session
passwd   - Set your own admin pin (defaults to 12345678)
passwd   - Set your own standard pin (defaults to 123456)
name     - Set who you are
url      - Set your key's keyserver url for obtaining public key
           like https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEYFPR
uif 1 on - Require touch action for signing
uif 2 on - Require touch action for encryption/decryption
quit

These steps set touch interaction for signing and decryption, to protect against attackers who may have control of your system so they cannot sign something without your knowledge.

The default already requires the user pin on first usage after the key has been plugged in, but only once. gpg> forcesig can change that to be always required if you prefer that (less comfort, more security).

Important

To prepare a second YubiKey, repeat Step 5 — Prepare the YubiKey with another YubiKey plugged into the system.

Step 6 – Move subkeys to YubiKey

First, you need to move the signing subkey to the signature slot. Open the key editor, which will start listing the keys:

gpg --edit-key "$KEYFPR"

Secret key is available.

sec  ed25519/1234567890ABCDEF
     created: 2026-02-03  expires: never       usage: SC
     trust: utlimate      validity: unknown
ssb  cv25519/DEADBEEFDEADBEEF
     created: 2026-02-03  expires: 2031-02-02  usage: E
ssb  ed25519/CAFEBABECAFEBABE
     created: 2026-05-14  expires: 2028-05-13  usage: S
ssb  ed25519/FAFABABAB0B0B0B0
     created: 2026-05-14  expires: 2027-08-07  usage: S
[ultimate] (1). Foo Bar <foo.bar@company.com>
[utlimate] (2)  Some One <some.1@gmail.com>

Select a key. From the previous example, keys are listed in order, starting from 0. To pick the first listed signing key in this example:

gpg> key 2

This toggles the selection of subkeys – the selected ones will now show a *.

If needed do further (un)select actions with the “key” command to match your needs, this guide assumes you selected one signing subkey, then:

gpg> keytocard

Choose the slot:

  • Signature

You will be asked for the passphrase of your primary key as well as the admin pin of the hardware key.

Since we want the signing key to only be present on the hardware, issue save which will remove it from the local keyring.

gpg> save

Listing your secret keys will now show that it has been moved to the card represented by > after the subkey.

gpg --list-secret-keys --with-subkey-fingerprint
...
ssb>  ed25519 2026-05-14 [S] [expires: 2028-05-13]
      DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
      Card serial no. = 0006 12345678

Next, move the encryption subkey to the encryption slot.

Start over with gpg --edit-key again, select the encryption subkey (e.g., key 1), then chose the Encryption slot.

Important

After moving, you will be prompted again to save changes. DO NOT SAVE, since this will delete the encryption key stored in your computer. We want to keep the encryption key around to use the same one on every hardware key.

$ gpg --edit-key "$KEYFPR"
gpg> key 1
# Make sure the key with "usage: E" is selected
gpg> keytocard
# Choose slot Encryption key
gpg> 2
# Leave the tool
gpg> quit
# It should look like this:
# Save changes? (y/N) n
# Quit without saving? (y/N) y

Important

To handle a second hardware key, run through the steps in this section again with the other hardware key plugged into your system.

At the step of transferring the signing key (Where you selected the first subkey when doing the first iteration of the steps), please select the second signing subkey this time. Combined with the different expiration dates, this ensures they will not both expire at the same time.

Step 7 — Remove your keys from the system

Important

Before you go on, ensure that you stored the backups created in step 3 in a safe location.

Now that you have two hardware keys (one signature subkey each, and both with the same for encryption) it is time to remove the key material from your local keyring. After that, and after you moved your backups to a safe place – no secret key material will remain on the system itself.

gpg --delete-secret-keys "$KEYFPR"

List your secret keys again to verify they are all known, but not present on the system anymore.

The following output shows:

  • The primary key with # as (sec#) which marks it as offline and not usable.

  • Subkeys marked with > like (ssb>) which marks them as being stored on a smartcard.

$ gpg --list-secret-keys --with-subkey-fingerprint
/home/youruser/.gnupg/pubring.kbx
---------------------------------------------------------
sec#  ed25519 2026-02-03 [SC]
      1234567890ABCDEF1234567890ABCDEF123456
uid                 [ultimate] Foo Bar <foo.bar@company.com>
uid                 [ultimate] Some One <some.1@gmail.com>
ssb>  cv25519 2026-02-03 [E] [expires: 2031-02-02]
      DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE
      Card serial no. = 0006 12345678
ssb>  ed25519 2026-05-14 [S] [expires: 2028-05-13]
      CAFEBABECAFEBABECAFEBABECAFEBABECAFEBA
      Card serial no. = 0006 12345678
ssb>  ed25519 2026-05-14 [S] [expires: 2027-08-07]
      FAFABABAB0B0B0B0FAFABABAB0B0B0B0FAFABA
      Card serial no. = 0006 12345678

Step 8 — Git commit signing

First, you need to configure Git. To enable signing git commits, set the commit.gpgsign option. Either per repository:

git config commit.gpgsign true

Or globally:

git config --global commit.gpgsign true

If you have more than one primary key with the same UID (username and mail), you can specify which key to use for signing.

git config --global user.signingkey "$KEYFPR"

If you want to sign a single commit:

git commit -S

Test commit signing:

cd $(mktemp -d)
git init
git commit -S -m "test signed commit" --allow-empty
git log -1 --show-signature

You should see: Good signature.

Finally, verification.

Do not forget to upload your public key in the different Git servers hosting your work. This shows how you can do it for GitHub:

gpg --armor --export "$KEYFPR"

Copy/paste into GitHub → Settings → SSH and GPG keys.

Step 9 — Launchpad and Ubuntu upload signing

Ensure Launchpad knows about your public key to associate your username, and thereby its permissions, with your signed uploads: https://launchpad.net/~<your-launchpad-username>/+editpgpkeys.

After building a package, you can sign the .changes file using debsign.

debsign <filename>_source.changes

It will determine the signature needed from the email in the changelog stanza. If this does not work out of the box, you might need to explicitly configure your signing key by exporting the DEBSIGN_KEYID="$KEYFPR".

You can try uploading a package to a PPA as described in the project documentation using the key associated with your Launchpad account.

Day 2 operations

This section covers some critical operations that you might need to look into some time later, some of which require the use of your backup of these keys.

Let another system use the same keys

You received your new computer and want to import your key.

There is no need to copy private keys, they move with the hardware keys now plugged in here.

First install the packages mentioned in the prerequisites.

Then plug in the YubiKey and check if it is detected:

gpg --card-status

This should be the same as what you saw when setting up the hardware key.

You can import your key automatically using the URL you set on your YubiKey at setup time. Or get it from the keyserver directly by specifying which key you need.

Import via the YubiKey’s stored URL:

gpg --edit-card
gpg> fetch

It should show:

# gpg: requesting key from 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$YOURKEY'
# gpg: key DEADBEEFDEADBEEF: public key "Your Name <yourname@example.com>" imported
# gpg: Total number processed: 1
# gpg:               imported: 1

Otherwise import the .asc public key you backed up, and run:

gpg --import /path/to/your/backup/public-key.asc

Verify that your keys have been imported:

gpg --list-keys

Trust yourself (well, your key):

gpg --edit-key "$KEYFPR"
gpg> trust
# 5 = I trust ultimately
gpg> 5
# Do you really want to set this key to ultimate trust? (y/N)
gpg> y
gpg> quit

To confirm keys are loaded and working correctly try signing with them:

echo "Ubuntu is awesome" | gpg --clearsign

Updating expiration dates

This section shows how to extend the expiration date of the different keys. To update the expiration date, you must have access to the private keys, so first import the secret keys from the backup file.

We should avoid interacting with the usual GPG keyring by temporarily creating a new one to do this operation. This makes the whole process simpler, since you won’t need to remove the secret keys after updating the expiration date.

export GNUPGHOME="$(mktemp -d -t gnupg-XXXXXXXX)"
export KEYFPR="YOUR_PRIMARY_KEY_FINGERPRINT"

# Import the key
gpg --import /path/to/your/backup/secret-key.asc
gpg --edit-key "$KEYFPR"

This lists the keys:

Secret key is available.

sec  ed25519/1234567890ABCDEF
     created: 2026-02-03  expires: never       usage: SC
     trust: ultimate      validity: unknown
ssb  cv25519/DEADBEEFDEADBEEF
     created: 2026-02-03  expires: 2031-02-02  usage: E
ssb  ed25519/CAFEBABECAFEBABE
     created: 2026-05-14  expires: 2028-05-13  usage: S
ssb  ed25519/FAFABABAB0B0B0B0
     created: 2026-05-14  expires: 2027-08-07  usage: S
[ultimate] (1). Foo Bar <foo.bar@company.com>
[utlimate] (2)  Some One <some.1@gmail.com>

In the gpg> prompts then:

# 1. Extend the primary key if needed (default selected)
gpg> expire
# enter duration (e.g. 1y)
1y
# Repeat to update expiration for other subkeys (key 2, key 3, etc.)
gpg> key 2
gpg> expire
1y
# after updating all keys, save and exit
gpg> save

Since we are operating under a temporary GNUPGHOME, don’t forget to export the updated public key as your new backup.

gpg --armor --export "$KEYFPR" > /path/to/your/backup/public-key.asc

Now we can safely delete the temporary GNUPGHOME and import the updated public key to the real local keychain.

rm -rf "$GNUPGHOME"
unset GNUPGHOME
gpg --import /path/to/your/backup/public-key.asc

Then, upload the updated key to the keyserver as shown in Step 3 - Make your new key(s) known.

If a subkey is compromised

If you followed this guide, then your keys should be safely locked away and backed up, and if only a subkey is compromised, we can use the primary key to generate new subkeys.

Do this the instant you are able! The longer you wait, the longer anyone with access to your compromised key can sign things on your behalf!

Import the backup keys as documented in the previous section. List the imported keys and extract the primary key fingerprint:

gpg --list-secret-keys
export KEYFPR="YOUR_PRIMARY_KEY_FINGERPRINT"

Now edit, select and revoke the compromised/lost subkeys:

gpg --edit-key "$KEYFPR"

Then select the subkey (by index) and revoke it:

Important

Warning - this is an irreversible action!

gpg> key $SUBKEY_INDEX
gpg> revkey

Save your changes and immediately upload to any keyserver(s) to which you previously used your key following the steps of Step 3 - Make your new key(s) known.

After doing that repeat steps Step 2 — Add subkeys to Step 7 — Remove your keys from the system, to create new subkeys to replace the revoked ones on your hardware keys.

If the primary key is compromised

In this case, you should revoke the entire key using the revocation certificate generated in the backup step. To do this, acquire your previously generated and backed up revocation certificate and upload it to the keyserver(s):

Important

Warning - this is an irreversible action!

gpg --import /path/to/your/backup/revocation.asc
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys "KEYFPR"