- Security
- A
All the intricacies of GPG signatures
Hello everyone! In this series of articles, I would like to go over the subtleties of working with GPG, which in my opinion are not sufficiently covered on the internet. Today I will tell you about signatures. I would divide them into two types: file signatures and key signatures.
Hello everyone! In this series of articles, I would like to discuss the subtleties of working with GPG, which I believe are not sufficiently covered on the internet. Today, I will tell you about signatures (and we will touch a bit on the Web of Trust model). I would divide them into two types: file signatures and key signatures.
File Signatures
Let's start with an example:
Bob wants to send Alice a message (we're not concerned about whether it's in plaintext right now). Bob sends his message and its checksum over the communication channel. Alice receives the message, calculates the checksum, and if it matches the one Bob sent, the message is MOST LIKELY intact. But let’s see what happens if someone appears in the middle – Eva. She might intercept Bob’s message, modify it, then simply recalculate the checksum of the modified message and send it to Alice. Alice will check the message and won’t notice the tampering.
How do we fix this? The most common and easiest-to-understand signatures will help – file signatures. What are they for? These signatures perform two important tasks: they check the integrity of the file during transmission (something simple checksums already handle) and verify the authenticity of the sender. I suggest we take a closer look at the second purpose. With the help of a signature, we can verify that a file was indeed sent by the owner of the key and that it hasn’t been tampered with.
Let’s go through an example:
Let’s assume that Bob and Alice have each other’s GPG key sets. Bob, as before, sends messages to Alice, BUT instead of the checksum, he sends a signature of the message made with his private key. Alice checks the message's signature with Bob’s public key. In this case, Eva is powerless, because after modifying the message, the signature would become invalid, and to fake it, she would need Bob’s private key.
Ugh, dry theory, let’s move on to practice. Let’s create a new key, write a file, and sign it:
❯ gpg --full-gen-key
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Select key type: (1) RSA and RSA (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) (9) ECC (sign and encrypt) *default* (10) ECC (sign only) (14) Existing key from card
Your choice? 1
RSA key length can be between 1024 and 4096.
What key size do you need? (3072) 4096
Requested key size - 4096 bits
Choose key expiration. 0 = does not expire = key expiration in n days w = key expiration in n weeks m = key expiration in n months y = key expiration in n years
Key expiration? (0) 1y
Key valid until Tue 25 Aug 2026 22:36:06 EEST
Is this correct? (y/N) y
GnuPG will create a user ID for the key.
Your real name: David
Email address: [email protected]
Note: You have chosen the following user ID: "David "
Change (N)Name, (C)Comment, (E)Email; (O)Accept/(Q)Quit? o
A lot of random numbers are required. It is recommended that you
do other activities (typing, moving the mouse, accessing disks) during the generation process; this will give the random number generator more opportunities to gather sufficient entropy.
A lot of random numbers are required. It is recommended that you
do other activities (typing, moving the mouse, accessing disks) during the generation process; this will give the random number generator more opportunities to gather sufficient entropy.
gpg: created directory '/home/david/.gnupg/openpgp-revocs.d'
gpg: revocation certificate written to '/home/david/.gnupg/openpgp-revocs.d/FB3A670DF78954124EB2DF68EF1515E8722BAB9D.rev'.
public and secret keys created and signed.
pub rsa4096 2025-08-25 [SC] [ valid until: 2026-08-25] FB3A670DF78954124EB2DF68EF1515E8722BAB9D
uid David sub rsa4096 2025-08-25 [E] [ valid until: 2026-08-25]
❯ echo "test hi" > msg
❯ gpg --sign -u [email protected] msg
❯ cat msg.gpg
�X���
��r+���bmsgh��0test hi
�3
# and many more bytes
As we can see, gpg
created a file with some kind of garbage, this is our compressed message + signature.
THIS FILE CONTAINS AN UNENCRYPTED MESSAGE, JUST COMPRESSED
Let's try the ASCII format:
❯ gpg --armor --sign -u [email protected] msg
❯ cat msg.asc
-----BEGIN PGP MESSAGE-----
owEBWAKn/ZANAwAKAe8VFehyK6udAawRYgNtc2dorL78dGVzdCBoaQqJAjMEAAEK
AB0WIQT7OmcN94lUEk6y32jvFRXociurnQUCaKy+/AAKCRDvFRXociurnX+oEAC4
5QpavKF6yNNOmPRLr4UQ4FfVciqfWeGRgq9FEtSmUkishzwjuNw9+UNG8aH7BlUg
# cut for easier reading
-----END PGP MESSAGE-----
We see the standard GPG ASCII format. However, this approach is not entirely correct, it's better to use --clear-sign
:
❯ gpg --clear-sign -u [email protected] msg
❯ ❯ cat msg.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
test hi
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEE+zpnDfeJVBJOst9o7xUV6HIrq50FAmiuCY0ACgkQ7xUV6HIr
q53MQhAAgFzLJsLLplWzeclgKPKf3oqZ9h/aN/E33ii1UDp7Bq1Hy6nxzlje+uqr
T5iRzOymMpdFYBi44/xkYNUj3xWaKkuvae2suNrjE5RP9tqRw1vDKGZtn/vP3T4p
# also cut
-----END PGP SIGNATURE-----
Thus, the original message is easier to read. Let's try extracting data from the previous formats and check them:
❯ gpg --verify msg.asc
gpg: Signature made Mon 25 Aug 2025 22:52:28 EEST
gpg: RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Good signature from "David " [ultimate]
❯ gpg -d msg.asc
test hi
gpg: Signature made Mon 25 Aug 2025 22:52:28 EEST
gpg: RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Good signature from "David " [ultimate]
As we can see, for gpg
it’s enough to simply run the decryption command: gpg -d
— but for clarity I showed how to just check integrity as well. Nice, but what if we’re publishing a program and aren’t sure everyone will bother verifying the signature? That’s where dettach-sign
comes in — a detached signature, meaning the signature is in a separate file:
❯ gpg --detach-sign -u [email protected] msg
❯ cat msg.sig
�3
,�TN��h��r+��h� ��r+�����T�k��n��P%;ەY�gW�W,fd_��݁��^.ٕ�t����
# shortened for readability
Let’s make it armored:
❯ gpg --armor --detach-sign -u [email protected] msg
❯ cat msg.asc
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE+zpnDfeJVBJOst9o7xUV6HIrq50FAmiuDdwACgkQ7xUV6HIr
q51wxQ/9EG2Asn6aQ87ST1wm3z2F7qLNxuUQ8m4y8dTxq/etUxLXaPwgij8Eae2G
# shortened for readability
BjUv9ctxbGYh9ctes6KVZVHINR16U3VmXTGdG5DSHUBcvCKb3VM=
=yBZq
-----END PGP SIGNATURE-----
We see a similar picture to what we had with --clear-sign
. Not surprising — you could say --clear-sign
simply stitches together an armored detach sign and the original file in a certain format. How to verify such a signature? There are two syntaxes: --verify file.sig
or --verify file.sig file
. The second one is used when the signature’s name doesn’t match the file name or is in another location. Let’s try:
❯ gpg --verify msg.asc
gpg: assuming signed data is in 'msg'
gpg: Signature made Wed 27 Aug 2025 20:24:35 EEST
gpg: RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Good signature from "David " [ultimate]
❯ gpg --verify msg.asc msg
gpg: Signature made Wed 27 Aug 2025 20:24:35 EEST
gpg: RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Good signature from "David " [ultimate]
Works identically. But what if the message is changed? Let’s check:
❯ cat msg
test hi
❯ echo ! >> msg
❯ cat msg
test hi
!
❯ gpg --verify msg.asc
gpg: assuming signed data is in 'msg'
gpg: Signature made Wed 27 Aug 2025 20:24:35 EEST
gpg: RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: BAD signature from "David " [ultimate]
Oh, as we can see GPG instantly noticed changes of even two bytes! Such a file should not be trusted.
That’s all well and good, but up to now we’ve been verifying our own signatures and had the private key. The point here is to verify someone else’s messages! I’ll delete the keys and export only the public one:
❯ gpg -o key.pub --export [email protected]
❯ gpg -o key --export-secret-keys [email protected]
❯ gpg --delete-secret-keys [email protected]
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
sec rsa4096/EF1515E8722BAB9D 2025-08-25 David Delete this key from the keyring? (y/N) y
This is a secret key! - still delete? (y/N) y
❯ gpg --delete-keys [email protected]
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
pub rsa4096/EF1515E8722BAB9D 2025-08-25 David Delete this key from the keyring? (y/N) y
❯ gpg --import key.pub
gpg: key EF1515E8722BAB9D: public key "David " imported
gpg: Total processed: 1
gpg: imported: 1
❯ gpg --verify msg.asc
gpg: assuming signed data is in 'msg'
gpg: Signature made Wed Aug 27 20:24:35 2025 EEST
gpg: using RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Good signature from "David " [unknown]
gpg: Warning: This key is not certified with a trusted signature!
gpg: There are no indications that the signature belongs to the owner.
Primary key fingerprint: FB3A 670D F789 5412 4EB2 DF68 EF15 15E8 722B AB9D
Oops! The signature is valid, but a warning appeared saying that the key is not certified with a trusted signature. Let's investigate!
Signature and trust in keys
As in the previous section, let's start with an example:
Last time, we assumed that Bob and Alice have each other's keys, but what if that's not the case? The simplest thing is for Bob to just give Alice his key. But what if Eve is in the middle again? She could generate her own key with the same identifier that she just received and send it to Alice. Then when Bob sends a message, Eve will check it with the intercepted key, change it if necessary, sign it, and send it to Alice with the new signature.
That means you can't blindly trust keys, so what should you do? The simplest solution recommended by the GNU project is to meet the person in person, check their passport details, take the email and key fingerprint, go home, download the key from the key servers using the fingerprint, check all the key data, sign the key, and send it to the owner... Wait! Sign the key? Yes! But before we understand what this means, let's remember HTTPSS. When we visit a website with encryption, it provides its certificate, signed by a certificate authority, and that certificate is also signed by someone, and so on, up to the root certificate authorities, whose certificates everyone trusts. This model is called hierarchical.
In GPG, a different model is used called the Web of Trust. The essence of it is that there are no root authorities; instead, it uses signatures from people you trust. There are 5 types of trust:
I don't know or won't respond
I don't trust
I trust to a limited extent
I fully trust
I absolutely trust
The first two tell GPG to ignore the signatures of these keys when checking trust. The third level is a 50/50 trust, meaning that to trust a key with such signatures, you need several of them (by default, 3). The fourth level is full trust, requiring just one such signature. Absolute trust is like level 4, but it only applies to your key... It would be the subject of another article, but we are better than that! So let's break down what absolute trust is.
Above I mentioned not the levels of trust in the identity of key owners (which would be stupid), but the LEVELS OF TRUST IN THE SIGNATURES OF THE IDENTITIES OF OTHER KEY OWNERS. It would be strange to trust the signature of another key if you don’t trust the identity of its owner.
Let’s look at an example: we have Tolya’s key, but we’re not sure if it’s really his key, so we look at the signatures — there are Anna, Maxim, and Kolya, whose signatures we trust to a limited extent. We look at the signatures on their keys, and so on, until we reach a signature that we trust completely.
You’ve probably noticed that GPG literally builds a weighted, directed graph (a tree, call it what you like) to verify the identity of a key.
That’s why you shouldn’t give absolute trust to someone else’s key. It can be compromised, revoked, etc., and you might not find out in time — and that’ll be sad. For most cases, assign limited trust to other people’s keys, so that if one is stolen, it won’t force you to trust the others. There’s another advantage to this approach — if one or two people decide to mess with the network, they simply won’t be able to do it.
So what should our friends Alice and Bob do:
They can find mutual friends (the theory of seven handshakes), check their keys, sign them, and ask them to sign Alice’s and Bob’s keys, then exchange signed keys. Done! Now no Eve can scare them!
Let’s fix our mistake:
gpg --gen-key
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Note: "gpg --full-generate-key" invokes a full-featured key creation dialog.
GnuPG needs to generate a user identifier for key identification.
Your full name: Alice
Email address: You have selected the following user identifier: "Alice"
Change (N)Name, (E)Email; (O)Accept/(Q)Quit? o
A lot of random numbers need to be gathered. It is advisable that you
perform other actions during the generation process (typing
on the keyboard, moving the mouse, accessing disks); this will provide the random number generator more opportunities to gather sufficient entropy.
A lot of random numbers need to be gathered. It is advisable that you
perform other actions during the generation process (typing
on the keyboard, moving the mouse, accessing disks); this will provide the random number generator more opportunities to gather sufficient entropy.
gpg: revocation certificate written to '/home/david/.gnupg/openpgp-revocs.d/85CDE7C586469C88071699079B9A4D44CEEF73B6.rev'.
public and secret keys have been created and signed.
pub ed25519 2025-08-28 [SC] [ expires: 2028-08-27] 85CDE7C586469C88071699079B9A4D44CEEF73B6
uid Alice
sub cv25519 2025-08-28 [E] [ expires: 2028-08-27]
❯ gpg --edit-key [email protected]
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
pub rsa4096/EF1515E8722BAB9D created: 2025-08-25 expires: 2026-08-25 usage: SC trust: unknown validity: unknown
sub rsa4096/0A5672421EFF6B2C created: 2025-08-25 expires: 2026-08-25 usage: E [ unknown ] (1). David gpg> sign -u Alice
pub rsa4096/EF1515E8722BAB9D created: 2025-08-25 expires: 2026-08-25 usage: SC trust: unknown validity: unknown Primary key fingerprint: FB3A 670D F789 5412 4EB2 DF68 EF15 15E8 722B AB9D David This key will expire on 2026-08-25.
Are you sure you want to sign this key
with your key "Alice" (9B9A4D44CEEF73B6)?
Really sign? (y/N) y
gpg> save
❯ gpg --verify msg.asc
gpg: Signature made Tue Aug 26 22:22:53 2025 EEST
gpg: using RSA key ID FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: checking trust database
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 trusted: 2 signed: 8 trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: depth: 1 trusted: 8 signed: 3 trust: 3-, 0q, 0n, 1m, 4f, 0u
gpg: depth: 2 trusted: 3 signed: 1 trust: 3-, 0q, 0n, 0m, 0f, 0u
gpg: next trust database check due 2026-06-08
gpg: Good signature from "David " [full]
Done! The error is gone. We signed with the absolute key and trust it.
Practice
Lastly, let's correctly download a signed file and verify it. I will take wofi-power-menu
:
❯ wget https://github.com/szaffarano/wofi-power-menu/releases/download/v0.3.1/wofi-power-menu-linux-x64
❯ wget https://github.com/szaffarano/wofi-power-menu/releases/download/v0.3.1/wofi-power-menu-linux-x64.asc
❯ gpg --recv-keys 42BE68F43D528467FC281E2E310FFE86A2E427BA
gpg: key 310FFE86A2E427BA: public key "Sebastián Zaffarano (Releases) " imported
gpg: Total processed: 1
gpg: imported: 1
❯ gpg -u [email protected] --lsign-key 42BE68F43D528467FC281E2E310FFE86A2E427BA
pub ed25519/310FFE86A2E427BA created: 2025-08-16 expires: never usage: SC trust: unknown validity: unknown
[ unknown ] (1). Sebastián Zaffarano (Releases) pub ed25519/310FFE86A2E427BA created: 2025-08-16 expires: never usage: SC trust: unknown validity: unknown Primary key fingerprint: 42BE 68F4 3D52 8467 FC28 1E2E 310F FE86 A2E4 27BA Sebastián Zaffarano (Releases) Are you sure you want to sign this key
with your key "David " (EF1515E8722BAB9D)?
The signature will be marked as non-exportable.
Really sign? (y/N) y
❯ gpg --verify wofi-power-menu-linux-x64.asc
gpg: assuming signed data in 'wofi-power-menu-linux-x64'
gpg: Signature made Sat 16 Aug 2025 13:49:30 EEST
gpg: using EDDSA key 42BE68F43D528467FC281E2E310FFE86A2E427BA
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 2 signed: 7 trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: depth: 1 valid: 7 signed: 3 trust: 2-, 0q, 0n, 1m, 4f, 0u
gpg: depth: 2 valid: 3 signed: 1 trust: 3-, 0q, 0n, 0m, 0f, 0u
gpg: next trustdb check due 2026-06-08
gpg: Good signature from "Sebastián Zaffarano (Releases) " [ultimate]
Done! Everything is intact! Note that before signing I checked all the data.
Conclusion
I have explained the correct use of one of GPG's features. I hope you will now correctly verify software signatures and not give unconditional trust to keys from a hypothetical GitHub. Wishing you all kindness and peace!
Write comment