How to remove all expired keys from your keyring

How can we remove our expired keys from our keyring?

In this post I’m going to briefly cover how to purge all expired and revoked keys from your keyring using gpg2.

Tl;Dr

gpg2 --list-public-keys --with-colons | grep -a '^pub:[re]:' | cut -d : -f5 | xargs -l gpg --batch --yes --delete-keys

Prerequisite knowledge

If you’re reading this post I’m assuming your well-versed enough to be able to use pgp keys fluidly to encrypt and sign data.

If not, DON’T worry. I recommend checking out these two linoxide posts to get a more comprehensive guide on how to use gpg.

The Backstory

As you may know, working with pgp keys using gpg is a fairly seamless, barebones process. However, that may not always be a good thing.

As I was learning more about keyring maintenance earlier today, I realized I had way to me expired keys still hanging out in my keyring. I also noticed that NO WHERE in the dusty documentation was there a way to easily purge your keyring of invalid keys. The most I could possibly do was utilize the gpg2 --list-public-keys --list-options show-unusable-uids and delete them individually from there.

show-unusable-uids Show revoked and expired user IDs in key listings. Defaults to no.

Not only would this be a tedious process if you haven’t been doing your regular cleaning, but I also found this command to be unreliable as it wouldn’t show me all of my invalid keys.

It may have had something to do with this bit of text that I also happened to stumble across in the dusty docs.

--with-colons Print key listings delimited by colons. Note that the output will be encoded in UTF-8 regardless of any --display-charset setting. This format is useful when GnuPG is called from scripts and other programs as it is easily machine parsed. The details of this format are documented in the file ‘doc/DETAILS’, which is included in the GnuPG source distribution.

Interesting…

It turns out that the human readable format that we are used to seeing for a typical gpg public key output doesn’t play nice with the machine. Instead we need to convert our output into a “machine parseable” format.

In Other Words

We have to go from something like this:

gpg2 --list-public-keys

pub   rsa4096 2015-01-05 [SC]
      2D1771FE4D767DJSA6B089FAD655A4F21830E06A
uid           [  expired  ] John Doe <hisexpiredkey.com>

pub   rsa4096 2012-11-15 [SC]
      5B2DA4B9F9JFPB2019d1878102F77ACF3F48CB21
uid           [  revoked  ] Jane Doe <herexpiredkey.org>
sub   rsa4096 2012-11-15 [E]

pub   rsa4096 2022-04-17 [SC] [expires: 2023-04-12]
      840391C6B5198E5BC46E29AE395DBDA8FA41644F
uid           [ultimate] Al.exe (Based in Los Angeles if anyone wants to trade signatures.) <alEXE-tech@protonmail.com>
sub   rsa4096 2022-04-17 [E] [expires: 2023-04-12]

To this:

gpg2 --list-public-keys --with-colons

uid:e::::1420483577::A4JFSYYYE957FE7F6D94F14069E9087E8A5FC4DA::John Doe <hisexpiredkey.com>::::::::::0:
pub:e:4096:1:ACC2602F3F4290SJLB21:1353005334:::-:::scESC::::::23::0:
fpr:::::::::B35B2DJFJ109F10949226F77ACC2602F3F48CB21:
uid:r::::1353005334::99A0C38FCCF794BF803B9E397F9DJ74389219594::Jane Doe <herexpiredkey.org>::::::::::0:
pub:r:4096:1:4F75939930498E2ADJDJ:13523215334::::::e::::::23:
fpr:::::::::73471302917A6769JJSJJKSK4F75939930498E2A:
uid:u::::1650178921::2BA40199B357A5BA344476AA186305F77F6CBF63::Al.exe (Based in Los Angeles if anyone wants to trade signatures.) <alEXE-tech@protonmail.com>::::::::::0:
sub:u:4096:1:FEF4CDCAD14A38FD:1650178921:1681282921:::::e::::::23:
fpr:::::::::7BA9BD9DEE25951A626DF556FEF4CDCAD14A38FD:

Quite a disgusting bit of output if you ask me. But if it helps us get the job done then so be it. More details on how to interpret this nonsense can be viewed here

The Command

After scouring the internet for a couple hours I managed to stumble across this old debian-security thread that had the piped command chain to do our job.

gpg2 --list-public-keys --with-colons | grep -a '^pub:[re]:' | cut -d : -f5 | xargs -l gpg --batch --yes --delete-keys

Let me quickly break down some of the important details for you all.

First we are going to list our public keys like I previously showed you. We are then piping that into grep so we will only target the public keys (pub), that are either revoked(r) or expired(e).

pub:e:4096:1:ACC2602F3F4290SJLB21:1353005334:::-:::scESC::::::23::0:
pub:r:4096:1:4F75939930498E2ADJDJ:13523215334::::::e::::::23:

We then utilize the cut command to separate our line sections by colons and remove all columns, EXCEPT the 5th column (or field).

ACC2602F3F4290SJLB21
4F75939930498E2ADJDJ

From there, it’s just a matter of running gpg --delete-keys on a batch of keys.


Conclusion

GnuPG does one job, and it does that job right! Unfortunately, it can be a bit of a headache at times.

Thanks for Reading! :>

– Al.exe