GNU Privacy Guard

A minimalist introduction
Version v1.0.0
Updated
Author jtriley2p License AGPL-3

Introduction

GNU Privacy Guard aka GPG is a privacy preserving tool used by devops engineers, darknet market vendors, and human rights activists. In this article we’ll create an elliptic curve keypair then sign, encrypt and decrypt data with it. No clients, no GUI’s, all you need is a shell (bash, zsh) terminal.

Key Generation

First we need to create a public-private key pair. GPG defaults to RSA, so we’ll need to use “expert” mode to access the elliptic curve cryptography options.

gpg --full-gen-key --expert

This starts an interactive session. Follow each step carefully, when prompted, you’ll need to jiggle the mouse during key generation to improve entropy. Be ready for this, elliptic curve keys are generated quickly. You’ll also be prompted for a password. It goes without saying but make it strong.

The session should look a bit like the following snippet. I chose “9” (ECC and ECC) for signing and encrypting, then the “9” (secp256k1) curve like good little paranoid civilians, “2y” in case quantum supremacy breaks our caveman abelian group asymmetric cryptography by 2026, confirm this is all correct with “y”, then enter some information. I’m omitting the email address for now, but use this field if you intend to use this with an email client. Finally “O” (okay) to confirm all choices.

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? 9
Please select which elliptic curve you want:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 9
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 Sep  3 [REDACTED] 2026 UTC
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: jtriley-eth
Email address: 
Comment: 
You selected this USER-ID:
    "jtriley-eth"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 0xF8842DEEB412DD47 marked as ultimately trusted
gpg: revocation certificate stored as '/home/user/.gnupg/openpgp-revocs.d/2D1AAD0BB87A52DDFAF66734F8842DEEB412DD47.rev'
public and secret key created and signed.

pub   secp256k1/0xF8842DEEB412DD47 2024-09-03 [SC] [expires: 2026-09-03]
      2D1AAD0BB87A52DDFAF66734F8842DEEB412DD47
uid                              jtriley-eth
sub   secp256k1/0x6BC6F860744B0301 2024-09-03 [E] [expires: 2026-09-03]

Great, so the final few lines show some identifying information about the key. No need to save this information manually, as we can retrieve it by listing the keys below.

Key Management

We can list all public keys known to our machine’s GPG as follows. This will include all public keys imported or generated, even those to which we have no private keys.

gpg --list-keys
pub   secp256k1/0xF8842DEEB412DD47 2024-09-03 [SC] [expires: 2026-09-03]
      2D1AAD0BB87A52DDFAF66734F8842DEEB412DD47
uid                   [ultimate] jtriley-eth
sub   secp256k1/0x6BC6F860744B0301 2024-09-03 [E] [expires: 2026-09-03]

This format is subject to change so if versions diverge from the time of writing to the time of reading, this is okay.

Now to export our public key, we should export it with the “armor” option, which converts it to readable ASCII text.

# replace 'jtriley-eth' with your own username

gpg --export --armor jtriley-eth
-----BEGIN PGP PUBLIC KEY BLOCK-----

mE8EZtdM8RMFK4EEAAoCAwTlArBoQUU1v7NvP4ND5l+b+M+Odhq+TikaK/SResQz
DHLPYwXC3DGz2rB5oPPZCK0U8VPwtyw5owbthjh/HlcqtAtqdHJpbGV5LWV0aIiW
BBMTCgA+FiEELRqtC7h6Ut369mc0+IQt7rQS3UcFAmbXTPECGwMFCQPCZwAFCwkI
BwMFFQoJCAsFFgIDAQACHgECF4AACgkQ+IQt7rQS3Uc/SwEAzfyyc63u+bmgb1oj
R5RODot6NnntITdmQ2cpG1CETCABALdVK9j1uuNY2rm4axriU3/FZG1qndBWH6Ni
AaSrk1xfuFMEZtdM8RIFK4EEAAoCAwQ8E7pAmvYeN41gYNYm9A7iWxvu1R1Xv/Mm
vSH24B/fEkBwyXyfBa1HvgeMq/LHliS0BwkHsYZat/fdc74tii8rAwEIB4h+BBgT
CgAmFiEELRqtC7h6Ut369mc0+IQt7rQS3UcFAmbXTPECGwwFCQPCZwAACgkQ+IQt
7rQS3UdxTgD/UzQPEZMZCUAmfPROYb7scPNMrUwtUQi273d7l2yDBYYBAJ7vp0kn
2LtD0YnuvBL9ejFHpzQxudHrTJ1238+bShx7
=XYFB
-----END PGP PUBLIC KEY BLOCK-----

This is a plaintext format that can be shared. As an exercise, import the key printed above in the next step.

Start by store the ascii text in a file; for the example we’ll call this jtriley.txt, though the naming scheme is arbitrary.

gpg --import jtriley.txt
gpg: key 0x6BC6F860744B0301: public key "jtriley-eth" imported
gpg: Total number processed: 1
gpg:               imported: 1

This key should now show up when using gpg --list-keys.

Signing

Next we’ll generate a cleartext signature for sharing. Start by creating a message.txt file.

i'm sorry that your warrantless surveillance regime was built on the
assumption that people would always need intermediaries to transact

Then we generate a cleartext signature as follows. This will prompt for the password entered during key generation.

# replace 'jtriley-eth' with your username
# replace 'message.txt' with your file path
#
gpg --clear-sign --local-user jtriley-eth message.txt

This creates a file with the same filename as input with a .asc suffix by default. We can “cat” this to the terminal:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

i'm sorry that your warrantless surveillance regime was built on the
assumption that people would always need intermediaries to transact
-----BEGIN PGP SIGNATURE-----

iNUEARMIAH0WIQQtGq0LuHpS3fr2ZzT4hC3utBLdRwUCZtdcBF8UgAAAAAAuAChp
c3N1ZXItZnByQG5vdGF0aW9ucy5vcGVucGdwLmZpZnRoaG9yc2VtYW4ubmV0MkQx
QUFEMEJCODdBNTJEREZBRjY2NzM0Rjg4NDJERUVCNDEyREQ0NwAKCRD4hC3utBLd
R0qVAP4nUB3bjxHeWqHb+S6wSbgde9p91hIyLQMvRhLquP2CRAD/UGf9iUVabVGY
e945XfIZjo/YeEmjYvrpoxEK/qGu5vA=
=phkG
-----END PGP SIGNATURE-----

Encryption

To encrypt our data, we have a few options. The most basic example is encrypting a message with the recipients public key, this is useful for smaller messages but can be cumbersome for larger messages.

Asymmetric

To encrypt a message with a foreign public key we’ll use the following. I’m using my own public key for this example so we can also demonstrate decryption.

# replace 'jtriley-eth' with the intended recipient's public key
# replace 'message.txt' with your file path
#
gpg --recipient jtriley-eth --encrypt message.txt

By default this creates a file with the same filename as the input with a .gpg suffix. This we won’t cat to the terminal since it’s in an unreadable format, but here’s how to decrypt the message, provided you have the corresponding private key.

# replace 'message.txt.gpg' with your file path
#
gpg --decrypt message.txt.gpg

By default this prints to the standard output, but can be stored in a file by using --output <outfilename>.

gpg: encrypted with 256-bit ECDH key, ID 0x6BC6F860744B0301, created 2024-09-03
      "jtriley-eth"
i'm sorry that your warrantless surveillance regime was built on the
assumption that people would always need intermediaries to transact

Asymmetric with Signature

We can also sign the message during encryption where the signature is verified during decryption.

Note: Since the commands are getting long, lines that end in \ tell the shell not to treat the newline character as a terminal character of the command but rather the command continues on the next line.

# replace 'jtriley-eth' with the signing username
# replace 'recv-user' with the recipient username
# replace 'message.txt' with your file path
#
gpg --local-user jtriley-eth \
    --recipient recv-user \
    --sign \
    --encrypt message.txt

The output is omitted for brevity but should be comparable to the previous section.

Symmetric with Signature

We can also send a message encrypted with a symmetric cipher and a passphrase.

Note that by default GPG will cache the password, so it may not prompt for it.

# replace 'jtriley-eth' with the signing username
# replace 'recv-user' with the recipient username
# replace 'twofish' with your preferred cipher algo
# replace 'message.txt' with your file path
#
gpg --local-user jtriley-eth \
    --recipient recv-user \
    --sign \
    --symmetric \
    --cipher-algo twofish \
    --encrypt message.txt

There’s a lot more going on here, so to summarize, we sign with jtriley-eth, the recipient is recv-user, we use symmetric cryptography, in particular twofish, though others are available, and we’re encrypting message.txt.

The default symmetric cipher algorithm is AES-128, though there are a variety of options to choose from (see gpg --help).

Summary

All of this information comes from the man page of GPG, it is far too long to read start to finish but is a great resource when the arcane corners of the internet aren’t helpful and the warnings should be read very carefully.

Until next time.