PGP - Send Encrypted and Signed Message - gpg

Send a secret message over untrusted Internet. Encryption prevents anyone from reading your message. Signing protects your message from modification. Public keys allow you to establish trust without meeting physically.

This article shows how you can use PGP encryption with 'gpg' tool. We'll simulate two users to make it easy to practice. PGP is well known, highly secure standard for encryption.

Alice will send a signed, encrypted message to Tero. This is the most obvious, basic use of PGP.

Background

GNU privacy guard, 'gpg', is a popular way to encrypt and sign. You're likely already depending on it, as Linux kernel development uses uses PGP signatures.

This article directly uses 'gpg' on the command line. To encrypt your everyday messaging, additional tools are often used to automate this. For example, email applications can be made to automatically encrypt your messages.

This article requires knowledge of Linux command line. I've tested the commands on Debian 12 Bookworm. They likely work on many other Linuxes too. Probably they could be adapted to lesser operating systems, such as Windows.

Alice will send an encrypted message to Tero.

Alice needs Tero's public key to encrypt. Tero needs Alice's public key to verify Alice's singature.

Setting Up Trust

These inital key exchange and verification steps need to be done only once. They establish trust between parties.

Generate Tero's Keypair

Let's install required tools. We interested in 'gpg' encryption tool. The others, 'micro' text editor and 'killall' from psmisc are just helpful extras.

$ sudo apt-get update
$ sudo apt-get install gpg micro psmisc

Let's create a keypair.

$ gpg --gen-key

I gave my full name. "Tero Karvinen DEMO KEY" and my email address "tero@example.com.invalid". As I'm actually using PGP, I added the text to make it clear this is not my real key.

I did not protect this key with a password. I simply gave empty password (twice) and confirmed that I don't want a password for my key (twice).

I now have a keypair. I have picked only interesting lines of the output.

$ gpg --fingerprint
pub   rsa3072 2023-11-17 [SC] [expires: 2025-11-16]
      B624 CDED 2430 252D 298D  7EC4 A8D8 1658 00B3 84A3
uid           [ultimate] Tero Karvinen DEMO KEY <tero@example.com.invalid>
sub   rsa3072 2023-11-17 [E] [expires: 2025-11-16]

The keypair consists of public key and secret key. Secret key is, well, secret. If someone ever sees it, I would have to go to each service where it's used and disable it. Public key is, well, public. I can put it on a web page and upload it to key servers.

Publicity of the public key is the magic of the whole system. Now I can exchange key with someone without meeting them.

Export My Public Key

Alice want's to send me a message. For this, she needs my public key. I'll export it. Plain 'cd' goes to our home directory.

$ cd
$ gpg --export --armor --output tero.pub

Parameters to export are

  • --export Export my public key
  • --armor Only use ASCII characters, so that the output can be viewed and copy -asted.
  • --output tero.pub Save the output into the file "tero.pub"

Let's have a look at our public key

$ ls
tero.pub

$ head -4 tero.pub 
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBGVXHrkBDADaY1iRTfmb9Zl/XZFUVG1LaL9A9y2eGvAehckzcQOQTNaYVBEC
sH+YRyT4np1FdPwDAWkJBUaOzz0DwmQtzRY6exizxG2vF95fsreiNQsuMu+YwkBK

So, here is the public key in ASCII armor. Notice how it says "PUBLIC KEY" at the start. Then there are some 40 lines of gibberish, the actual key in ASCII armor, base64 encoded. And at the end, it says "END PGP PUBLIC KEY".

Alice, Simulated

Let's create Alice. We could create another user in the operating system or use another computer. But to make our practice easier, we'll just use a folder to simulate Alice.

$ cd
$ mkdir alice/
$ chmod og-rwx alice/

We created a folder in our home directory. It was protected so that only the owner (us) can use it, because otherwise 'gpg' complains all the time.

To simulate Alice, we can just work inside this folder, and replace 'gpg' with 'gpg --homedir .' This way, Alice has her own settings and own keyring, separate from the one we actually use.

$ cd alice/
$ gpg --homedir . --fingerprint
gpg: keybox '/home/tero/alice/pubring.kbx' created
gpg: /home/tero/alice/trustdb.gpg: trustdb created
$ gpg --homedir . --fingerprint

So, every command as Alice will run in Alice's directory 'cd ~/alice', and start with 'gpg --homedir .'.

On the first run, the 'gpg' command created Alice's configuration files. They would be automatically created anyway when we first run some 'gpg' commands.

The use of "--homedir ." means use current working directory to save gpg configuration files. You can always give the command 'pwd' to see the current working directory, the dot "." in the command.

Listing the fingerprints of all keys prints nothing, because Alice does not yet have any keys. Not her own, and no imported keys.

It's time for Alice to create her own keypair.

$ gpg --homedir . --gen-key

Real name "Alice", email "alice@example.com.invalid", empty passphrase, yes really (x2). And the key is generated.

We can see Alice's key in her keyring. I've abbreviated the output.

$ gpg --homedir . --fingerprint
pub   rsa3072 2023-11-17 [SC] [expires: 2025-11-16]
      B20F D80B 705C 791D C878  0030 7BAA 4F13 2645 134F
uid           [ultimate] Alice <alice@example.com.invalid>
sub   rsa3072 2023-11-17 [E] [expires: 2025-11-16]

Alice Imports and Verifies Tero's key

In most crypto stories, Alice is chatting with Bob. But today, she'll send me, Tero, a message. To send messages, Alice needs Tero's public key.

The public key is literally public. Alice could get it from a web page, key server, unencrypted email - anywhere. The only important thing is to verify that this key really belongs to Tero. Here, we can just copy the exported public key as Alice is simulated.

$ cd
$ cp -v tero.pub alice/
'tero.pub' -> 'alice/tero.pub'

$ cd alice/
$ gpg --homedir . --import tero.pub
gpg: key A8D8165800B384A3: public key "Tero Karvinen DEMO KEY <tero@example.com.invalid>" imported

Alice can check the fingerprint to verify that this is indeed Tero's key. This step is needed if Tero's public key was obtained over insecure channel, like unencrypted email, a web page or a key server.

$ gpg --homedir . --fingerprint
pub   rsa3072 2023-11-17 [SC] [expires: 2025-11-16]
      B624 CDED 2430 252D 298D  7EC4 A8D8 1658 00B3 84A3
uid           [ unknown] Tero Karvinen DEMO KEY <tero@example.com.invalid>
sub   rsa3072 2023-11-17 [E] [expires: 2025-11-16]
  • [Nokia ringtone Säkkijärven polkka playing]
  • Tero: Hi Alice!
  • Alice: Hi Tero! Let's chat for a moment so I know it's you.
  • [blah blah]
  • Alice: OK, would you read your fingerprint.
  • Tero: bravo-six-two-four, charlie-delta-echo-delta...
  • Alice: Great, it matches! I'll send you a message soon.

Alice signs Tero's key to mark it as trusted. Obviously, Tero's key in your test will have a different fingerprint. Alice could also refer to keys with their email addresses. As Alice is verifying the fingerprint, using it in the command protects against mistakes.

$ gpg --homedir . --sign-key "B624 CDED 2430 252D 298D  7EC4 A8D8 1658 00B3 84A3"

Keys could also be verified using singatures from trusted third parties.

We can now see the trust

$ gpg --homedir . --fingerprint
uid           [ultimate] Alice <alice@example.com.invalid>
uid           [  full  ] Tero Karvinen DEMO KEY <tero@example.com.invalid>

So Alice has ultimate trust for her own key. It's been like this since the beginning, when Alice generated her keypair on her own computer with "--gen-key".

Now, after verifying and signing Tero's key, Alice has full trust on Tero's key.

Now that Alice has Tero's key, she can encrypt messages to Tero.

Tero needs Alices Public Key to Know It's Her

Alice wants to sign her messages. Tero needs Alice's key to know that it's really her.

The process for exporting, importing, verifying and trusting the key is the same as before. Only the roles have been swapped.

Alice:

$ gpg --homedir . --export --armor --output alice.pub
$ cp -v alice/alice.pub .
'alice/alice.pub' -> './alice.pub'

Key is sent over untrusted channel

$ cd 
$ cp -v alice/alice.pub .

Tero imports the key

$ gpg --import alice.pub

Nokia ringtone, chat, verify fingerprint...

$ gpg --sign-key "B20F D80B 705C 791D C878  0030 7BAA 4F13 2645 134F"
$ gpg --fingerprint
uid           [  full  ] Alice <alice@example.com.invalid>

Trust Established!

The initial steps of establishing trust are completed. Tero and Alice have exchanged keys, and verified that they have the correct keys.

Note that Tero and Alice only needed to verify that they have the correct public keys. The keys could be sent over untrusted and hostile network, such as the Internet. In fact, public key encryption allows establishing trust over untrusted channel.

Alice Sends a Secret Message

Alice writes a message:

$ cd ~/alice/
$ micro message.txt

In the message, Alice writes the message, then saves Ctrl-S and quits Ctrl-Q.

Hi Tero,

This is my secret message. I'm so happy we have PGP to protect our communications! 

The right to private communication is important in a free society. 

-- Alice

She encrypts and signs the message

$ gpg --homedir . --encrypt --recipient tero@example.com.invalid --sign --output encrypted.pgp --armor message.txt

The parts of the command are

Simulation:

  • --homedir . Use gpg configuration and keyring from working directory 'pwd'. We already did 'cd ~/alice', so that's what we're using. This is just for our Alice simulation here

Encrypt to Tero using Tero's public key

  • --encrypt Encrypt the message
  • --recipient tero@example.com.invalid To key identified by email address. Only keys within our keyring are considered, so it's safe to use email addresses here. Of course, fingerprints work, too. Encryption only needs one key, the public key for the recipient.

Sing using Alice's secret key

  • --sign Sign the message using Alice's secret key. Recipient of this message can use Alice's public key to verify that it's really her sending the message.

Output file, encrypted

  • --armor Use normal, printable ASCII characters for the message. This way, we can copy-paste it, and it does not break if we send it in email body. It uses base64 to show binary data using normal letters.
  • --output encrypted.pgp Save the encrypted message to this file

Input file, plain text

  • message.txt The plain text file we wish to encrypt

Encrypted message was generated.

$ ls encrypted.pgp 
encrypted.pgp
$ head -4 encrypted.pgp 
-----BEGIN PGP MESSAGE-----

hQGMA8AvdpcLFVGWAQwAsyUo5zn4l0V7+Db8juusSpk7fll5FBs7aCxi4Obns92m
PcfMaE8TP+slIP1ngw/Ljs8X7ODrHMdmRrXXMbM0cGTnJzxci4q30Fi1AIg1QLvC

The encrypted message starts with "BEGIN PGP MESSAGE" and ends with "END PGP MESSAGE". The 20+ lines of gibberish in between is Alice's secret message, encrypted, signed and ASCII armored.

Even Alice can't decrypt the message now. It's been encrypted with Tero's public key. Only Tero can open it, because only Tero has Tero's public key.

Encrypted message can be sent over hostile, untrusted channel like the Internet. We'll simulate the transfer by copying the file.

$ cp -v alice/encrypted.pgp .
'alice/encrypted.pgp' -> './encrypted.pgp'

Tero Decrypts and Verifies the Message

Tero is happy to receive "encrypted.pgp". I wonder what Alice could be writing in this secret message.

$ gpg --decrypt encrypted.pgp 
gpg: encrypted with 3072-bit RSA key, ID C02F76970B155196, created 2023-11-17
      "Tero Karvinen DEMO KEY <tero@example.com.invalid>"
Hi Tero,

This is my secret message. I'm so happy we have PGP to protect our communications! 

The right to private communication is important in a free society. 

-- Alice
gpg: Signature made Fri 17 Nov 2023 12:52:22 PM EET
gpg:                using RSA key B20FD80B705C791DC87800307BAA4F132645134F
gpg: Good signature from "Alice <alice@example.com.invalid>" [full]

The message was decrypted with Tero's secret key. Now Tero can read the message: "Hi Tero, This is my secret...".

Alice's signature was verified using Alice's public key. Now Tero knows it's really her.

gpg: Good signature from "Alice <alice@example.com.invalid>" [full]

GPG says that the signature could be verified with a key already in Tero's keyring "Good signature". It's 'from "Alice ...'. The key used for signing is already trusted by us "[full]".

Well Done

Well done, you have now

  • Encrypted messages, so that attackers can't read them
  • Signed messages, so that attackers can't impersonate your contacts
  • Played with gpg, a critically important PGP tool
  • Have a nice practice environment to learn more about PGP

Next, you could try

  • Send a message on the other direction, from Tero to Alice.
  • Read Copeland et. al. 1999 GNU Privacy Handbook
  • Read how Linux kernel maintainers use PGP. Ryabitsev: Kernel Maintainer PGP guide
  • Try some commonly used 'gpg' features on your own
    • Detatched signatures, often used for verifying ISO images and software downloads
    • Distributing keys, using key servers. You still have to verify that the keys are really from the right person.

Troubleshooting

No trouble? No need for troubleshooting. Go encrypt some messages!

"gpg: agent_genkey failed: No such file or directory" - solution: 'killall gpg-agent'

$ cd ~/alice/
$ gpg --homedir . --gen-key 
gpg: agent_genkey failed: No such file or directory
Key generation failed: No such file or directory

With all the playing around, generating and deleting folders and keys, we have confused gpg-agent. It's a tool to remember our passphrase for the key for a while. Let's just kill the process, so it can automatically start and work normally.

$ killall gpg-agent

Killall kills processes by name. If you don't have it already installed, you can just use package manager: 'sudo apt-get update; sudo apt-get -y install psmisc'.

Now let's run the same command again

$ cd ~/alice/
$ gpg --homedir . --gen-key 
pub   rsa3072 2023-11-17 [SC] [expires: 2025-11-16]
      B20FD80B705C791DC87800307BAA4F132645134F
uid                      Alice <alice@example.com.invalid>
sub   rsa3072 2023-11-17 [E] [expires: 2025-11-16]

Real name "Alice", email "alice@example.com.invalid", empty passphrase, yes really (x2). And the key is generated.

'gpg: key "B624 ... 84A3" not found: No public key' - solution: List fingerprints

$ gpg --homedir . --sign-key "B624 CDED 2430 252D 298D  7EC4 A8D8 1658 00B3 84A5"
gpg: key "B624 CDED 2430 252D 298D  7EC4 A8D8 1658 00B3 84A3" not found: No public key

You can't trust a key you don't have.

Check which keys you actually have in your keyring

$ gpg --fingerprint # for your main keyring
$ cd ~/alice/; gpg --homedir . --fingerprint # for alices keyring

To fix it:

  • Import the key. You need to import the key from file to get it to your keyring. Exporting and importing public keys is described above.
  • Use correct fingerprint. Your fingerprints will be different than what's demonstrated here. High quality, crypto level randomness is required for encryption to work. Use "--fingerprint" to show fingerprints of keys in your keyring.
  • Are you being Alice or Tero? Commands that simulate Alice are run in "~/alice" and containt "--homedir .".