Skip to content

GnuPG

About GnuPG

GnuPG is a tool for secure communication.

According to gnupg.org:

GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. A wealth of frontend applications and libraries are available. GnuPG also provides support for S/MIME and Secure Shell (ssh).

Installation

GnuPG is available in most Linux distributions' package repositories:

  • Debian: apt-get install -y gnupg
  • Red Hat: dnf install -y gnupg2
  • SUSE: zypper in -y gpg2

For Windows, you can install either the command-line tool or the full Gpg4win suite. Download it from the official site.

Quick Start

Tutorial - Preparation

We'll go through a brief tutorial below, using a Linux system. Let's prepare our system for that by running these commands:

Bash
mkdir /tmp/gpgdemo
chmod 777 /tmp/gpgdemo
cd /tmp/gpgdemo
umask 022

Generating Keys

GnuPG uses public-key cryptography so that users may communicate securely. In a public-key system, each user has a pair of keys consisting of a private key and a public key. A user's private key is kept secret; it need never be revealed. The public key may be given to anyone with whom the user wants to communicate.

Let's get started with our own key first:

Bash
# Generate a key
gpg --generate-key

# Export the public key to share it later
gpg --export --no-armor --output mykey.gpg clifford.weinmann@azul.africa

That's it. Now we're ready to use the key to encrypt data.

Encrypting Files

To encrypt & decrypt an existing file, follow these steps:

Bash
# Encrypt an existing file
gpg --encrypt --output hosts.gpg /etc/hosts

# Inspect the files
ls -l /etc/hosts hosts.gpg
file /etc/hosts hosts.gpg

# Decrypt the file again, and save it to a new name
gpg --quiet --decrypt --output hosts hosts.gpg
diff /etc/hosts hosts

Note that most gpg command line options have shorter forms too, for example the above commands can be rewritten as:

Bash
# Encrypt an existing file
gpg -e -o hosts.gpg /etc/hosts
# Decrypt the file again, and save it to a new name
gpg -q -d -o hosts hosts.gpg

We'll stick to the longer form here for better readability.

Generating & Encrypting Passwords

This document was triggered by a need to share passwords securely.

Of course GnuPG can generate random strings to use as passwords, with:

Bash
$ gpg --gen-random --armor 1 18
MpHT8GQCJQ0gCAHklJDTkJ6w

Where 1 is a quality level, and 18 is the number of random bytes to generate (before Base64 encoding).

For some quick alternate ways to generate random strings to use as passwords, see Generate Passwords.

Now let's use GnuPG you can encrypt such a password and save it to a file:

Bash
gpg --gen-random --armor 1 18 | gpg --encrypt --output my-password.gpg

Decrypt the file with:

Bash
gpg --quiet --decrypt my-password.gpg

A convenient way to manage such password files in an organized way is with th pass utility.

Tip: If you're pasting passwords into the command line, at least in bash, start the command with a space to prevent it from being added to the shell history.

Share Files With Others

How do we share encrypted data with others? Let's demonstrate this using 2 separate local user accounts. First, we'll create a new account for this purpose:

Bash
sudo useradd --create-home --shell /bin/bash azuldemo

In a new terminal window, let's create a key for the new user:

Bash
$ sudo -i -u azuldemo
azuldemo$ gpg --quick-generate-key azuldemo@azul.africa
azuldemo$ cd /tmp/gpgdemo
azuldemo$ gpg --export --no-armor --output azuldemo.gpg azuldemo@azul.africa
azuldemo$ gpg --list-key --with-wkd-hash azuldemo@azul.africa
pub   ed25519 2025-02-01 [SC] [expires: 2028-02-01]
      3C9859929C83B53A9FB4CD52503503E3C82E9A5E
uid           [ultimate] azuldemo@azul.africa
              35j5icoafbu65ej4y5iz9fgjxojx5nmu@azul.africa
sub   cv25519 2025-02-01 [E]

azuldemo$ echo -e 'expire\n0\ny\nsave\n' | gpg --command-fd 0 --edit-key azuldemo@azul.africa

Back in our own terminal, let's import the new user's key & sign it:

Bash
$ gpg --show-key --with-wkd-hash azuldemo.gpg
pub   ed25519/503503E3C82E9A5E 2025-02-01 [SC] [expires: 2028-02-01]
      Key fingerprint = 3C98 5992 9C83 B53A 9FB4  CD52 5035 03E3 C82E 9A5E
      Keygrip = 0E6A6B153ECC60288CF9E58F6212684A2AAA8D75
uid            azuldemo@azul.africa
               35j5icoafbu65ej4y5iz9fgjxojx5nmu@azul.africa
sub   cv25519/6D2DE5AB2F55DF1D 2025-02-01 [E]
      Keygrip = 7870915BA0681AE0A5DDA49A615C49B35EF47596

$ gpg --import azuldemo.gpg

$ echo -e 'sign\ny\nsave\n' | gpg --command-fd 0 --local-user clifford.weinmann@azul.africa --edit-key azuldemo@azul.africa

Now we can encrypt some data to send to them:

Bash
$ gpg --quiet --decrypt my-password.gpg | gpg --encrypt --armor --recipient azuldemo@azul.africa > shared-password.asc
$ gpg --quiet --decrypt my-password.gpg | gpg --encrypt --armor --local-user clifford.weinmann@azul.africa --sign --recipient azuldemo@azul.africa > signed-password.asc

Back in our test user's terminal:

Bash
# Decrypt the password
azuldemo$ gpg --decrypt signed-password.asc
azuldemo$ gpg --quiet --decrypt shared-password.asc

# Import the sender's key so we can reply
azuldemo$ gpg --import mykey.gpg

# Sign the key with ours to indicate that we trust it - note "sign" and "save" subcommands below
azuldemo$ echo 'Got it!' | gpg --encrypt --armor --recipient clifford.weinmann@azul.africa > reply.asc

# Get rid of the trust prompt
azuldemo$ gpg --edit-key clifford.weinmann@azul.africa

pub  ed25519/456757387702E12F
     created: 2025-02-04  expires: 2028-02-04  usage: SC  
     trust: unknown       validity: unknown
sub  cv25519/FFFA8A60501A3000
     created: 2025-02-04  expires: 2028-02-04  usage: E   
[ unknown] (1). Quintin <quintin@example.com>

gpg> sign

pub  ed25519/456757387702E12F
     created: 2025-02-04  expires: 2028-02-04  usage: SC  
     trust: unknown       validity: unknown
 Primary key fingerprint: EFDB 050E 225A 587B 6AB8  96EF 4567 5738 7702 E12F

     Quintin <quintin@example.com>

This key is due to expire on 2028-02-04.
Are you sure that you want to sign this key with your
key "azuldemo@azul.africa" (4DC72CE825EF2CF5)

Really sign? (y/N) y

gpg> save


# Create a reply message
azuldemo$ echo 'No trust prompt ;-)' | gpg --encrypt --armor --recipient clifford.weinmann@azul.africa

Tutorial - Cleanup

Clean up after demo (from our own terminal):

Bash
rm -r /tmp/gpgdemo
sudo userdel -r azuldemo

Sharing Your Public Key

Using GnuPG to encrypt our own files is great.

As demonstrated above, it is equally handy for sharing encrypted documents with others. In the example above, the both users were on the same machine, so we could just copy a file. In practice we need to find a better way to get our public keys to people who want to send us encrypted messages / files.

There are numerous ways to share your exported public key, including:

  • Manually sending the key to someone via email / instant message / file share / web server etc. This works, but is a manual process.
  • Publish it on a (public) key server. This eliminates the manual effort, but trust can be an issue - how does anyone know it's really your key?
  • Share it via DNS records (cert, pka, dane). This solves the above problems, but is complicated to set up.
  • Web Key Directory (WKD) publishes your public key on your own website, and is our preferred approach.

To use WKD, the owner of the key must first export their key, with:

Bash
gpg --export --no-armor --output azuldemo.gpg azuldemo@azul.africa
gpg --list-key --with-wkd-hash azuldemo@azul.africa

Send both the output file (azuldemo.gpg in the above example) and the output from the gpg --list-key --with-wkd-hash <key> command to your trusty local web administrator.

The administrator will then publish it on the website, using the WKD hash (the 32 character identifier before your domain name - 35j5icoafbu65ej4y5iz9fgjxojx5nmu in our earlier example) as file name. For azul.africa, this means:

  • Publish it at https://openpgpkey.azul.africa/.well-known/openpgpkey/azul.africa/hu/<hash>
  • Copy to mu1.azul.ninja:/usr/share/nginx/openpgpkey/.well-known/openpgpkey/azul.africa/hu/<hash>

A third party that wants to send you a file can then import it automatically, with:

Bash
gpg --auto-key-locate clear,nodefault,wkd  --locate-keys <your-email-address>

Once this is done, they can encrypt a file / message with you as --recipient.

This will prompt them with a message saying "It is NOT certain that the key belongs to the person named in the user ID". To avoid this prompt, they can ask you to verify your key signature (just like we all do with those SSH host keys 😁), and then sign it with their own key:

Bash
$ gpg --edit-key stan.lovisa@azul.africa 
gpg (GnuPG) 2.4.4; Copyright (C) 2024 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  rsa3072/73C483F3F7C91885
     created: 2025-01-31  expires: 2027-01-31  usage: SC  
     trust: marginal      validity: unknown
sub  rsa3072/543F4A9B8A98ACD2
     created: 2025-01-31  expires: 2027-01-31  usage: E   
[ unknown] (1). Stan Lovisa <stan.lovisa@azul.africa>

gpg> sign
gpg: using "A9EA7F3D" as default secret key for signing

pub  rsa3072/73C483F3F7C91885
     created: 2025-01-31  expires: 2027-01-31  usage: SC  
     trust: marginal      validity: unknown
 Primary key fingerprint: B82D C217 0404 2277 71A4  2132 73C4 83F3 F7C9 1885

     Stan Lovisa <stan.lovisa@azul.africa>

This key is due to expire on 2027-01-31.
Are you sure that you want to sign this key with your
key "Clifford Weinmann <clifford@weinmann.africa>" (9FD07C97758B1A18)

Really sign? (y/N) y

gpg> save

Further Reading

For a more thorough introduction to GPG, check out the GNU Privacy Handbook. The online English version is available here.