GPG (the GNU Privacy Guard) is a complete and free implementation of the OpenPGP standard. Based on various mature algorithms to select from, GPG acts as a convenient tool for daily cryptographic communication.

GPG has two primary functionalities: (1) it encrypts and signs your data for secure transfering and verifiable information integrity, and (2) it features a versatile key management system to construct and promote web of trust. GPG also has a well-designed command line interface for easy integration with other applications such as git.

This article is going to briefly elaborate some key concepts and usage of GPG, and then present demonstration to cryptographically sign git commits with the help of GPG.

Modern Cryptography 101

To understand how GPG or other privacy tools work, I should first introduce some basic ideas of modern cryptography. Let’s start with the two primary problems of secure communication, which includes data encryption and data integrity/authenticity verification.

Data Encryption

Peer-to-peer data encryption aims to prevent the message from being spied by a potential third party, especially when the two parties are communicating over a channel open to the public. Imagine Alice and Bob are mailing through pigeons, with the message unencrypted and clearly written on the paper. It is possible for a third person called Blake to intercept the pigeon, open the attached mailbox and read the message in it, without Alice and Bob knowing his existence.

Data encryption is introduced to defend against such attacks. For secure data exchange, Alice and Bob should agree on some kind of invertible message processing pipeline. The sender preprocesses (encrypts) the message before it attached to the pigeon, and the recipient performs the inverted process (decrypts) to read the clear message. In terms of cryptography, such pipeline is called a cryptographic algorithm or a cipher.

A cipher usually works with a key (or several keys). With the cipher fixed, the message encrypted with one key should only be decrypted with the same one. Modern ciphers are carefully designed to satisfy that Blake is hard to perform decryption without the key, even if he knows the full detail of the cipher. Under this assurance, Alice and Bob only have to choose a specific algorithm as cipher from the public list, and agree on the key before communication. This simplifies the process of negotiation, as they don’t have to discuss the sophisticated implementation of the cipher.

The currently available ciphers can be roughly categorized into two families, the symmetric ciphers and the public-key ciphers.

Symmetric Ciphers

Symmetric ciphers encrypt and decrypt messages using the same key. They went back far into human history. You might have heard of the Caesar cipher that replaces each plaintext letter with a different one a fixed number of places down the alphabet, which is a famous example of this category. For Caesar cipher, the key is the number of positions being shifted, like 3 for a tranformation of A->D, B->E.

Symmetric cipher exposes several drawbacks in realistic usage. First, it provides no defense against the scenario of the key being stolen. If Blake somehow knows the key, he can both spy and forge the messages sent between Alice and Bob. Also, it would require $n(n-1)/2$ keys to achieve pairwise communication among $n$ persons, increasing the expense of key exchange and opportunity of leakage.

Public-key Ciphers

By contrast, the public-key ciphers mitigate the problems by adopting a pair of keys instead of just one. A message encrypted by one key might only be decrypted with the other, and vice versa.

Practically we name one of them as public key and the other as secret key. The public key is published to whom we want to communicate with, while the secret key is kept locally and must only be known to ourselves. When Alice sends a message to Bob, the message is encrypted with Bob’s public key, and Bob uses his own private key to decrypt it on receiving.

Public-key ciphers reduce the adverse impact of public key leakage. An attacker with Alice’s public key in hand is unable to decrypt messages sent by others to her. Also, only $n$ keys have to be exchanged for $n$-person pairwise communication. The advantages overall result in lower key exchanging expense and inclined popularity of public-key ciphers in real life.

Digital Signatures for Data Integrity

Ciphers solve the problem of data encryption, preventing the messages transfered from being spied by a third party, albeit they do not guarantee the integrity and authenticity of the data. Bob cannot tell whether the message he received is truly sent by Alice, since his public key is known by the world. Towards this purpose, the concept of digital signatures must be introduced.

Digital signatures employs the idea of hashing. In cryptography, hashing is a technique to generate digest for a piece of message. The digest must be almost unique, that is, two different messages should ideally have unequal value of digests. Also, it should guarantee that no one would recover the original plaintext from the digest .

Practically, Alice the sender would attach an encrypted digest as digital signature along with the message, by first applying a hash function and then encrypt with Alice’s own private key on the message. Anyone can decrypt the signature with Alice’s public key to verify that the message is truly signed by Alice and sent as-is. Since no one else knows Alice’s private key, the signature cannot be forged and hence is a mighty tool to assure authenticity.

GPG

The ciphers and digital signatures form the foundation of modern cryptography, upon which OpenPGP is proposed and GPG built as a high-level structure for convenient daily usage. This post will not explain the full details of GPG, but its basic idea and some of the frequently-used operations as tutorial.

Compared with the basic public-key system, OpenPGP further adopts a more sophisticated design. OpenPGP adopts a concept “user” to distinguish identities. A user is uniquely identified by his real name and email, and could own a primary key pair plus an optional collection of sub key pairs, each key pair with potentially different capabilities such as encryption or signing. The separation of keys’ responsibility enables one to revoke compromised keys without interfering the validity of others, leading to more flexible key management.

Key Generation

To create a user and generate the key pair, we can use the gpg --generate-key command

$ gpg --generate-key
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

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

Real name: FooBar
Email address: foobar@foobar
You selected this USER-ID:
"FooBar <foobar@foobar>"

Change (N)ame, (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 417706EE02BA78E3 marked as ultimately trusted
gpg: revocation certificate stored as '/home/hsfzxjy/.gnupg/openpgp-revocs.d/A100A3E7D94F665A2CB5A34D417706EE02BA78E3.rev'
public and secret key created and signed.

pub rsa3072 2023-01-10 [SC] [expires: 2025-01-09]
A100A3E7D94F665A2CB5A34D417706EE02BA78E3
uid FooBar <foobar@foobar>
sub rsa3072 2023-01-10 [E] [expires: 2025-01-09]

In this example, we’ve created a user with real name being FooBar and the email foobar@foobar. During the process, the program will prompt a dialog inquiring to enter a passphrase, which acts as the main guardian to access your secret key.

By default GPG generates two keys with different capabilities. The primary key prefixed with pub is for signing (S) and certifying (C), and a sub key prefixed with sub for encrypting (E). With gpg --list-key and gpg --edit-key commands, we can inspect the keys stored in our local database and edit one or more of them.

Basic Document Signing

When posting a document to the public, one would like to claim his issuance and expects no one could tamper the content, which can be achieved by digitally signing the document. Let’s check an example

$ echo "hello world" > doc
$ gpg --sign -u FooBar doc
$ cat doc.gpg
-- some binary data --

Here we create a file named doc with a string "hello world" as the content. gpg --sign -u FooBar signs and encrypts the given document with user FooBar‘s secret key, with the bundled result written to a new file doc.gpg. A person knowing FooBar‘s public key could verify its integrity with --verify

$ gpg --verify doc.gpg
gpg: Signature made Tue 10 Jan 2023 09:02:03 PM CST
gpg: using RSA key A100A3E7D94F665A2CB5A34D417706EE02BA78E3
gpg: issuer "foobar@foobar"
gpg: Good signature from "FooBar <foobar@foobar>" [ultimate]

or directly decrypt it with --decrypt

$ gpg --decrypt doc.gpg
hello world
gpg: Signature made Tue 10 Jan 2023 09:02:03 PM CST
gpg: using RSA key A100A3E7D94F665A2CB5A34D417706EE02BA78E3
gpg: issuer "foobar@foobar"
gpg: Good signature from "FooBar <foobar@foobar>" [ultimate]

If the content of doc.gpg be tampered, either of the above operations will fail.

GPG provides several flags to customize the generation of digital signature. For instance, flag --clearsign forces the signature to be separately attached after the plain text, which is more convenient for scenario like sending via e-mail

$ gpg --clearsign -u FooBar -o- doc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

hello world
-----BEGIN PGP SIGNATURE-----

iQHBBAEBCgAsFiEEoQCj59lPZlostaNNQXcG7gK6eOMFAmO9cJMOHGZvb2JhckBm
b29iYXIACgkQQXcG7gK6eONlJAv2NULQR9aPfVfLj6rpKcRxvKDna2vnHhVg2Pyj
I12lJfsF6kA4wMCkuJ5Kzk2OOLAPFHAh+Y5zYnx825vP7ckHBvhYfwfkKmE7wpqN
ptX/ij0TDtp71Nq/oabBOMG1jYop0AwfCd3c5X27UyCGz6V5mdm3Dea4hILsQHld
OsTLz7B6y8FmA4kT5mHOreVMI1cd3NPKKugSS0bSmSv/DN/Znlb72pbD0Hq0iLi/
8LJAEuj2eEnUH5NyS1tY2GHBBUttMMGhgrNAxrBr445+ZGpczUJVYVzS1tdTWwSZ
uzHrD0CWtiIcFe6Au/pzDmbq+EJ9fF7YECCfc0m/QANGUNJydXs18c7IfrT9Awze
UJwYNKxGkXipVfGJECJZ4IvDiGiWDHv/QCZ+Bc0d6ZIu5nUcm4pC3q4RFm7jn/s9
OnDiXaOgHjGiOKOR/Auofzr8gerq0uFFYcbiWots8hjLOlITO2iGMF3jzZk8ncdU
QeT4711EQ4jzoy/5vfOCU7YuKh4=
=gdHf
-----END PGP SIGNATURE-----

With -o<filename> the output will be directed to <filename> instead of the default file name doc.gpg.

Document Encryption and Trust of Web

Document signed using above method could be read by a wide audience, as long as they have user FooBar‘s public key. For a more limited usage where the document should be seen by specific recipient, say user BazBaz, we should encrypt it with BazBaz‘s public key.

The command gpg --export -u BazBaz > bazbaz.gpg will dump all public keys of user BazBaz to file bazbaz.gpg, which can be distributed and imported by other users across the web. As an example, user FooBar imports the file to his local database

(foobar) $ gpg --import bazbaz.gpg
gpg: key 90D332C875527240: public key "BazBaz <bazbaz@bazbaz>" imported
gpg: Total number processed: 2
gpg: imported: 2
gpg: new subkeys: 1
gpg: new signatures: 1
(foobar) $ gpg --list-key
pub rsa3072 2023-01-10 [SC] [expires: 2025-01-09]
A100A3E7D94F665A2CB5A34D417706EE02BA78E3
uid [ultimate] FooBar <foobar@foobar>
sub rsa3072 2023-01-10 [E] [expires: 2025-01-09]

pub rsa3072 2023-01-10 [SC]
EE0B65758BBA776A2D0521B290D332C875527240
uid [ unknown] BazBaz <bazbaz@bazbaz>
sub rsa3072 2023-01-10 [E]

As we can see, the public key of BazBaz now shows up in the local list, but somehow the uid is labeled as [unknown] instead of [ultimate] as FooBar does.

The label [unknown] indicates that GPG will distrust any newly imported keys by default. OpenPGP comes with a multi-level trust model in defense against someone pretending as others’ identity, with [unknown] being the least trusted level. GPG will prompt us if we attempt to encrypt with an [unknown] key

(foobar) $ gpg --sign --encrypt -u foobar --recipient bazbaz doc
gpg: 9F85CD170E8B1269: There is no assurance this key belongs to the named user

sub rsa3072/9F85CD170E8B1269 2023-01-10 BazBaz <bazbaz@bazbaz>
Primary key fingerprint: EE0B 6575 8BBA 776A 2D05 21B2 90D3 32C8 7552 7240
Subkey fingerprint: 7E53 135B C569 F125 63D1 BEF2 9F85 CD17 0E8B 1269

It is NOT certain that the key belongs to the person named
in the user ID. If you *really* know what you are doing,
you may answer the next question with yes.

Use this key anyway? (y/N)

This mechanism protects us from accidentally sending secret information to forged identity.

To tell GPG that the identity is really trusted, we can sign the public key to increase its trust level. Remember this must be done after you actually verify the identity via direct contact to that person. The --sign-key flag is used for this purpose

(foobar) $ gpg -u foobar --sign-key bazbaz
pub rsa3072/90D332C875527240
created: 2023-01-10 expires: never usage: SC
trust: unknown validity: unknown
sub rsa3072/9F85CD170E8B1269
created: 2023-01-10 expires: never usage: E
[ unknown] (1). BazBaz <bazbaz@bazbaz>


pub rsa3072/90D332C875527240
created: 2023-01-10 expires: never usage: SC
trust: unknown validity: unknown
Primary key fingerprint: EE0B 6575 8BBA 776A 2D05 21B2 90D3 32C8 7552 7240

BazBaz <bazbaz@bazbaz>

Are you sure that you want to sign this key with your
key "FooBar <foobar@foobar>" (417706EE02BA78E3)

Really sign? (y/N) y
(foobar) $ gpg --list-key
-- omit --
pub rsa3072 2023-01-10 [SC]
EE0B65758BBA776A2D0521B290D332C875527240
uid [ full ] BazBaz <bazbaz@bazbaz>
sub rsa3072 2023-01-10 [E]

Now check the list again, we can see that the trust level of BazBaz‘s key changes from [unknown] into [full].

OpenPGP’s trust model allows trust to propagate over the web, which eases the overhead of acknowledging key identities. In short, if user A trusts user B‘s identity, and user B has signed the public key of user C, then user A will transitively trust user C‘s identity. User A by this way has no need to individually verify the identity of all the imported keys and therefore enjoys an easier key management scheme.

GPG and Git/Github Integration

GPG can be employed to claim the authenticity of your code by digitally signing your Git commits. Since Git itself uses email to distinguish authors, it’s possible to commit as other people’s identity. A story described how one could push code to Github as the identity of Linus Torvalds. Such vulnerability can be exploited to disseminate malicious code or falsy information over the internet.

Integrate GPG with Git

The Github Docs has a series of posts as the guideline to commit signing and Github interoperation. To start with, we should tell Git about our signing key:

$ gpg --list-secret-keys --keyid-format long
sec rsa3072/417706EE02BA78E3 2023-01-10 [SC] [expires: 2025-01-09]
A100A3E7D94F665A2CB5A34D417706EE02BA78E3
uid [ultimate] FooBar <foobar@foobar>
ssb rsa3072/25E8CB9C4F68EE16 2023-01-10 [E] [expires: 2025-01-09]
$ git config --global gpg.signingkey 417706EE02BA78E3!

With the ! suffix the key precedes others and would always be used. We can alternatively configure to sign commits by default

$ git config --global commit.gpgsign true

As demonstration, let’s switch to the workspace of a git repository and commit the code as usually did

$ git add . && git commit -m 'signed commit'

Afterwards, we can inspect the history and see a digital signature attached

$ git show --show-signature HEAD
commit 57ac1c20094d0248a4a3e8676050f53f547a6afa (HEAD -> hexo)
gpg: Signature made Wed 11 Jan 2023 04:37:14 PM CST
gpg: using RSA key A100A3E7D94F665A2CB5A34D417706EE02BA78E3
gpg: Good signature from "FooBar <foobar@foobar>" [ultimate]
Author: hsfzxjy <hsfzxjy@gmail.com>
Date: Wed Jan 11 16:37:14 2023 +0800

signed commit
-- omit --

which indicates the commit has been signed with success.

Integrate GPG with Github

GPG-signed commits can be highlighted with a Verified label displayed aside on Github, as showcased in the image below,

from which other people would know and trust the authenticity of this commit. Towards this effect, one should associate his GPG keys with Github profile. As instructed in “Adding a GPG Key”, the GPG public key is firstly exported from the command line in the text-form as

$ gpg --armor --export foobar
-----BEGIN PGP PUBLIC KEY BLOCK-----
# GPG public key exported
-----END PGP PUBLIC KEY BLOCK-----

which should be copied to the clipboard with the separators included. Then in the upper-right corner of any page on Github, click the profile avatar and select Settings -> Access -> SSH and GPG keys -> New GPG key, paste the previously copied content into the box, and confirm with the Add GPG Key button, we should finish the association.

Conclusion

GPG is a convenient software to do cryptography jobs and perform key management. While its history went back into old days and the UX might look wierd, it still stands as one of the de-facto standards in modern world. This article extensively explains the fundamental idea of modern cryptography on which GPG is based, followed with the demonstration of some GPG every-day usages, and further the instruction to integrate it with external tools/services such as Git or Github. Hopefully it will enlighten you about the approaches to carry out secure message exchange in daily life.