GnuPG is a freely available command line encryption suite based on OpenPGP encryption library, and it is probably the most used encryption suite in the world. Every IT professional sooner or later face use cases that require to know how to handle it, but the point is that, despite a trivial usage of this suite may look quite easy, there are some not so obvious nuances that if known can really improve the experience, also saving from making some hidden mistakes that can really painful if anything would go wrong.
This is a quick, easy yet comprehensive GPG tutorial with the aim to provide a quick yet thorough explanation of how to safely use this amazing encryption suite.
PGP, GnuPG, Open PGP
These are tightly bound terms that quite often people mislead: this is certainly because of the lots of changes that happened during the long story of the evolution of PGP.
PGP - Pretty Good Privacy
PGP (Pretty Good Privacy) is an encryption software developed by Phil Zimmermann that uses a serial combination of hashing, data compression, symmetric-key cryptography and of course public-key cryptography.
Open PGP
In 1997 Zimmermann became convinced that an open standard for PGP encryption was critical for them and for the cryptographic community as a whole: PGP Inc. proposed to the IETF a new standard granting them permission to use the name OpenPGP to describe it as well as any program that supported the standard. The OpenPGP standard (RFC-4880), managed by the OpenPGP Working Group, is aimed at defining a mail encryption standard.
GPG - Gnu PG
GnuPG (GPG) is the free implementation of the PG encryption develped by Werner Koch in 1999. It probably is the most used suite that implements PG.
The Lab Environment
The most straightforward way to learn GPG is seeing it in action, seeing the interactions between at least five users; for this reason we are going to setup a small lab with the following users:
- foo
- bar
- baz
- qux
- fred
We will use them to learn how to generate the keys and to trust someone else's keys as well as exchange GPG-encrypted or signed data.
Install And Configure The Vim Plugin
For the ancient ones (like me) who love the old fashioned Vim, there's a GPG plugin that lets users seamlessly edit encrypted files within vim.
Since vim is broadly used, it seems wise to deliver this plugin along with some reasonable defaults to every newly created user; it is enough adding it to the /etc/skel directory as follows:
We also add the /etc/skel/.vimrc configuration file and set up as to to have Vim create armored files (ASCII files, so that they are easy to cut-and-paste or to send by email).
" Armor files
let g:GPGPreferArmor=1
Finally, let's configure the gpg-agent related variables, or the plugin can get mixed-up about what values to use:
Create The Users
Let's create the users necessary for this lab as follows:
then we set a password to each of them.
foo user:
bar user:
baz user:
qux user:
and of course fred:
The aim of this lab is mock-up real-life: in a real-life scenario most of the times the system engineer connects to a remote host using SSH and once connected type the GPG commands - or switches to other users using sudo before typing them. For this reason I'm either showing some commands after switching the user with sudo, or submitting them using SSH.
Since the actual behavior is the same, to keep our lab conveniently small, instead of connecting to a remote system we SSH connect to the localhost with these users .
Let's SSH connect as any of the above users:
Of course the first time we have to set as trusted the fingerprint the host key of the localhost:
The authenticity of host 'localhost (::1)' can't be established.
ECDSA key fingerprint is SHA256:uTJ2NlprMvpiMbtCIW1Z0vBWacam13gkvHQOPcqpH7c.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
simply type "yes".
Configure User-Specific GPG Settings
Obviously each user can customize GPG as by his own needs - for example, to customize it for the "foo" user:
create the ~/.gnupg directory:
GnuPG reads its configuration from the ~/.gnupg/gpg.conf file - create it with the following contents:
no-greeting
require-cross-certification
# default-recipient-self
keyid-format long
with-fingerprint
personal-digest-preferences SHA512
cert-digest-algo SHA512
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
fixed-list-mode
no-emit-version
#no-comments
verify-options show-uid-validity
list-options show-uid-validity
keyserver hkps://keys.openpgp.org
keyserver-options no-honor-keyserver-url
keyserver-options include-revoked
keyserver-options auto-key-retrieve
this sample file contains some options to avoid some information leakage and to prefer strong algorithms.
When done with editing this file, disconnect the "foo" user:
Getting Acquainted To GPG
This post focuses on GnuPG 2.1.18 or later - this means that if you are using Red Hat or CentOS 8 or Rocky Linux 8 the GPG suite is already installed with the right version.
Let's verify the installed version on your system:
the output on my system is:
gpg (GnuPG) 2.2.20
libgcrypt 1.8.5
Copyright (C) 2020 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: /home/vagrant/.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
as you can see, ECDH is listed among the Pubkey: this means that this version does support Elliptic Curves keys. In order to choose which one to use, we can list the supported ECC curves as follows:
the output on my system is:
cfg:curve:cv25519;ed25519;nistp256;nistp384;nistp521;secp256k1
Generating the key-pair(s)
GPG relies both on symmetric and asymmetric cryptography: more precisely it uses
- public keys to encrypt files and verify digital signatures
- private keys to digitally sign files or decrypt files
The best practice in a real-life scenario is to have a Primary private key used to generate subkeys either for encrypt, sign or authentication specific purposes, but you can just have a single key to do everything as a whole, although this is not recommended.
So the very first thing to do is generate your own keys.
Key Types And Capabilities
GPG Keys are called:
- Keys (The Primary Keys): this kind of key has Certification and Signing capabilities
- sec identifies the secret key
- pub identifies the public key
- Subkeys (Keys bound to the Primary Key)
- ssb identifies the secret key
- sub identifies the public key
Mind that keys can have one or more of the following capabilities:
SSH as bare ssh-rsa keys (monkeysphere subkey-to-ssh-agent or HSM)
SSH as pgp-sign-rsa certificates
TLS according to RFC 5081 (supported by GnuTLS)
I guess there are other software that can use it
Generating the Primary Key Pair
The GPG environment of a user gets initialized as soon as he generates (or imports) its first private key.
My personal advice is to use both the --expert and --full-gen-key command line options since they enable all the features.
Just to show you - don't type it:
the outcome is:
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)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(9) ECC and ECC
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(13) Existing key
(14) Existing key from card
Your selection?
As you see, thanks to the previous command line switches, we can choose among 14 different options; some of them have the key type twice - for example "RSA and RSA'': these options are used to create both the Primary Key and a subkey within a single task. The default choice is "(1) RSA and RSA''.
While connected using SSH
Lets generate "bar" user's Primary Key simulating the use case of an SSH connected user:
as you see, it is as:
- we picked up the option "(8) RSA (set your own capabilities)"
- set the sign and cert capabilities
- set the key to never expire
Right after typing the password to SSH connect as foo user, gnupg asks for the password to be used protect the key we are about to generate:
┌─.....................................................┐
│ Please enter the passphrase to │
│ protect your new key │
│ │
│ Passphrase: ________________________________________ │
│ │
│ <OK> <Cancel> │
└─ ┘
choose a good password and wait until the generation of the key succeeds.
The last part of the output is as follows:
Note that this key cannot be used for encryption. You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub rsa2048 2022-07-21 [SC]
1414EDCC5BBBC87956624F381A44B621829F5714
uid Bar User (Bar User Primary Key) <bar@carcano.local>
In real life we would SSH login for true and then type the command to generate the Primary Key, ... but here to go a little bit faster instead of logging in we directly executed the GPG statement to generate the keys.
As by the note in the previous snippet, this key cannot be used for encryption: to say you all, the key-pair generated (we can call it primary-key) is used only for identifying the owner, ... we'll get on this very soon.
The best practice with GPG is avoiding to operate using the Primary Keys: just create subkeys for encryption and for digital signature, then save the Primary Key and its revocation certificate off-line, and store them in a very secure place.
While Switching User With Sudo
This is the time to show you how to deal with sudo, ... let's switch to the "baz" user:
we can now create the Primary Key as follows - note that this time we have to supply --pinentry-mode loopback or we won't be granted the right to access the to enter the password, causing the command to fail:
as you see, we are generating a key with the same features of the one we have just created for the "bar" user:
- "(8) RSA (set your own capabilities)"
- sign and cert capabilities
- key never expires
exit the sudoed shell to the previous user:
Elliptic Curve Keys
As for the foo user, ... we generate a key with the following features:
- ECC with nistp256 elliptic curve
- sign and cert capabilities
- key never expires
This time we do not switch user, we run the command "as the foo user" (-u foo):
Let's generate the Primary Key for the "qux" user:
and for the "fred" user:
Generating the Encryption subkey
We are ready to create a subkey for encryption purposes - become the "foo" user:
now launch the interactive wizard to create the subkey as follows:
here is a snippet of the interactive procedure:
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(12) ECC (encrypt only)
(13) Existing key
(14) Existing key from card
Your selection? 12
Please select which elliptic curve you want:
(1) Curve 25519
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(9) secp256k1
Your selection? 3
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) 1y
Key expires at Fri Jul 21 09:42:47 2023 UTC
Is this correct? (y/N) y
Really create? (y/N) y
as you see we created a key with the following features:
- "(12) ECC (encrypt only)"
- using elliptic curve NIST P-256 "(3) NIST P-256"
- valid for one year only (1y)
the last part of the output of the generation wizard lists the available keys, with the new one among them (ssb nistp256/F43E205F0A259AC7)
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
type "save" to store the new key into the keyring and exit from the GPG interactive command prompt back to the shell:
gpg> save
Let's create an encryption subkey also for the "bar" user - just exit the sudoed shell we are currently in and become the "bar" user:
we can avoid to use the interactive procedure, but first we need the fingerprint of the Primary Key:
the output is as follows:
the interactive procedure can be skipped by using the --quick-add-key option along with the answers to the questions of the interactive wizard right at the end of the statement:
we generated a key with the following features:
- RSA
- encryption capability
- valid for one year only
exit the sudoed shell:
Generating the Signing subkey
Same way we created an encryption subkey, we can create a signing subkey - become the "foo" user:
now launch the interactive wizard to create the subkey as follows:
here is a snippet of the interactive procedure:
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(12) ECC (encrypt only)
(13) Existing key
(14) Existing key from card
Your selection? 10
Please select which elliptic curve you want:
(1) Curve 25519
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(9) secp256k1
Your selection? 3
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) 1y
Key expires at Fri Jul 21 09:53:06 2023 UTC
Is this correct? (y/N) y
Really create? (y/N) y
as you see we created a key with the following features:
- "(10) ECC (sign only)"
- using elliptic curve NIST P-256 "(3) NIST P-256"
- valid for one year only (1y)
the last part of the output of the generation wizard lists the available keys, with the new one among them (ssb nistp256/4401D4D107578972)
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
type "save" to store the new key into the keyring and exit from the GPG interactive command prompt back to the shell:
gpg> save
Let's create an signing subkey also for the "baz" user - just exit the sudoed shell we are currently in and become the "baz" user:
as we saw, we can avoid to use the interactive procedure, but first we need the fingerprint of the Primary Key:
the output is as follows:
the interactive procedure can be skipped by using the --quick-add-key option along with the answers to the questions of the interactive wizard right at the end of the statement:
we generated a key with the following features:
- RSA
- sign capability
- valid for one year only
exit the sudoed shell:
Generating the Authentication subkey
Same way we created an encryption and a signing subkeys, we can create an authentication subkey - become the "foo" user:
now launch the interactive wizard to create the subkey as follows:
here is a snippet of the interactive procedure:
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(12) ECC (encrypt only)
(13) Existing key
(14) Existing key from card
Your selection? 11
Possible actions for a ECDSA/EdDSA key: Sign Authenticate
Current allowed actions: Sign
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? a
Possible actions for a ECDSA/EdDSA key: Sign Authenticate
Current allowed actions: Sign Authenticate
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? q
Please select which elliptic curve you want:
(1) Curve 25519
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(9) secp256k1
Your selection? 3
Please specify how long the key should be valid.
0 = key does not expire
= key expires in n days
w = key expires in n weeks
m = key expires in n months
y = key expires in n years
Key is valid for? (0) 1y
Key expires at Fri Jul 21 10:03:00 2023 UTC
Is this correct? (y/N) y
Really create? (y/N) y
as you see we created a key with the following features:
- "(11) ECC (set your own capabilities)"
- added the "authenticate" capability "(A)"
- using elliptic curve NIST P-256 "(3) NIST P-256"
- valid for one year only (1y)
the last part of the output of the generation wizard lists the available keys, with the new one among them (nistp256/F9C6C45EB9650672):
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2023-07-21 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
you can see, now foo user three private subkeys:
- nistp256/F43E205F0A259AC7 for encryption (usage: E)
- nistp256/4401D4D107578972 for signing (usage: S)
- nistp256/F9C6C45EB9650672 authentication (usage: SA)
type "save" to store the new key into the keyring and exit from the GPG interactive command prompt back to the shell:
gpg> save
Generating The Revocation Certificate
The aim of a revocation certificate is providing a way to invalidate the key if it gets stolen.Despite gpg automatically generates the revocation certificate when generating the Primary Key (as by the best practice), it is wise to know how to generate other revocation certificates if necessary:
If you are not working as the "foo" user, switch to it using sudo:
now generate the revocation certificate as follows:
the output is:
sec nistp256/7B221C7A2ACA4DE9 2022-07-21 Foo User (Foo User Primary Key) <foo@carcano.local>
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? 1
Enter an optional description; end it with an empty line:
>
Reason for revocation: Key has been compromised
(No description given)
Is this okay? (y/N) y
Revocation certificate created.
Operating Secret Keys
Now that every user of the lab has some GPG keys, we can see how to operate on keys. The very most of the following examples requires to be run as "foo" user, so we switch to the "foo" user right now:
Listing The Available Keys
We can list the secret (private) keys as follows:
the output is:
/home/foo/.gnupg/pubring.kbx
----------------------------
sec nistp256/7B221C7A2ACA4DE9 2022-07-21 [SC]
Key fingerprint = 56AE 9AFF A54F A111 B08C F0C4 7B22 1C7A 2ACA 4DE9
uid [ultimate] Foo User (Foo User Primary Key) <foo@carcano.local>
ssb nistp256/F43E205F0A259AC7 2022-07-21 [E] [expires: 2023-07-21]
ssb nistp256/4401D4D107578972 2022-07-21 [S] [expires: 2023-07-21]
ssb nistp256/F9C6C45EB9650672 2022-07-21 [SA] [expires: 2023-07-21]
The first column specifies the key type:
- sec: SECret key
- ssb: Secret SuBkey
so the output shows:
- a private key (the line that starts by "sec")
- some subkeys (the lines that start by "ssb")
- the uid of the secret key (the line that starts by "uid")
Adding Additional Uid To A Primary Key
You may want to add one or more additional uids to a Primary Key, but first you must enter the interactive procedure to modify the Primary Key.
In this example we are modifying foo@carcano.local Primary Key:
just type adduid to enter the interactive wizard to add a new uid to the Primary key:
here is a snippet of the wizard:
Real name: Thud User
Email address: thud@carcano.local
Comment: Thud User Primary Key
You selected this USER-ID:
"Thud User (Thud User Primary Key)<thud@carcano.local>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
the output is as follows:
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2024-07-25 usage: SA
[ultimate] (1) Foo User (Foo User Primary Key) <foo@carcano.local>
[ unknown] (2). Thud User (Thud User Primary Key)<thud@carcano.local>
don't bother for the "[ unknown ]" trust on the left of Thud's uid: it's just a matter of reloading.
Remember to save the changes before getting back to the shell:
by now Thud's key is shown as "trust ultimate" when you run the "gpg --list-secret-keys" statement.
Selecting A Specific Uid
In order to run commands for a specific uid, you must first select it: just launch the interactive edit of the key (in this example the Primary Key is foo@carcano.local):
the output is as follows:
Secret key is available.
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2024-07-25 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
[ultimate] (2) Thud User (Thud User Primary Key)<thud@carcano.local>
now select (by index) the uid you are interested to run commands onto.
For example, to select Thud's uid:
now the output is:
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2024-07-25 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
[ultimate] (2)* Thud User (Thud User Primary Key)<thud@carcano.local>
note that now Thud's uid is marked with a "star" character - this means that the uid has been selected, and that any following command will be related to this uid. Since we are only learning how to select a uid, we do not run any other command.
Type exit to get back to the shell:
Deleting A Specific Uid
You may want to delete a specific uid from a Primary Key, but first you must enter the interactive procedure to modify the Primary Key.
In this example we are modifying foo@carcano.local Primary Key:
the output is as follows:
Secret key is available.
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2024-07-25 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
[ultimate] (2) Thud User (Thud User Primary Key)<thud@carcano.local>
now refer to "Selecting A Specific Uid" to select the uid you want to delete: in this example, pick the key number 2 to delete Thud's uid.
type "deluid" to enter the interactive wizard to delete the subkey:
the wizard asks you to confirm:
Really remove this user ID? (y/N) y
remember to save the changes before getting back to the shell:
Selecting SubKeys
In order to run commands for a specific subkey, you must first select it: just launch the interactive edit of the key (in this example the Primary Key is foo@carcano.local):
the output is as follows:
Secret key is available.
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2023-07-21 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
now select (by index) the subkey (ssb) you are interested to run commands onto.
For example, to select the last subkey:
now the output is:
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb* nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2023-07-21 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
note that now the last subkey is marked with a "star" character - this means that the subkey has been selected, and that any following command will be related to this subkey. Since we are only learning how to select a subkey, we do not run any other command.
Type exit to get back to the shell:
Extending The Expiration Of A SubKey
You may want to extend the expire time of a subkey of a Primary Key, but first you must enter the interactive procedure to modify the Primary Key.
In this example we are modifying foo@carcano.local Primary Key:
the output is as follows:
Secret key is available.
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2023-07-21 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
now refer to "Selecting Subkeys" to select the subkey you want to extend the validity - in this example, pick the key number 2 (nistp256/4401D4D107578972)
type "expire" to enter the interactive wizard to setup a new expiration for the subkey:
this is a snippet of the interactive procedure:
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) 2y
Key expires at Thu 25 Jul 2024 10:21:58 AM UTC
Is this correct? (y/N) y
remember to save the changes before getting back to the shell:
Deleting A SubKey
You may want to delete a specific subkey of a Primary Key, but first you must enter the interactive procedure to modify the Primary Key.
In this example we are modifying foo@carcano.local Primary Key:
the output is as follows:
Secret key is available.
sec nistp256/7B221C7A2ACA4DE9
created: 2022-07-21 expires: never usage: SC
trust: ultimate validity: ultimate
ssb nistp256/F43E205F0A259AC7
created: 2022-07-21 expires: 2023-07-21 usage: E
ssb nistp256/4401D4D107578972
created: 2022-07-21 expires: 2023-07-21 usage: S
ssb nistp256/F9C6C45EB9650672
created: 2022-07-21 expires: 2023-07-21 usage: SA
[ultimate] (1). Foo User (Foo User Primary Key) <foo@carcano.local>
now refer to "Selecting Subkeys" to select the key you want to delete - in this example, pick the key number 3 (nistp256/F9C6C45EB9650672)
type "delkey" to enter the interactive wizard to delete the subkey:
the wizard asks you to confirm:
Do you really want to delete this key? (y/N) y
remember to save the changes before getting back to the shell:
Exporting The Secret Keys
You must of course take backups of your keys. Since in this lab we created most of the keys for the "foo" user, if you are not working as this user, switch to it using sudo as follows:
since we switched to the "foo" user using sudo, we must always specify the --pinentry-mode loopback command option.
GnuPG provides two different ways of exporting secret keys, as described below.
Exporting A Specific Secret Key Along With Every SubKey
This can be achieved by supplying the --export-secret-keys command switch as follows:
Exporting only SubKeys Of A Specific Key
Since subkeys are (only a little bit) less sensitive of the Primary Key, you may want to backup only the private keys of the subkeys of a specific Primary Key, and securely store them into another USB stick to put in a secure storage quicker to reach than the one where you put the USB stick with the Primary Key.
This approach shortens the time to restore them from a backup when needed, since it is quite rare that you need to restore the Primary Key - actually you'll need it only when signing other keys and a very few other cases.
This time you must specify the --export-secret-subkeys command switch as by the example below:
Securely Working With The Primary Key
The Primary Key is the most sensitive key, since it is the one that holds your uids and so that is tightly bound to your own reputation. For this reason you must carefully handle it, and always be able to access it.
The best practice is to:
- generate a revocation certificate right after creating the Primary Key
- export the key to a secure offline device, such as an USB stick (best would be an HSM) and store it on a secure place such as a bank safety box
Store The Primary Key And The Revocation Certificate Offline
Let's save the Primary Key and revocation certificate offline - for the sake of simplicity, in this tutorial we see how to export it on an USB stick.
Mount the device you want to store the Primary Keys - in this example the USB stick is /dev/sde:
switch to the user you want export the Primary Key - in this lab we are using the "foo" user:
let's now export the secret keys:
remember that after switching user with sudo it is mandatory to specify the --pinentry-mode loopback command option
create the revocation certificate of the Primary Key if you didn't already, then copy it too to the USB stick:
exit the sudoed shell:
unmount the USB stick:
switch again to the "foo" user:
we are almost ready to shred the Primary Key: as every private key, it is stored beneath ~/.gnupg/private-keys-v1.d, but we need to know the filename first.
This can be easily achieved by listing the secret keys showing the key grip as follows:
output is:
sec nistp256/7B221C7A2ACA4DE9 2022-07-21 [SC]
Key fingerprint = 56AE 9AFF A54F A111 B08C F0C4 7B22 1C7A 2ACA 4DE9
Keygrip = 8F0E9847884EF3ABCFFD1E0CBEE2F831A1033357
uid [ultimate] Foo User (Foo User Primary Key) <foo@carcano.local>
ssb nistp256/F43E205F0A259AC7 2022-07-21 [E] [expires: 2023-07-21]
Keygrip = D496BB6AC61001B12FAC4813B4D61E7429E7B9F9
ssb nistp256/4401D4D107578972 2022-07-21 [S] [expires: 2023-07-21]
Keygrip = 83A6E2398552A36636A1F804F2CF9DF165A7355C
ssb nistp256/F9C6C45EB9650672 2022-07-21 [SA] [expires: 2023-07-21]
Keygrip = 0A917D86F6744C226BE445AFDED54E681B80096E
the key grip of the Primary Key is 8F0E9847884EF3ABCFFD1E0CBEE2F831A1033357.
Let's shred and remove the file containing the key from the disk:
let's check the outcome:
the output is:
note the pound sign (#) right after the sec word: this means that the secret key is missing, that is exactly what we wanted to achieve.
Importing The Primary Key
Some tasks such as extending the subkeys, generate new ones, or signing other keys require the secret key of the Primary Key to be available: this means that sometime we need to reload the private key from the offline backup.
Mount the device containing the backup of the Primary Keys - in this example it is /dev/sde:
switch to the user you want import the Primary Key - in this lab we use the "foo" user:
now just type:
the output is:
exit the sudoed shell:
unmount the USB stick:
Generating A New Primary Key
It is certainly worth the effort to spend some words also on the steps to be followed if you generate a new Primary Key to supersede the old one: in order to have this new key to become automatically trusted by each one who have already set your old key as trusted, you must sign the new Primary Key with the old Primary key indeed.
This can be done as follows:
for more information on this topic, read "The Web Of Trust" later on.
If it isn't already, set the old key as trusted:
by doing so, every key you signed with the old key is evaluated as trusted.
Lastly, for your own convenience, modify the default key into ~/.gnupg/gpg.conf file, so to refer commands to this new key by default:
Operating Public Keys
Exporting The Public Key(s)
Sometimes it is necessary to export a public key - for example to share a key without using a keyserver.
In this example we are going to export fred's public key, so let's switch to "fred" user:
Now we export the public key of fred's specific Primary Key along with the ones of its subkeys:
Importing The Public Key(s)
We can of course import one or more public keys from a file.
In this example we import into foo's public keyring the fred's public keys we previously exported:
as you see we did it within a single statement running the command using sudo.
Listing The Available Keys
Switch to the user you want to list the keys - in this example we are switching to the "foo" user:
now list the available public keys:
the output is as follows:
the first column specifies the key type:
- pub: PUBlic key
- sub: public SUBkey
Showing Fingerprints And Key-ID
When running some statements - such as when sending keys to keyserver, it is necessary to supply the fingerprint of the key. You can show fingerprints by adding the command switch as follows:
the output is as follows:
the last 20 characters of the fingerprint are the key id.
Deleting A Public Key
Switch to the user you want to delete the key - in this example we are switching to the "foo" user:
then list the available public keys as follows:
the output is as follows:
/home/foo/.gnupg/pubring.kbx
----------------------------
pub nistp256/7B221C7A2ACA4DE9 2022-07-21 [SC]
Key fingerprint = 56AE 9AFF A54F A111 B08C F0C4 7B22 1C7A 2ACA 4DE9
uid [ultimate] Foo User (Foo User Primary Key) <foo@carcano.local>
sub nistp256/F43E205F0A259AC7 2022-07-21 [E] [expires: 2023-07-21]
sub nistp256/4401D4D107578972 2022-07-21 [S] [expires: 2023-07-21]
sub nistp256/F9C6C45EB9650672 2022-07-21 [SA] [expires: 2023-07-21]
pub rsa2048/AB3E2E9F213CC658 2022-07-25 [SC]
Key fingerprint = 0559 862A C951 CFB8 317C 8A7B AB3E 2E9F 213C C658
uid [ unknown] Fred User (Fred User Primary Key) <fred@carcano.local>
we can now delete the public key by using the --delete-key command switch.
For example, to delete fred's public key:
this is a snippet of the wizard:
pub rsa2048/AB3E2E9F213CC658 2022-07-25 Fred User (Fred User Primary Key) <fred@carcano.local>
Delete this key from the keyring? (y/N) y
Delivering The Public Key To GPG KeyServers
In real life you don't need to export public keys to files to exchange them to other parties: you just have to send your public keys to keyservers. These are public available servers that store every GPG public key the GPG community delivers to them. This means that GPG users can easily retrieve the public key of other parties simply asking them to these servers.
It is really easy to send public keys to the key servers - just mind that you must supply the key-id, so first you have to list the keys with the fingerprints so to guess the key id:
the output is as follows:
the last 20 characters of the fingerprint are the key id.
Now you can run the actual statement to send the key to the server:
you can of course override the default keyserver where to send the key by supplying the --keyserver option followed by the FQDN of the keyserver you want to use - for example "--keyserver pgp.mit.edu".
Fetching Public Keys From GPG Keyservers
We can of course fetch the GPG public keys of other entities (either humans or devices) that have been submitted to Key Servers. By the way, if you just need to look up keys, you may find it convenient to use the web ui provided by https://keys.openpgp.org/.
For example, if "fred" user delivered its public keys to the Key Servers, we would be able to retrieve it as follows:
you can of course override the default keyserver where to send the key by supplying the --keyserver option followed by the FQDN of the keyserver you want to use - for example "--keyserver pgp.mit.edu".
The Web Of Trust
This is how OpenPGP trusts public keys: you can find all the details at http://www.gnupg.org/gph/en/manual.html#AEN385, but by default the rule is that a key is considered validated if it meets both of the following two conditions:
- It is signed by enough valid keys, meaning one of the following:
- You have signed it personally
- It has been signed by one fully trusted key
- It has been signed by three marginally trusted keys
- The path of signed keys leading from it back to your own key is five steps or shorter
As for the trust levels:
this is the trust level of newly imported keys – it is the default.
this is a way to mark a key as “to be reviewed” to assign the right level of trust later on
this kind of trust is typically for people or entities you guess that behave right, but you do not directly know. A key marginally trusted is automatically shifted to trusted only if it signed by the key of at least three entities whom keys have been trusted by you
you are sure that the owner of these keys carefully signs the keys of other entities. Be wary that this means that any other key that gets signed by his key get automatically trusted
this is the highest level – you blindly trust who uses this key – you can consider this trust level suitable only for your own keys
it provides a way to never trust an entity, even if it has been (or will be) trusted by other entities you trust. This actually blocks the web of trust on this key
Since the first requisite of the web of trust are signed keys, to see this in action we need some public keys to sign. In a real world scenario, we'd use key servers to receive and send public keys, but since we are in a lab, we rely on exports to files.
Let's begin by exporting the public keys of every user.
and importing the keys of the users we want to trust: in order to avoid trivial examples, in this lab we are going to trust the keys of bar, baz and qux users as "Marginally Trusted": this way, after having each of them signing fred's key we'll see the web of trust in action, automatically guessing the trust level of the fred's key as "Fully Trusted".
Trusting Someone Else's Key
In our lab we are working as "foo" user, so let's switch to it:
we start by importing the keys of "bar", "baz" and "qux" users:
now let's trust as "marginal" bar's key:
the output of the interactive menu is as follows:
pub rsa2048/1A44B621829F5714
created: 2022-07-21 expires: never usage: SC
trust: unknown validity: full
sub rsa2048/60C80A0AD439C4A4
created: 2022-07-21 expired: 2022-07-22 usage: E
sub rsa2048/3CF0DB6D5B7B5331
created: 2022-07-21 expires: 2023-07-21 usage: S
[ full ] (1). Bar User (Bar User Primary Key) <bar@carcano.local>
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? 3
remember to save before exiting the GPG interactive menu:
gpg> save
let's repeat the step for baz's key (remember to save before exiting the GPG interactive menu!):
and for qux's key (remember to save before exiting the GPG interactive menu!):
exit the sudoed shell to the previous user:
Signing A Key
In our mocked up scenario, fred's key will be signed by both "bar", "baz" and "qux" users.
Let's start from "bar": we must add fred's public key to bar's public keyring:
of course, in a real world scenario bar user would retrieve fred's key from keyservers.
Now let's make bar user to sign fred's key (remember to save before exiting the GPG interactive menu!):
Mind that if the private key of the user (bar in this case) has been exported and removed from the keyring, you'll get the following error:
gpg: signing failed: No secret key
in this case you have to re-import bar's private key into bar's private GPG keyring first.
Type "save" to save the key to the keyring and exit the GPG interactive command prompt:
gpg> save
As for now the generated signature is only in bar's keyring: bar must now export fred's public key, so that other users can see his signature:
of course in a real world bar user would send fred's public key to key servers.
Now it's baz's turn - let's import the updated fred's key:
then baz signs fred's key (remember to save before exiting the GPG interactive menu!):
and eventually baz exports a new version of fred's key with the signature he added:
Finally it has come qux's turn: import fred's public key into his public keyring:
then qux signs fred's key (remember to save before exiting the GPG interactive menu!):
and eventually he exports it:
Listing Signatures on a Key
In our lab we work as "foo" user, so let's switch to it:
let's import (or of course retrieve from key server) the key we want to check the signatures:
now let's list the signatures:
the output is as follows:
pub rsa2048/AB3E2E9F213CC658 2022-07-25 [SC]
Key fingerprint = 0559 862A C951 CFB8 317C 8A7B AB3E 2E9F 213C C658
uid [ full ] Fred User (Fred User Primary Key) <fred@carcano.local>
sig 3 AB3E2E9F213CC658 2022-07-25 Fred User (Fred User Primary Key) <fred@carcano.local>
sig 1A44B621829F5714 2022-07-25 Bar User (Bar User Primary Key) <bar@carcano.local>
sig 7404BF5387181611 2022-07-25 Baz User (Baz User Primary Key) <baz@carcano.local>
sig 1317D81E1CE2B6B3 2022-07-25 Qux User (Qux User Primary Key) <qux@carcano.local>
Automatic Assignment Of Trust Level
As previously told, GPG is able to automatically guess trust level for public keys.
Let's have a look to the available public keys:
the output is as follows:
/home/foo/.gnupg/pubring.kbx
----------------------------
pub nistp256/7B221C7A2ACA4DE9 2022-07-21 [SC]
Key fingerprint = 56AE 9AFF A54F A111 B08C F0C4 7B22 1C7A 2ACA 4DE9
uid [ultimate] Foo User (Foo User Primary Key) <foo@carcano.local>
sub nistp256/F43E205F0A259AC7 2022-07-21 [E] [expires: 2023-07-21]
sub nistp256/4401D4D107578972 2022-07-21 [S] [expires: 2023-07-21]
sub nistp256/F9C6C45EB9650672 2022-07-21 [SA] [expires: 2023-07-21]
pub rsa2048/AB3E2E9F213CC658 2022-07-25 [SC]
Key fingerprint = 0559 862A C951 CFB8 317C 8A7B AB3E 2E9F 213C C658
uid [ unknown] Fred User (Fred User Primary Key) <fred@carcano.local>
pub rsa2048/1A44B621829F5714 2022-07-21 [SC]
Key fingerprint = 1414 EDCC 5BBB C879 5662 4F38 1A44 B621 829F 5714
uid [ unknown] Bar User (Bar User Primary Key) <bar@carcano.local>
sub rsa2048/3CF0DB6D5B7B5331 2022-07-21 [S] [expires: 2023-07-21]
pub rsa2048/7404BF5387181611 2022-07-21 [SC]
Key fingerprint = 42C1 515B D21D 96B0 6479 D914 7404 BF53 8718 1611
uid [ unknown] Baz User (Baz User Primary Key) <baz@carcano.local>
sub rsa2048/DE8B6BB533181C7E 2022-07-25 [S] [expires: 2023-07-25]
pub rsa2048/1317D81E1CE2B6B3 2022-07-25 [SC]
Key fingerprint = 4086 6F42 0748 D33D 438D 3600 1317 D81E 1CE2 B6B3
uid [ unknown] Qux User (Qux User Primary Key) <qux@carcano.local>
as you see, despite we previously set "Marginal" trust level to "bar", "baz" and "qux" user's keys, they are all listed with "unknown trust".
Let's check the contents of the trustdb:
the output is:
The meaning of the number in the second column is as follows:
I don't know or won't say
I do NOT trust
I trust marginally
I trust fully
I trust ultimately
so the "4" in the second column means "Marginal Trust '', which is exactly what we expected.
The problem is that we are still missing another requirement for the web of trust, ... besides being trusted, bar, baz and qux keys must also be signed by the user, or they will not be considered "safe".
So let's sign bar's key:
then baz's key:
and finally qux's key:
let's check the trust level of the keys now:
the output is as follows:
/home/foo/.gnupg/pubring.kbx
----------------------------
pub nistp256/7B221C7A2ACA4DE9 2022-07-21 [SC]
Key fingerprint = 56AE 9AFF A54F A111 B08C F0C4 7B22 1C7A 2ACA 4DE9
uid [ultimate] Foo User (Foo User Primary Key) <foo@carcano.local>
sub nistp256/F43E205F0A259AC7 2022-07-21 [E] [expires: 2023-07-21]
sub nistp256/4401D4D107578972 2022-07-21 [S] [expires: 2023-07-21]
sub nistp256/F9C6C45EB9650672 2022-07-21 [SA] [expires: 2023-07-21]
pub rsa2048/AB3E2E9F213CC658 2022-07-25 [SC]
Key fingerprint = 0559 862A C951 CFB8 317C 8A7B AB3E 2E9F 213C C658
uid [ full ] Fred User (Fred User Primary Key) <fred@carcano.local>
pub rsa2048/7404BF5387181611 2022-07-21 [SC]
Key fingerprint = 42C1 515B D21D 96B0 6479 D914 7404 BF53 8718 1611
uid [ full ] Baz User (Baz User Primary Key) <baz@carcano.local>
sub rsa2048/DE8B6BB533181C7E 2022-07-25 [S] [expires: 2023-07-25]
pub rsa2048/1317D81E1CE2B6B3 2022-07-25 [SC]
Key fingerprint = 4086 6F42 0748 D33D 438D 3600 1317 D81E 1CE2 B6B3
uid [ full ] Qux User (Qux User Primary Key) <qux@carcano.local>
pub rsa2048/1A44B621829F5714 2022-07-21 [SC]
Key fingerprint = 1414 EDCC 5BBB C879 5662 4F38 1A44 B621 829F 5714
uid [ full ] Bar User (Bar User Primary Key) <bar@carcano.local>
sub rsa2048/3CF0DB6D5B7B5331 2022-07-21 [S] [expires: 2023-07-21]
so signing the key
- dynamically raised the "guessed" trust level from "Marginal" to full for bar, baz an qux
- automatically guessed the "Full" trust level for fred's key, since it is signed by three or more marginally trusted users
Operating With GPG
Now that we have a lab with all the necessary users and keys to play with, we can begin doing something.
Signing documents
The most straightforward GPG application is signing - there are actually three ways to sign a document using GPG:
- GPG signing
- GPG cleartext signing
- GPG Detached signing
The first two methods, since they embed the signature, actually generate a file with both the contents of the document and its signature. The last one instead leaves the document unchanged and generates a signature file: for this reason the last method is suitable for the purpose of signing not only documents, but also software packages too.
GPG Signing
This is the first method - in this example we are working as the "baz" user:
we just have to provide the --sign command switch as follows:
let's inspect the kind of contents of the generated file (README.gpg)
as expected, the outcome is a data document that embeds the GPG signature.
exit the sudoed shell and become the "foo" user:
we can now verify the signature of the signed document:
the outcome is as follows:
we can then decipher the GPG signed document and extract the original one by providing both the --decrypt and --output command line options:
the output is as follows:
as you see, before extracting the original document gpg anyway checks the signature.
Exit the sudoed shell.
Cleartext Siging
This is the second method - in this example we are working as the "baz" user:
this time we just provide the --clearsign command switch as follows:
let's see the contents of the "/tmp/README.asc" file:
the output is just a snippet of the first 8 lines and last 7 lines of the file:
as you see the file contains
- a cleartext header ("-----BEGIN PGP SIGNED MESSAGE----- ...") that claims that the document is PGP signed
- a cleartext footer, ("-----BEGIN PGP SIGNATURE-----","-----END PGP SIGNATURE-----") with the actual signature
so, as suggested by the name of the command line switch we provided, this time we generated a document with a cleartext signature.
Exit the sudoed shell and become the "foo" user:
same way as we already did, let's verify the signed document:
the outcome is as follows:
we can then decipher the GPG signed document and extract the original one by providing both the --decrypt and --output command line:
the output is as follows:
as you see, before extracting the original document gpg anyway checks the signature.
Exit the sudoed shell.
Detached Signature
This is the third and last method: since it is broadly used to sign software, I describe it in the next paragraph "Signing Software".
Signing Software
GPG is broadly used to generate signature files of software packages.
In this example we are working as the "baz" user:
Verifying A Detached Signature
To provide a real life example, we download Gitea - a GIT implementation with a very nice WebUI - along with the file containing the GPG signature of the software package.
The very first thing to do is gather some information on the key that has been used to generate the signature file:
the output is as follows:
# off=0 ctb=89 tag=2 hlen=3 plen=563
:signature packet: algo 1, keyid 5FC346329753F4B0
version 4, created 1657658277, md5len 0, sigclass 0x00
digest algo 8, begin of digest 81 ad
hashed subpkt 33 len 21 (issuer fpr v4 CC64B1DB67ABBEECAB24B6455FC346329753F4B0)
hashed subpkt 2 len 4 (sig created 2022-07-12)
subpkt 16 len 8 (issuer key ID 5FC346329753F4B0)
data: [4096 bits]
now that we know the fingerprint of the key (CC64B1DB67ABBEECAB24B6455FC346329753F4B0), we retrieve the public key used to generate the signature from the keyservers:
since we don't have other keys that let us guess a trust for this key (see "The Web Of Trust" for more information on this topic), we must trust it manually.
This actually involves to trust the key - (set it as fully trusted):
and to sign the key:
we are finally ready to check if the downloaded file matches its signature:
the output is as follows:
gpg: Signature made Tue 12 Jul 2022 08:37:57 PM UTC
gpg: using RSA key CC64B1DB67ABBEECAB24B6455FC346329753F4B0
gpg: Good signature from "Teabot <teabot@gitea.io>" [full]
Primary key fingerprint: 7C9E 6815 2594 6888 62D6 2AF6 2D9A E806 EC15 92E2
Subkey fingerprint: CC64 B1DB 67AB BEEC AB24 B645 5FC3 4632 9753 F4B0
For the sake of completeness, in the event of a mismatched signature, the output would have been:
gpg: Signature made Tue 12 Jul 2022 08:37:57 PM UTC
gpg: using RSA key CC64B1DB67ABBEECAB24B6455FC346329753F4B0
gpg: BAD signature from "Teabot <teabot@gitea.io>" [full]
Generating A Detached Signature
In this example we are still working as the "baz" user. If you are not that user, switch to it:
Let's copy the "echo" command and pretend that we built a new amazing fancy software:
now we want to GPG sign our fancy software binary:
let's see the contents of the signature file:
the output is as follows:
now let's pretend that the "foo" user want to install myfancysoftware: just exit the sudoed shell and become the "foo" user:
since foo user is cautious, he checks the signature first:
the output is as follows:
gpg: Signature made Wed 27 Jul 2022 01:49:44 PM UTC
gpg: using RSA key 76006E9AEFC950E44EC1D539DE8B6BB533181C7E
gpg: Good signature from "Baz User (Baz User Primary Key) <baz@carcano.local>" [full]
Primary key fingerprint: 42C1 515B D21D 96B0 6479 D914 7404 BF53 8718 1611
Subkey fingerprint: 7600 6E9A EFC9 50E4 4EC1 D539 DE8B 6BB5 3318 1C7E
So the signature is good.
Exit the sudoed shell:
GPG Signed RPM Packages
RPM is a sophisticated archive format that does not only pack a set of files and directories within a package file, but can also run scripts and evaluate conditionals. It is made by putting a header structure on top of a CPIO archive. The package itself has four sections: the second one contains the GPG signature to verify the integrity of the package.
We can query rpm to get information about the signature of the already installed RPM package:
the output is as follows:
ufdio: 1 reads, 17154 total bytes in 0.000005 secs
D: loading keyring from pubkeys in /var/lib/rpm/pubkeys/*.key
D: couldn't find any keys in /var/lib/rpm/pubkeys/*.key
D: loading keyring from rpmdb
D: serialize failed, using private dbenv
D: opening db environment /var/lib/rpm cdb:private:0x401
D: opening db index /var/lib/rpm/Packages 0x400 mode=0x0
D: locked db index /var/lib/rpm/Packages
D: opening db index /var/lib/rpm/Name 0x400 mode=0x0
D: read h# 395
Header SHA1 digest: OK
D: added key gpg-pubkey-6d745a60-60287f36 to keyring
D: read h# 397
Header SHA1 digest: OK
D: added key gpg-pubkey-2f86d6a1-5cf7cefb to keyring
D: read h# 514
Header SHA1 digest: OK
D: added key gpg-pubkey-442df0f8-608c8351 to keyring
D: Using legacy gpg-pubkey(s) from rpmdb
D: read h# 16
Header V4 RSA/SHA256 Signature, key ID 6d745a60: OK
Header SHA256 digest: OK
Header SHA1 digest: OK
(none)D: closed db index /var/lib/rpm/Packages
D: closed db index /var/lib/rpm/Name
D: closed db environment /var/lib/rpm
D: Exit status: 0
RPM verifies the signature of the packages using its own GPG keyring.
You can add GPG keys to RPM keyrings by saving the key file into the /etc/pki/rpm-gpg directory.
For example, we can download the GPG file used to sign PostgreSQL14 RPM packages as follows:
once saved, the file must also be imported as follows:
we can list the GPG keys available in the RPM keyring as follows:
if everything worked properly, the new key must be among the list:
gpg(Release Engineering <infrastructure@rockylinux.org>)
gpg(Fedora EPEL (8) <epel@fedoraproject.org>)
gpg(PostgreSQL RPM Building Project <pgsql-pkg-yum@postgresql.org>)
Having the key imported in the RPM gpg keyring lets us enable GPG check before installing the downloaded package.
This can be achieved by adding the gpgcheck and gpgkey directives within each repository stanza in the specific ".repo" files into /etc/yum.repos.d directory.
This is an example snippet of how to set these directives:
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-PGDG
You can of course GPG sign your own RPM packages.
The signing feature for the rpm command line utility is provided by the rpm-sign RPM package - let's install it as follows:
in the following example we are working as "foo" user, so become the "foo" user:
configure the RPM macros of the user.
Please note that since we can have several keys within our GPG keyring, we must specify which GPG key must be used to sign RPMs: in this example we are configuring it to use "foo@carcano.local" GPG key:
although this is optional, we can even specify the GPG command we use to sign along with its options - here I can specify the "--pinentry-mode loopback" since I'm using Red Hat 8 / CentOS 8:
Now everything is properly set up to sign RPM packages.
Just to provide an example, the rpm command to sign the foo-0.1-1.el8.x86_64.rpm RPM packages is:
after typing the password to unlock the secret key for the signature, the RPM packages get signed.
Encrypting and decrypting
The other straightforward purpose of GPG is file encryption. When encrypting, OpenPGP:
- first compresses the data (if file size is enough to allow compression)
- then applies a symmetric algorithm with a random session key generated on the fly to encrypt the compressed data
The session key itself is then encrypted by asymmetric algorithms using the public key of each one of the specified recipients.
As an example, let's make a copy the /usr/share/doc/gnupg2/README file:
then let's encrypt the copy:
Since gpg does not remove the original file, it's up to you to properly shred it, for example:
let's become the "bar" user:
since bar was among the recipients, we must be able to decrypt the file:
the output is as follows:
gpg: encrypted with ECDH key, ID F43E205F0A259AC7
gpg: encrypted with 2048-bit RSA key, ID 65010FD4CE7E3E49, created 2022-07-26
"Bar User (Bar User Primary Key) <bar@carcano.local>"
Exit the sudoed shell:
Exploiting The Vim GPG plugin
Since we previously installed it, we can now exploit the vim GPG plugin.
Become the "foo" user:
Let's begin from editing the /tmp/README.asc GPG encrypted file we created a while ago:
As you can see the plugin seamlessly decrypts the file. You can modify it at wish: when you save it the plugin seamlessly re-encrypts it.
Now let pretend foo is a system administrator that works in the same team with bar: we can set to encrypt for both of them by default simply by adding both of the to the GPGDefaultRecipients list in the ~/.vimrc file:
" Set the default recipient list
let g:GPGDefaultRecipients=["foo@carcano.local", "bar@carcano.local"]
To try it, let's create a new file - please note that in order to have vim correctly guess we want to create a GPG encrypted file, the new file must be an ".asc" file.
So:
both foo and bar user are now listed in the recipients list as shown in the following snippet:
GPG: ----------------------------------------------------------------------
GPG: Please edit the list of recipients, one recipient per line.
GPG: Unknown recipients have a prepended "!".
GPG: Lines beginning with "GPG:" are removed automatically.
GPG: Data after recipients between and including "(" and ")" is ignored.
GPG: Closing this buffer commits changes.
GPG: ----------------------------------------------------------------------
Foo User (Foo User Primary Key) <foo@carcano.local> (ID: 0x7B221C7A2ACA4DE9 created at Wed 27 Jul 2022 07:19:11 AM UTC
Bar User (Bar User Primary Key) <bar@carcano.local> (ID: 0x1A44B621829F5714 created at Thu 21 Jul 2022 09:17:15 AM UTC
~
~
~
~
~
~
~
~
GPGRecipients_1
If you wish you can of course add other recipients to this list - you must of course already have imported their public key in the GPG public keyring before.
When done, simply type :q to exit from the GPG settings of this file and switch to the vim edit mode, then just work as you are usually doing with vim.
If you want to see the list of recipient, just switch to command mode and type:
If necessary, you can of course modify the list of recipients by switching to command mode by typing:
exit the sudoed shell:
Hidden Recipients
As we saw the encrypted file can contain sensitive information about the key or keys necessary to decrypt the file.
For example, let's become "qux" user:
and try to decrypt the file:
the outcome is the following message:
gpg: encrypted with ECDH key, ID F43E205F0A259AC7
gpg: encrypted with RSA key, ID 65010FD4CE7E3E49
gpg: decryption failed: No secret key
So, although qux user is not able to decrypt the file, he knows the ID of the keys that are necessary to decrypt it. This can pose serious security risks to the individuals that own those keys, if qux is evil.
For this reason, when dealing with very risky situations, it's better to specify the recipients as hidden:
Let's switch back to the "foo" user:
and encrypt the file again, but this time using the -R (capital letter) option to list the recipients as hidden:
now let's switch to the "qux" user again:
and try to decrypt the file again:
this time the output is:
gpg: selecting card failed: No such device
gpg: selecting card failed: No such device
gpg: encrypted with ECDH key, ID 0000000000000000
gpg: encrypted with RSA key, ID 0000000000000000
gpg: decryption failed: No secret key
So now we are not leaking any kind of information.
Just exit the sudoed shell:
Signing And Encrypting A document
If you wish, you can of course both encrypt and sign a document.
Switch to the "foo" user:
in order to both encrypt and sign, you must provide both the -e and --sign command line switches.
For example, to encrypt and sign the /usr/share/doc/gnupg2/README so that both foo and bar users can use it, type:
Footnotes
Here it ends this tutorial on GPG. We thoroughly saw how to generate and manage Primary Keys and subkeys, what are the best practices and how to use them to sign and encrypt documents. We also learned how GPG can automatically figure out the trust level to assign to public keys using the Web Of Trust.