GnuPG Test Rig

These notes explore the basics of using GnuPG/gpg2 through a test rig, of sorts, wherein Alice and Bob create their keyrings and exchange secret or signed messages. GnuPG provides an option (--homedir) to identify a directory for holding keyrings and their associated data. The test rig then comes down to using separate such directories for Alice and Bob. By keeping these two test-rig directories apart from your live directory (~/.gnupg) as well, the test rig does not interfere with your actual keyrings.

The examples herein use gpg2 version 2.2.5 (with libgcrypt version 1.8.2).

Alice and Bob Set up Their Test Rigs

The test rig consists of container directory ~/gpg2 with separate GnuPG directories for Alice and Bob underneath:

-> mkdir gpg2
-> cd gpg2
-> mkdir --mode 0700 alice
-> mkdir --mode 0700 bob

Each directory gets a simple configuration file appropriate for a test rig, in which passphrases are not needed:

-> cat > alice/gpg.conf << EOF
no-greeting
keyid-format long
EOF
-> cp alice/gpg.conf bob

Lastly, there's a little file serving as a test message:

-> echo "Hello there, world." > hello.txt

Here's the snapshot:

 -> ls -R
.:
alice/  bob/  hello.txt

./alice:
gpg.conf

./bob:
gpg.conf

To keep the test rig contained and easy-going, the working directory for running commands is assumed to be ~/gpg2, below. All of Alice's gpg2 commands will start with option "--homedir alice" and will consequently acquire the convenience options in file gpg.conf and look to directory alice for reading and writing key-pairs. Likewise, all of Bob's commands will start with option "--homedir bob" and use directory bob. And that's all there is to the test rig.

The directory names are chosen to indicate their roles; gpg2 does not care.

In typical usage (without --homedir), gpg2 assumes directory ~/.gnupg and creates it automatically if absent. The configuration file is optional and still up to the user to manage; see also --options and --no-options.

Alice Creates Her Keys

Alice begins her GnuPG career by creating three key-pairs, or simply keys, a primary key and two secondary keys. She will use her primary key exclusively for certifying (i.e., signing) other keys. She will use one secondary key for signatures, and the other for encryption/decryption. Because Alice starts each command with "--homedir alice", gpg2 stores all of her keys in directory alice.

First, Alice creates her primary key using --quick-generate-key:

-> gpg2 --homedir alice --quick-generate-key Alice rsa cert never
gpg: keybox '/home/ray/gpg2/alice/pubring.kbx' created
⋮
gpg: /home/ray/gpg2/alice/trustdb.gpg: trustdb created
gpg: key 4821DF647F864C58 marked as ultimately trusted
gpg: directory '/home/ray/gpg2/alice/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/ray/gpg2/alice/openpgp-revocs.d/614EBCB112A59D09581872304821DF647F864C58.rev'
public and secret key created and signed.
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                            Alice

Alice labels her key with her name, chooses the RSA algorithm ("rsa"), restricts use to certification ("cert"), and opts-out of expiration ("never"). Since this is her first key, gpg2 creates required ancillary files and directories underneath alice.

Alice's private keyring now holds her primary secret key ("sec"), an RSA key with 2048 bits, created 2018-03-06, and slated for certification ("[C]"):

-> gpg2 --homedir alice --list-secret-keys 
⋮
sec   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [ultimate] Alice

Her public keyring holds the public ("pub") partner of the primary secret key:

-> gpg2 --homedir alice --list-public-keys
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [ultimate] Alice

This key-pair's user ID is "Alice". That string of 40 hexadecimal digits (614E⋯4C58) is the key-pair's fingerprint. The string of 16 hex digits (4821⋯4C58) is the key's long ID, the last 16 digits of the fingerprint.

Next, Alice adds two secondary key-pairs or subkeys under her primary key using --quick-add-key. She designates one for encryption ("encr") and one for signing ("sign"). The primary key is identified by its fingerprint:

-> gpg2 --homedir alice --quick-add-key 614EBCB112A59D09581872304821DF647F864C58 rsa encr never
-> gpg2 --homedir alice --quick-add-key 614EBCB112A59D09581872304821DF647F864C58 rsa sign never

Now Alice's secret keyring additionally holds two subkeys ("ssb"), one for encryption ("[E]") and the other for signing ("[S]"):

-> gpg2 --homedir alice --list-secret-keys 
⋮
sec   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [ultimate] Alice
ssb   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
ssb   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

And her public keyring holds the partner subkeys ("sub"):

-> gpg2 --homedir alice --list-public-keys
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [ultimate] Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

Each subkey has its own fingerprint (not shown above) and hence long ID. In subsequent use, gpg2 will select the correct key for signing messages from her, decrypting messages for her, and certifying others' public keys imported into her keyring.

Some documentation uses "master key" instead of "primary key".

Alice sees long key IDs because she set option keyid-format to "long" in her configuration file. She can choose short IDs instead (the last 8 digits of the fingerprint) or omit IDs altogether. The gpg2 man page says more.

Outside of a test rig, Alice would likely identify her primary key with her full name possibly followed by her email address, something like "Alice Ecila <alice@example.net>". The command to create her primary key would then be this:

-> gpg2 --homedir alice --quick-generate-key "Alice Ecila <alice@example.net>" rsa cert never

She would likely add a passphrase to her secret keys, too, by adjusting her configuration file. And she might also choose to put expiration dates on her keys, as Bob elects to do.

See section "How to manage your keys" of the man page for details on commands --quick-generate-key and --quick-add-key. This section also documents other commands for editing existing keys, such as modifying the expiration date, password, or user ID.

GnuPG supports keys other than the default RSA with 2048 bits:

-> gpg2 --version
⋮
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
⋮

See the man page under command --quick-add-key to specify your preference. Bob uses ECC algorithms for his keys.

To see fingerprints for subkeys, too, add options --fingerprint and --with-subkey-fingerprint when listing keys. For example:

-> gpg2 --homedir alice --fingerprint --with-subkey-fingerprint --list-public-keys
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      Key fingerprint = 614E BCB1 12A5 9D09 5818  7230 4821 DF64 7F86 4C58
uid                 [ultimate] Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
      Key fingerprint = 67AC 40D6 09B6 EFFF E6F5  70C8 45D7 DFB6 0F00 9D55
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]
      Key fingerprint = A49B F44C 9122 B8CF 5F7B  8C6D D1CA 5592 A01F 8FF9

If you prefer a concise display, omit --fingerprint:

-> gpg2 --homedir alice --with-subkey-fingerprint --list-public-keys
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [ultimate] Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
      67AC40D609B6EFFFE6F570C845D7DFB60F009D55
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]
      A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9

You can add either or both of these options to gpg.conf.

To determine the fingerprint for a key-pair, whether primary or secondary, GnuPG/OpenPGP hashes the public component's core data along with some metadata about the key—all serialized according to specs. The hashing algorithm is SHA-1, yielding a fingerprint of 160 bits shown as 40 hexadecimal digits. The long key ID takes the lower-order 64 bits, and the short key ID takes the lower-order 32 bits. When the fingerprint is written in hex digits representing the bits from higher-order to lower-order, the right-most 16 digits represent the lower-order 64 bits; the right-most 8 digits represent the lower-order 32 bits. (A hex digit encodes 4 bits.) See also RFC 4880 §12.2, Key IDs and Fingerprints.

Alice's GnuPG directory now holds her public and private keys along with trust information:

-> ls -R alice
alice:
gpg.conf           private-keys-v1.d/  pubring.kbx~  trustdb.gpg
openpgp-revocs.d/  pubring.kbx         tofu.db

alice/openpgp-revocs.d:
614EBCB112A59D09581872304821DF647F864C58.rev

alice/private-keys-v1.d:
3F4911C26B34CC7722AF4B4889D4C2BB62969A96.key
B30172EEC9822655DAE7D603CD13F424943F82FD.key
D9F0BC0AB92B308C2C085451FBCE4D95C6FFECC7.key

File gpg.conf is the configuration file Alice added to the directory herself. File pubring.kbx holds her public keyring. Directory private-keys-v1.d holds each private key in a file identified by the key's keygrip, described below. Files trust.db and tofu.db are the trust databases for GnuPG's two key-validation models, Web of Trust and Trust on First Use (TOFU). Directory openpgp-revocs.d holds a pre-generated revocation certificate for the primary key, identified by its fingerprint. Alice directly edits her configuration file, and gpg2 creates and manages all the rest on her behalf.

Each key-pair also has its own keygrip, an ID represented by 40 hexadecimal digits. A keygrip is not the same as the key's fingerprint. When listing keys, add option --with-keygrip to see each keygrip; for example:

-> gpg2 --homedir alice --with-keygrip --list-public-keys
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
      Keygrip = 3F4911C26B34CC7722AF4B4889D4C2BB62969A96
uid                 [ultimate] Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
      Keygrip = B30172EEC9822655DAE7D603CD13F424943F82FD
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]
      Keygrip = D9F0BC0AB92B308C2C085451FBCE4D95C6FFECC7

Keygrip IDs are used to name the files storing private keys:

-> ls alice/private-keys-v1.d/
3F4911C26B34CC7722AF4B4889D4C2BB62969A96.key
B30172EEC9822655DAE7D603CD13F424943F82FD.key
D9F0BC0AB92B308C2C085451FBCE4D95C6FFECC7.key

The notion of a keygrip pertains to GnuPG; the term "keygrip" does not appear in RFC 4880 OpenPGP Message Format. The reference manual for GnuPG's Libgcrypt describes the keygrip as "the SHA-1 hash of the public key parameters expressed in a way depended [sic] on the algorithm", under function gcry_pk_get_keygrip. The exact definition comes down to the C code implementing the keygrip for a given key algorithm and may differ from algorithm to algorithm. So functionally, it's just an opaque key identifier of 40 hexadecimal digits. Its cryptographic provenance attests to its soundness but does not otherwise inform its use. (The man pages for gpg-agent and gpgsm also refer to keygrips.)

A subkey can also be assigned to authentication ("auth") with SSH. See also: Using GnuPG (2.1) for SSH authentication; How to use a GPG key for SSH authentication; man page under --export-ssh-key.

Alice Exports Her Public Keys

The next step for Alice is to package her public keys into a file she can share with anybody who wants to send her a private message or who wants to verify her signature, such as Bob. For this she uses --export to extract key "Alice" from her public keyring, and she adds option --output to direct the key data to a file of her choice:

-> gpg2 --homedir alice --output alice-pubkeys.gpg --export Alice
-> file -kr alice-pubkeys.gpg 
alice-pubkeys.gpg: GPG key public ring, created Tue Mar 13 15:16:20 2018
- data

The output file is binary inside. That's fine for an email attachment or for direct download. For sharing by copy-and-paste, additional option --armor encodes (not encrypts!) the binary data with a Base64 wrapper:

-> gpg2 --homedir alice --armor --output alice-pubkeys.asc --export Alice
-> file -kr alice-pubkeys.asc
alice-pubkeys.asc: PGP public key block Public-Key (old)
- , ASCII text
- data

So far, Alice's public keyring holds only her fledgling keys. Eventually, it will contain public keys for all of her communicants. Without that subsequent label "Alice", command --export would extract all of Alice's public keys. To be on the safe side, Alice verifies that she got only what she expected:

-> gpg2 alice-pubkeys.gpg
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
pub   rsa2048/4821DF647F864C58 F24625CA2253CE59 2018-03-13 [C]
      Key fingerprint = 614E BCB1 12A5 9D09 5818  7230 4821 DF64 7F86 4C58
uid                           Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

This check confirms that alice-pubkeys.gpg exports only the primary key labeled "Alice" along with its subkeys, as desired. Alice repeats this check for alice-pubkeys.asc.

Alternatively, Alice can use her long key ID to extract her public key:

-> gpg2 --homedir alice --output alice-pubkeys.gpg --export 4821DF647F864C58

The short ID 7F864C58 will do as well—the last eight digits (32 bits) of the fingerprint. Of course, the entire fingerprint does the job, too. (See section "HOW TO SPECIFY A USER ID" of the man page for details.)

If the warning about guessing your meaning annoys you, try this alternative:

-> gpg2 --import-options show-only --import alice-pubkeys.gpg 
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      Key fingerprint = 614E BCB1 12A5 9D09 5818  7230 4821 DF64 7F86 4C58
uid                            Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

gpg: Total number processed: 1

No keys are actually imported.

For extra detail about the keys in alice-pubkeys.gpg (or alice-pubkeys.asc), add option --verbose:

-> gpg2 --verbose alice-pubkeys.gpg 
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      Key fingerprint = 614E BCB1 12A5 9D09 5818  7230 4821 DF64 7F86 4C58
uid                           Alice
sig        4821DF647F864C58 2018-03-13   [selfsig]
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sig        4821DF647F864C58 2018-03-13   [keybind]
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]
sig        4821DF647F864C58 2018-03-13   [keybind]

This report shows that the subkeys are signed, or certified, by the self-signed primary key; that's what it means to be a subkey bound to a primary key.

Bob Creates And Exports His Keys

Bob follows Alice's lead in setting up his new keyrings—one primary key for certification, one subkey for encryption/decryption, and one subkey for signing. To spice things up, Bob uses algorithms from Elliptic Curve Cryptography, or ECC; he selects CV25519 for encryption/decryption and ED25519 for signing. He also decides to have his keys expire in one year because he prefers to refresh them annually.

-> gpg2 --homedir bob --quick-generate-key Bob ed25519 cert 1y
⋮
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      EDB5C5F0B26C71C7C862FEDE8400C46B86657827
uid                            Bob
-> gpg2 --homedir bob --quick-add-key EDB5C5F0B26C71C7C862FEDE8400C46B86657827 cv25519 encr 1y
-> gpg2 --homedir bob --quick-add-key EDB5C5F0B26C71C7C862FEDE8400C46B86657827 ed25519 sign 1y

Here is Bob's secret keyring, with signature information added via option --with-sig-check:

-> gpg2 --homedir bob --with-sig-check --list-secret-keys 
⋮
sec   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      EDB5C5F0B26C71C7C862FEDE8400C46B86657827
uid                 [ultimate] Bob
sig!3        8400C46B86657827 2018-03-16  Bob
ssb   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob
ssb   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob

gpg: 3 good signatures

To list his public keys with signatures, Bob can abbreviate with --check-sigs (or --check-signatures):

-> gpg2 --homedir bob --check-sigs
⋮
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      EDB5C5F0B26C71C7C862FEDE8400C46B86657827
uid                 [ultimate] Bob
sig!3        8400C46B86657827 2018-03-16  Bob
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob

gpg: 3 good signatures

Here, "sig!" labels a good signature. The "3" in "sig!3" indicates high confidence in this self-signature. See the man page under options --check-signatures and --default-cert-level for details.

Finally, Bob extracts his public keys into file bob-pubkeys.gpg and checks his work:

-> gpg2 --homedir bob --output bob-pubkeys.gpg --export Bob
-> gpg2 -v bob-pubkeys.gpg 
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      Key fingerprint = EDB5 C5F0 B26C 71C7 C862  FEDE 8400 C46B 8665 7827
uid                           Bob
sig        8400C46B86657827 2018-03-16   [selfsig]
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sig        8400C46B86657827 2018-03-16   [keybind]
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]
sig        8400C46B86657827 2018-03-16   [keybind]

During a snow-day on the third nor'easter of the month, Bob was curious about what hash gpg2 used for signing his keys. Eventually, he came up with this whopper:

-> gpg2 --homedir bob --export-options export-minimal --export bob | gpg --list-packets | grep digest
	digest algo 8, begin of digest 4d e9
	digest algo 8, begin of digest 7b ea
	digest algo 8, begin of digest fe d2
	subpkt 32 len 117 (signature: v4, class 0x19, algo 22, digest algo 8)

Consulting RFC 4880 §9.4, Constants, he learned that "algo 8" indicates SHA-256. He similarly confirmed that gpg2 uses version 4 of OpnePGP by piping into "grep version" instead.

Alice and Bob Exchange Public Keys

Since Alice and Bob wish to share private information through insecure channels, each must import the other's public keys. First, Alice gets file bob-pubkeys.gpg, and Bob gets file alice-pubkeys.asc (or alice-pubkeys.gpg). Because public-key files contain nothing to hide, Alice and Bob can exchange them however they wish—HTTP, FTP, email, USB stick, key server. Next, Alice imports bob-pubkeys.gpg into her public keyring, and Bob imports alice-pubkeys.asc into his.

Alice Imports Bob's Keys

Before Alice imports file bob-pubkeys.gpg, she first looks-up the primary fingerprint:

->-> gpg2 --homedir alice --fingerprint --import-options show-only --import bob-pubkeys.gpg
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      Key fingerprint = EDB5 C5F0 B26C 71C7 C862  FEDE 8400 C46B 8665 7827
uid                            Bob
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]

gpg: Total number processed: 1

She verifies this fingerprint against the fingerprint Bob has published on his TLS-certified website. She is satisfied she's got the genuine article, and so she imports Bob's keys for real:

-> gpg2 --homedir alice --import bob-pubkeys.gpg
gpg: key 8400C46B86657827: public key "Bob" imported
gpg: Total number processed: 1
gpg:               imported: 1

Alice's keyring now incorporates Bob's public key, but with unknown trust:

-> gpg2 --homedir alice --list-public-keys Bob
⋮
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      EDB5C5F0B26C71C7C862FEDE8400C46B86657827
uid              [ unknown] Bob
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]

To endorse her trust in Bob's public key, Alice certifies it (i.e., signs it) with her primary key (4821DF647F864C58):

-> gpg2 --homedir alice --lsign-key Bob
⋮
Are you sure that you want to sign this key with your
key "Alice" (4821DF647F864C58)
⋮

Alice's public keyring now holds trusted keys for Bob:

->  gpg2 --homedir alice --check-sigs Bob
⋮
pub   ed25519/8400C46B86657827 2018-03-16 [C] [expires: 2019-03-16]
      EDB5C5F0B26C71C7C862FEDE8400C46B86657827
uid                 [  full] Bob
sig!3        8400C46B86657827 2018-03-16  Bob
sig!  L      4821DF647F864C58 2018-03-16  Alice
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]
sig!         8400C46B86657827 2018-03-16  Bob
⋮

Alice finally has what she needs to encrypt messages to Bob and to verify Bob's signature. She's done with all of this keyring business (until she meets Carol, but that's another story).

Bob Imports Alice's Keys

Bob prefers calling Alice to verbally verify the fingerprint of her primary key, so he adds handy option --with-icao-spelling when reviewing alice-pubkeys.asc before importing:

-> gpg2 --homedir bob --with-icao-spelling alice-pubkeys.asc 
gpg: WARNING: no command supplied.  Trying to guess what you mean ...
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
                        "Six One Four Echo  Bravo Charlie Bravo One
                         One Two Alfa Five  Niner Delta Zero Niner
                         Five Eight One Eight  Seven Two Three Zero
                         Four Eight Two One  Delta Foxtrot Six Four
                         Seven Foxtrot Eight Six  Four Charlie Five Eight"
uid                           Alice
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

Option --with-icao-spelling tells gpg2 to spell out the fingerprint using the radiotelephony phonetic alphabet of the International Civil Aviation Organization. To facilitate verbal verification, Bob reads these names to Alice. For example, Bob reads "Bravo Charlie Bravo One" instead of "BCB1". Alice in turn crosses-off each name on her printed copy.

Bob is confident that he's got the real deal, so he imports alice-pubkeys.asc and certifies Alice's key:

-> gpg2 --homedir bob --quiet --import alice-pubkeys.asc
-> gpg2 --homedir bob --lsign-key Alice
⋮
Are you sure that you want to sign this key with your
key "Bob" (8400C46B86657827)
⋮

Now Bob's public keyring holds the desired keys:

-> gpg2 --homedir bob --check-sigs Alice
⋮
pub   rsa2048/4821DF647F864C58 2018-03-13 [C]
      614EBCB112A59D09581872304821DF647F864C58
uid                 [  full  ] Alice
sig!3        4821DF647F864C58 2018-03-13  Alice
sig!  L      8400C46B86657827 2018-03-16  Bob
sub   rsa2048/45D7DFB60F009D55 2018-03-13 [E]
sig!         4821DF647F864C58 2018-03-13  Alice
sub   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]
sig!         4821DF647F864C58 2018-03-13  Alice
⋮

At last, Bob is equipped to encrypt messages to Alice and to verify Alice's signature.

Alice Sends Bob a Signed File

Alice wants to send file hello.txt to Bob. She decides that there is nothing secret about the banal contents of file. Still, she does wish to reassure Bob that she herself composed it, that what he reads is what she wrote. So, she simply signs the file without encrypting it to produce a new file embedding both content and signature. Alice sends the latter file to Bob via any convenient channel. He then verifies the signature and extracts the original message.

Alice Signs Her File

Alice uses operation option --sign (-s) to sign hello.txt. She combines message and signature into file hello-signed.txt.gpg:

-> gpg2 --homedir alice --output hello-signed.txt.gpg --sign hello.txt
-> file hello-signed.txt.gpg
hello-signed.txt.gpg: data

Since signing uses one of Alice's private keys, she will be prompted to enter the passphrase of her private keyring. The output format is binary. Alice can use --armor for an ASCII wrapper.

-> gpg2 --homedir alice --armor --output hello-signed.txt.asc --sign hello.txt
-> file hello-signed.txt.asc 
hello-signed.txt.asc: PGP message Compressed Data (old)

Without direction from --output, gpg2 would put its results in hello.txt.gpg or hello.txt.asc. Because Alice did not elect encryption, anyone can read its contents using gpg2.

Next, Alice sends file hello.txt.gpg or hello.txt.asc to Bob as, say, an everyday email attachment. Or she may post these files to her public website.

There is nothing special about Bob in the example above. Alice's secret key drives her signature, and anyone holding her public key can subsequently verify that signature. In particular, the command for signing plaintext does not use option --recipient for or anyone.

Alice does not specify which of her three private keys should be used for signing because gpg2 automatically selects her signing key.

If Alice wants to check that her signing key is used, she can add option --verbose:

-> gpg2 --homedir alice --output hello-signed.txt.gpg --verbose --sign hello.txt
gpg: using pgp trust model
gpg: using subkey D1CA5592A01F8FF9 instead of primary key 4821DF647F864C58
gpg: writing to 'hello-signed.txt.gpg'
gpg: pinentry launched (3700 unknown 0.9.7 ? ? ?)
gpg: RSA/SHA256 signature from: "D1CA5592A01F8FF9 Alice"

-> gpg2 --homedir alice  --list-secret-keys
⋮
ssb   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

As desired, gpg2 selects Alice's bespoke signing key.

Although the content of hello.txt is embedded into hello-signed.txt.gpg as cleartext, it is not apparent to ordinary tools. For example:

-> strings hello-signed.txt.gpg 
('qf
d4fa`
SdY2
t2pq
M/wF
d/od
Ya~3
-> cat hello-signed.txt.gpg
unintelligible binary gibberish

The original content is there, however, but it is compressed by default:

 -> pgpdump hello-signed.txt.gpg 
Old: Compressed Data Packet(tag 8)
	Comp alg - ZIP <RFC1951>(comp 1)
⋮

It's this compression that superficially obscures the content. (As it turns out, however, less under Fedora can see through this fog and displays the cleartext content while ignoring the signature.)

Compression can be inhibited with option --compress-level. For example:

-> gpg2 --homedir alice --output hello-signed2.txt.gpg --compress-level 0 --sign hello.txt
-> strings hello-signed2.txt.gpg 
#b	hello.txtZ
peHello there, world.
L]t-q

GnuGP supports compression by ZIP, zlib, and bzip2 in accordance with OpenPGP. See also option --compress-algo in the man page.

Bob Verifies and Reads Alice's File

Bob receives Alice's email and saves the attached file hello.txt.gpg. He uses operation option --verify to check the signature and saves the message in file hello-msg.txt:

-> gpg2 --homedir bob --output hello-msg.txt --verify hello-signed.txt.gpg 
gpg: Signature made Mon 19 Mar 2018 01:22:22 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]
-> cat hello-msg.txt 
Hello there, world.

When Bob wants just to read the message without saving it, he uses a hyphen after --option in place of an actual filename:

-> gpg2 --homedir bob --output - --verify hello-signed.txt.gpg 
Hello there, world.
gpg: Signature made Mon 19 Mar 2018 01:22:22 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

If Bob omits option --output altogether, gpg2 does not save or display the embedded message:

-> gpg2 --homedir bob --verify hello-signed.txt.gpg 
gpg: Signature made Mon 19 Mar 2018 01:22:22 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

Bob does not tell gpg2 what key to use because hello-signed.txt.gpg embeds the key ID, which gpg2 then looks-up in his public keyring. Since verification does not use his private keyring, there is no passphrase for him to enter.

By adding option --verbose, Bob can see what message digest Alice used and the name of the original file:

-> gpg2 --homedir bob --verbose --verify hello-signed.txt.gpg 
gpg: original file name='hello.txt'
⋮
gpg: binary signature, digest algorithm SHA256, key algorithm rsa2048

So Alice uses SHA-256 for signing; she's hip.

Carol Reads Alice's File, Too

Carol notices file hello-signed.txt.asc on Alice's website and decides to have a look:

-> gpg2 --homedir carol --output - --verify hello-signed.txt.asc 
Hello there, world.
gpg: Signature made Mon 19 Mar 2018 01:22:22 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Can't check signature: No public key

Carol can read Alice's message embedded in hello-signed.txt.asc as cleartext. She has not yet imported Alice's public keys, however, so gpg2 cannot check the signature—whether valid or not.

Alice Sends Bob a File with a Detached Signature

Alice dislikes combining her message and its signature into a common file. Instead, she signs the file and puts only its signature into a new file. She then sends both the unaltered message and its detached signature to Bob in separate files. He verifies the signature file against the message file before reading her message.

Alice Signs Her File

Alice signs hello.txt and puts its signature in a separate file by using operation option --detach-sign (-b) instead of --sign:

-> gpg2 --homedir alice --detach-sign hello.txt
-> file hello.txt.sig 
hello.txt.sig: data

She will be prompted to enter the passphrase of her private keyring. The default output file for the signature is hello.txt.sig, a binary file. If Alice were to add option --armor for ASCII-armored data, the default output file would become hello.txt.asc instead:

-> gpg2 --homedir alice --armor --detach-sign hello.txt
-> file hello.txt.asc 
hello.txt.asc: PGP signature Signature (old)

In either case, Alice can use option --output to overrule the default in favor of a filename more to her tastes.

Next, Alice sends both files hello.txt and hello.txt.sig to Bob as, say, everyday email attachments. Or she may post them to her public website.

As with --sign, above, Alice does not specify which of her three private keys should be used for signing because gpg2 automatically selects her signing key. She can add option --verbose if she would like to see the key and digest used.

Out of curiosity, Alice taps pgpdump (eponymous package) to confirm the key and hash algorithm that gpg2 used for signing:

-> pgpdump hello.txt.sig 
Old: Signature Packet(tag 2)(307 bytes)
	Ver 4 - new
	Sig type - Signature of a binary document(0x00).
	Pub alg - RSA Encrypt or Sign(pub 1)
        Hash alg - SHA256(hash 8)
	Hashed Sub: issuer fingerprint(sub 33)(21 bytes)
	v4 -	Fingerprint - a4 9b f4 4c 91 22 b8 cf 5f 7b 8c 6d d1 ca 55 92 a0 1f 8f f9
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Mon Mar 19 11:06:28 EDT 2018
	Sub: issuer key ID(sub 16)(8 bytes)
	Key ID - 0xD1CA5592A01F8FF9
	Hash left 2 bytes - 0e 2a 
	RSA m^d mod n(2048 bits) - ...
		-> PKCS-1

So Alice sees that gpg2 used SHA-256 with her signing key:

-> gpg2 --homedir alice --list-secret-keys
⋮
ssb   rsa2048/D1CA5592A01F8FF9 2018-03-13 [S]

Alternatively, Alice can use --list-packets with gpg2:

-> gpg2 --homedir alice --list-packets hello.txt.sig 
# off=0 ctb=89 tag=2 hlen=3 plen=307
:signature packet: algo 1, keyid D1CA5592A01F8FF9
	version 4, created 1521471988, md5len 0, sigclass 0x00
        digest algo 8, begin of digest 0e 2a
        hashed subpkt 33 len 21 (issuer fpr v4 A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9)
	hashed subpkt 2 len 4 (sig created 2018-03-19)
	subpkt 16 len 8 (issuer key ID D1CA5592A01F8FF9)
	data: [2048 bits]

She learns from RFC 4880 §9.4, Constants, that "algo 8" denotes SHA-256.

Bob Verifies Alice's Signature and Reads Her File

Bob receives Alice's email and saves the attachments, retaining filenames hello.txt and hello.txt.sig. He verifies the signature as reassurance that Alice composed hello.txt:

-> gpg2 --homedir bob --verify hello.txt.sig hello.txt
gpg: Signature made Mon 19 Mar 2018 11:06:28 AM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

Bob does not tell gpg2 what key to use because hello.txt.sig embeds the key ID, which gpg2 then looks-up in his public keyring. Since verification does not use his private keyring, there is no passphrase for him to enter.

Bob can confidently read hello.txt in his favorite text viewer.

Carol Reads Alice's File, Too

Carol notices cleartext file hello.txt on Alice's website and opens it in her favorite text editor. Since she has not yet imported Alice's public keys, Carol cannot check the companion signature:

-> gpg2 --homedir carol --verify hello.txt.sig hello.txt
gpg: Signature made Mon 19 Mar 2018 11:06:28 AM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Can't check signature: No public key

Alice Posts Signed Cleartext

From time to time, Alice likes to sign short posts to a forum she participates in. She posts the content itself as cleartext and appends the signature in ASCII armor. Her posts look like this:

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

Hello there, world.
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEEpJv0TJEiuM9fe4xt0cpVkqAfj/kFAlqyr5UACgkQ0cpVkqAf
j/mWnAf/Tjc4HxDlAcptzgjaTbChcrtlSPgbnrLdLnWDd8IIKgewQD7l12zooP6B
n+v17CULJXMSgBZfUtBggLhGja/+s8KAwwWtqrr9IWPtScp4fMd/KsxUTo9xptxm
wQUaBjQR7dnxXYNVEhOPId1nGrk41mhHhtVSXFjq4of+AK5HhARn0r7HEnQ9MzFa
G7kih/RvwZh03MtfHRGL50vdIvukOnh67XRsSCKmvR3Rufo1KemZDUsKOEOnyEJq
GoDR1bFrupHp/0LsxFQ0isubwJcbZTUrbDvCjHYAvfWiEj0B/hCYZ8fglbfq7Zif
21laiptPQjbUOMqzejGQgRptK6rlpw==
=L0YS
-----END PGP SIGNATURE-----

Forum readers can view her message in the viewer of their choice. Anyone wishing to confirm that the message comes from Alice can use the signature to verify it.

Alice Signs Her Message

Alice uses operation option --clear-sign (or --clearsign) to sign her message. Here gpg2 reads Alice's message from standard input and writes the message and its signature to standard output:

-> gpg2 --homedir alice --clear-sign 
¡Hola, mundo!   Alice enters this.
Ctl-d           Alice enters this.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

¡Hola, mundo!
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEEpJv0TJEiuM9fe4xt0cpVkqAfj/kFAlqyln4ACgkQ0cpVkqAf
j/m9Qgf+KJhdIDF4wsLZFhLgSeGVBUDhh06/iOrojQ+O75k6eIhmW4b5cuikzJwM
gxZ7wp8oL4KS3YFVzLZJWxmY+4cRu+kVZdY+HspDMY0WLIxtljHE66MEDACmOgFT
D2h3X3ag+L3n5TWEZ8GAhenYjdo4BE+DKh4mku7MmXaFR1y0vvkEZ1pGjjyi+6hR
QesqhMfnvyvSS1KL9LNPqlcfjEwLiI1bhZ9sIfOW7+U2f7bkEXZub9gg0jX5f1of
1Nf7TWmjcxLLPBfnomaS5eycK5ANwBhtskOPFZfCphg2DXE5gbrtLDqaY5dTWj4l
yPY0u55ARsjqzRyNN/AWGT8h0dcpDg==
=cAug
-----END PGP SIGNATURE-----

Alice simply copies the output and pastes it into her blog, grabbing everything from "-----BEGIN PGP SIGNED MESSAGE-----" through "-----END PGP SIGNATURE-----", inclusively.

But usually Alice prefers to write her message using her favorite text editor and then save her missive to a file. She then signs the file:

-> gpg2 --homedir alice --output - --clear-sign hello.txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Hello there, world.
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEEpJv0TJEiuM9fe4xt0cpVkqAfj/kFAlqynS8ACgkQ0cpVkqAf
⋮
=FVbj
-----END PGP SIGNATURE-----

The hyphen following option --output tells gpg2 to write its results to standard output. If she likes, Alice can supply a filename instead. Absent --output, gpg2 would have used file hello.txt.asc. As usual, gpg2 seeks permission to overwrite an existing file.

Bob Reads and Verifies Alice's Post

Bob reads Alice's post and decides he'd like to verify its provenance. He copies everything from "-----BEGIN PGP SIGNED MESSAGE-----" through "-----END PGP SIGNATURE-----", inclusively. He uses operation option --verify without a filename argument. At the prompt, he pastes in what he just copied and finishes with Ctl-d:

-> gpg2 --homedir bob --verify 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

¡Hola, mundo!
-----BEGIN PGP SIGNATURE-----

iQEzBAEBCAAdFiEEpJv0TJEiuM9fe4xt0cpVkqAfj/kFAlqyln4ACgkQ0cpVkqAf
⋮
=cAug
-----END PGP SIGNATURE-----
Ctl-d
gpg: Signature made Wed 21 Mar 2018 01:29:34 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

If he prefers, Bob can paste the cleartext and signature into a file, say msg-sig.txt. He then names the file as an argument to --verify:

-> gpg2 --homedir bob --verify msg-sig.txt 
gpg: Signature made Wed 21 Mar 2018 01:29:34 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

Bob does not tell gpg2 what key to use because the signature embeds the key ID, which gpg2 then looks-up in his public keyring. Since verification does not use his private keyring, there is no passphrase for him to enter.

With option --output, the message can be saved into a separate file after verification:

-> gpg2 --homedir bob --output msg.txt --verify msg-sig.txt 
gpg: Signature made Wed 21 Mar 2018 01:29:34 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]
-> cat msg.txt 
¡Hola, mundo!

Only the delimited message is verified. Any extraneous content outside of the delimited data is ignored. For example:

-> echo "Extraneous stuff" > tinker
-> cat tinker msg-sig.txt tinker > tinker2
-> gpg2 --homedir bob --verify tinker2
gpg: Signature made Wed 21 Mar 2018 01:29:34 PM EDT
gpg:                using RSA key A49BF44C9122B8CF5F7B8C6DD1CA5592A01F8FF9
gpg: Good signature from "Alice" [full]

Alice Sends Bob an Encrypted File

When Alice wants to send a private file to Bob, she encrypts the file using his public key. Then Alice and Bob can safely transfer the encrypted file through whatever means they find convenient. Bob subsequently uses his private key to decrypt the file.

Alice Encrypts a File for Bob

To encrypt file hello.txt just for Bob, Alice uses operation option --encrypt (-e) with his public key specified by option --recipient (-r):

-> gpg2 --homedir alice --output hello-bob.txt.gpg --recipient Bob --encrypt hello.txt
-> file hello-bob.txt.gpg 
hello-bob.txt.gpg: data

Since the output file hello-bob.txt.gpg is binary inside, Alice mails it to Bob as an attachment. When Alice prefers copy-and-paste, she adds option --armor:

-> gpg2 --homedir alice --armor --output hello-bob.txt.asc --recipient Bob --encrypt hello.txt
-> file hello-bob.txt.asc 
hello-bob.txt.asc: PGP message Public-Key Encrypted Session Key (old)

In either case, the source file hello.txt is left intact—as plaintext. By convention, the output file gets an additional extension to indicate encryption, either gpg for binary ciphertext or asc for ASCII (Base64) ciphertext. If Alice had not included option --output to name the encrypted file, gpg2 would have used hello.txt.gpg or hello.txt.asc.

When Alice wants to encrypt multiple files to Bob, she can substitute --encrypt-files for --encrypt and list the plaintext files. For example:

-> gpg2 --homedir alice --recipient Bob --encrypt-files hello.txt howdy.txt 

She must accept the default names for the output files; option --output won't work with multi-file encryption. Here she gets hello.txt.gpg and howdy.txt.gpg, unless these files already exist.

If Alice wants to send the same encrypted file to multiple people, she would specify their public keys in a separate --recipient option for each when encrypting the file. Like this:

-> gpg2 --homedir alice --output hello-bob-carol.txt.gpg --recipient Bob --recipient Carol --encrypt hello.txt

Her public keyring must have an encryption key for each recipient.

If Alice wants decryption capability to file hello-bob.txt.gpg (or hello-bob.txt.asc) for herself, she must simultaneously encrypt the plaintext file for herself, too.

If the output file exists, howsoever it is specified, gpg2 will not overwrite it without Alice's permission:

-> gpg2 --homedir alice --recipient Bob --encrypt hello.txt
File 'hello.txt.gpg' exists. Overwrite? (y/N) n
Enter new filename: howdy.txt.gpg

Option --yes gives that permission, out of the gate:

-> gpg2 --homedir alice --recipient Bob --yes --encrypt hello.txt
-> # Nothing to see here.

Although Alice's public keyring holds three keys for Bob (one primary plus two secondary), Alice does not need to specify which of these to use. Instead, gpg2 chooses the correct key based on the recipient, Bob, and the operation, encryption, together. Adding option --verbose shows the key ID:

-> gpg2 --homedir alice --verbose --recipient Bob --encrypt hello.txt
gpg: using pgp trust model
gpg: using subkey 26F6803D1D3E2E41 instead of primary key 8400C46B86657827
gpg: This key probably belongs to the named user
gpg: reading from 'hello.txt'
gpg: writing to 'hello.txt.gpg'
gpg: ECDH/AES256 encrypted for: "26F6803D1D3E2E41 Bob"

This key (26F6803D1D3E2E41) is Bob's encryption subkey, as expected:

-> gpg2 --homedir alice --list-public-keys Bob
⋮
sub   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
sub   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]

Alternatively, Alice can check what keys encrypted file hello-bob.txt.gpg uses this way:

-> gpg2 --homedir alice --list-only --decrypt hello-bob.txt.gpg
gpg: encrypted with 256-bit ECDH key, ID 26F6803D1D3E2E41, created 2018-03-16
      "Bob"

Here, no decryption is attempted, by dint of option --list-only.

Bob Decrypts the File from Alice

When Bob receives the email from Alice, he saves the attached file and then decrypts it using operation option --decrypt (-d):

-> gpg2 --homedir bob --decrypt hello-bob.txt.gpg 
gpg: encrypted with 256-bit ECDH key, ID 26F6803D1D3E2E41, created 2018-03-16
      "Bob"
Hello there, world.

He will be prompted to enter the passphrase of his private keyring. Then gpg2 reports the long ID and the user ID of the encryption key, followed by the plaintext message itself.

Although Bob's private keyring has three secret keys (one primary plus two secondary), he does not specify which of these to use. Instead, gpg2 chooses the encryption key that is the partner to this public key:

-> gpg2 --homedir bob --list-secret-keys
⋮
ssb   cv25519/26F6803D1D3E2E41 2018-03-16 [E] [expires: 2019-03-16]
ssb   ed25519/A10FF1BD3B1CE182 2018-03-16 [S] [expires: 2019-03-16]

Should Bob tire of seeing what key was used to decrypt his message, he can simply add option --quiet:

-> gpg2 --homedir bob --quiet --decrypt hello-bob.txt.gpg 
Hello there, world.

Typically, Bob would save the decrypted message into its own file, say hello-alice.txt:

-> gpg2 --homedir bob --output hello-alice.txt --quiet --decrypt hello-bob.txt.gpg 
-> cat hello-alice.txt 
Hello there, world.

When the requested output file exists, gpg2 asks for permission to overwrite it.

-> gpg2 --homedir bob --output hello-alice.txt --quiet --decrypt hello-bob.txt.gpg 
File 'hello-alice.txt' exists. Overwrite? (y/N) n
Enter new filename: howdy-alice.txt

Option --yes gives Bob's blessing upfront to overwrite the existing file:

-> gpg2 --homedir bob --output hello-alice.txt --quiet --yes --decrypt hello-bob.txt.gpg
-> # Nothing to see here.

When Bob has multiple files to decrypt, he can substitute --decrypt-files for --decrypt and list the encrypted files. For example:

-> gpg2 --homedir bob --quiet --decrypt-files hello-alice.txt.gpg howdy-carol.txt.gpg

The encrypted files need not come from the same person. What counts is that each is encrypted to Bob's public key. He must accept the default names for the output files; option --output won't work with multi-file decryption. Here he gets hello-alice.txt and howdy-carol.txt, unless these files already exist.

Because Alice encrypted her file with Bob as the sole recipient, only he can decrypt hello-bob.txt.gpg. Even she is out of luck:

-> gpg2 --homedir alice --quiet --decrypt hello-bob.txt.gpg 
gpg: decryption failed: No secret key

Decryption fails because Alice's private keyring does not have a key corresponding to the public key (26F6803D1D3E2E41) used for encryption. Of course not: That secret key belongs to Bob, exclusively.

Alice Encrypts a File for Herself

When Alice would like to encrypt a file for herself, she has the choice of symmetric-key encryption or public-key encryption (aka asymmetric-key encryption). For symmetric-key encryption, she must specify a passphrase. For public-key encryption, she names her own public key as the recipient. She does not give a passphrase, but she is implicitly using the passphrase for her private keyring nonetheless. To later decrypt her file in either case, she must supply the underlying passphrase—the explicit passphrase of symmetric-key encryption or the implicit passphrases of public-key encryption.

What's the difference? For symmetric-key encryption, Alice must always enter a passphrase. But she can use a different passphrase for different files or groups of files, if that makes sense to her. When it's time to decrypt, she needs only that passphrase. In particular, there is no private keyring to worry about. For public-key encryption, she does not have to bother entering a passphrase. All files encrypted this way essentially share the passphrase of her private keyring, which she may find convenient. When it's time to decrypt, however, she must additionally have her private keyring handy. She may find that inconvenient; perhaps she prefers not to copy this keyring to her mobile devices, for instance.

Alice can resolve her dilemma by using both symmetric-key and public-key encryption simultaneously.

Alice Uses Symmetric-Key Encryption

Alice uses operation option --symmetric (-c) to encrypt hello.txt into hello.txt.gpg. She will be prompted to enter the passphrase that gpg2 will use to generate the encryption key:

-> gpg2 --homedir alice --symmetric hello.txt
-> file hello.txt.gpg 
hello.txt.gpg: GPG symmetrically encrypted data (AES cipher)

Since gpg2 leaves the source plaintext file intact, Alice deletes hello.txt. She's careful to also delete an intermediate backup that her editor creates:

-> rm -i hello.txt hello.txt~
rm: remove regular file 'hello.txt'? y
rm: remove regular file 'hello.txt~'? y

Alice subsequently uses operation option --decrypt to recover the plaintext hidden in hello.txt.gpg and save it to hello.txt:

-> gpg2 --homedir alice --output hello.txt --decrypt hello.txt.gpg 
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
-> cat hello.txt
Hello there, world.

Alternatively, she can drop option --output to have the contents written to standard output:

-> gpg2 --homedir alice --quiet --decrypt hello.txt.gpg
Hello there, world.

Before actual decryption, she will be prompted to enter the passphrase, that she used for encryption, above.

Alice Uses Public-Key Encryption

For public-key encryption of file hello.txt, Alice uses operation option --encrypt and names her own public key for option --recipient:

-> gpg2 --homedir alice --encrypt --recipient Alice hello.txt
-> file hello.txt.gpg 
hello.txt.gpg: PGP RSA encrypted session key - keyid: B6DFD745 559D000F RSA (Encrypt or Sign) 2048b .

There is no passphrase for public-key encryption. As above for symmetric-key encryption, Alice is careful to delete source file hello.txt and any intermediate copies her editor created.

Alice later uses operation option --decrypt to reveal the plaintext hidden in hello.txt.gpg. When prompted, she must give the passphrase for her private keyring:

-> gpg2 --homedir alice --decrypt hello.txt.gpg 
gpg: encrypted with 2048-bit RSA key, ID 45D7DFB60F009D55, created 2018-03-13
      "Alice"
Hello there, world.

Or, Alice can instead use --output to save the plaintext to a file of her choosing.

Alice Uses Both Symmetric-Key and Public-Key Encryption

Alice can easily combine both types of encryption in one tidy command:

-> gpg2 --homedir alice --symmetric --encrypt --recipient Alice hello.txt

She will be prompted to enter a passphrase for the symmetric-key encryption; there is no key for the public-key encryption.

She again uses operation option --decrypt to later access her secret. When gpg2 can locate Alice's private keyring, here via --homedir, it prompts her for its passphrase:

-> gpg2 --homedir alice --quiet --decrypt hello.txt.gpg 
Hello there, world.

When gpg2 cannot locate Alice's private keyring, here in the absence of --homedir, it prompts her for the passphrase of symmetric-key encryption:

-> gpg2 --quiet --decrypt hello.txt.gpg 
Hello there, world.

Alice Discovers Session Keys

When Alice uses public-key encryption, her message is ultimately encrypted with a symmetric cipher using a session key generated randomly on her behalf. That session key is itself encrypted using her public key. Option --show-session-key reveals the session key when decrypting a file:

-> gpg2 --homedir alice --yes --encrypt --recipient Alice hello.txt
-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg
gpg: encrypted with 2048-bit RSA key, ID 45D7DFB60F009D55, created 2018-03-13
      "Alice"
gpg: session key: '9:3FD84FB5545CE7A19269F4108B356613786B6EA3A93C5D774D3F5BBFE9E62D1B'
Hello there, world.

The number 9 before the colon identifies the symmetric cipher, which here indicates AES with a 256-bit key. (See RFC 4880 §9.2, Symmetric-Key Algorithms.) The string of 64 hexadecimal digits after the colon represents the AES key (64 × 4 bits = 256 bits). When Alice wants to decrypt her file, gpg2 first prompts for the passphrase protecting her secret keyring and subsequently uses her private key to decrypt the session key. Then gpg2 uses the session key to decrypt the message via AES.

Even when Alice lists multiple recipients, her message is encrypted just once with a symmetric cipher. But now the random session key is repeatedly encrypted by each recipient's public key. For example:

-> gpg2 --homedir alice --yes --encrypt --recipient Alice --recipient Bob hello.txt
-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg
gpg: encrypted with 256-bit ECDH key, ID 26F6803D1D3E2E41, created 2018-03-16
      "Bob"
gpg: encrypted with 2048-bit RSA key, ID 45D7DFB60F009D55, created 2018-03-13
      "Alice"
gpg: session key: '9:11EB130965028844E6D2A94295D79FBD714E5C79E8B124584254FB860BC283EE'
Hello there, world.
-> gpg2 --homedir bob --quiet --show-session-key --decrypt hello.txt.gpg
gpg: session key: '9:11EB130965028844E6D2A94295D79FBD714E5C79E8B124584254FB860BC283EE'
Hello there, world.

When Alice wants to decrypt this file, gpg2 uses her private key to decrypt the session key, after prompting for her keyring's passphrase. When Bob wants to decrypt his copy of the file, gpg2 happily uses his private key instead. Either way, it recovers the same session key and proceeds to decrypt the message.

And when Alice combines symmetric-key and public-key encryption, her message is still encrypted just once with a symmetric cipher. But now the session key is encrypted twice, once with her public key and once via a bespoke passphrase. To decrypt the file, gpg2 recovers the session key using either the private key or the passphrase. For example:

-> gpg2 --homedir alice --yes --symmetric --encrypt --recipient Alice hello.txt

If gpg2 can find her private keyring, it prompts for the keyring's passphrase to proceed:

-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg 
gpg: encrypted with 1 passphrase
gpg: encrypted with 2048-bit RSA key, ID 45D7DFB60F009D55, created 2018-03-13
      "Alice"
gpg: session key: '9:E0AA77FF33FC0A28096A84E4A9F5D22F07DB5BF37DFC46E6BD8B1C4EA1B7E1B0'
Hello there, world.

If gpg2 cannot find the keyring, it asks for the bespoke passphrase instead:

-> gpg2 --show-session-key --decrypt hello.txt.gpg 
gpg: AES encrypted session key
gpg: encrypted with 1 passphrase
gpg: encrypted with RSA key, ID 45D7DFB60F009D55
gpg: session key: '9:E0AA77FF33FC0A28096A84E4A9F5D22F07DB5BF37DFC46E6BD8B1C4EA1B7E1B0'
Hello there, world.

For this case combining public-key and symmetric-key encryption, gpg2 uses AES-128 to additionally encrypt the common AES-256 session key. This second encryption requires its own key, of 128 bits, that gpg2 derives on demand using the bespoke passphrase.

The story changes when Alice uses only symmetric-key encryption:

-> gpg2 --homedir alice --yes --symmetric hello.txt
-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg 
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
gpg: session key: '7:3F27C6C5E69FE953279D452A3E32C9C8'
Hello there, world.

The prefix "7:" identifies the symmetric cipher as AES with a 128-bit key, given by the following 32 quartets [§9.2]. In this case, gpg2 derives the session key on demand whether it's encrypting or decrypting the message by prompting for the passphrase.

For symmetric encryption, alone or in combination with public-key encryption, gpg2 derives the session key for the AES-128 layer from the bespoke passphrase by means of a string-to-key ("s2k") transformation. OpenPGP uses Iterated and Salted S2K.

The salt is an 8-byte random number generated for each message and saved in the output file as cleartext. It's not a secret; it cannot be secret because it's needed to recover the session key.

Iterated and Salted S2K works by hashing a long string that repeats the salt and passphrase; it's this salt-passphrase pair that gets iterated. The hash bits then form the key bits. Essentially, the salt selects a cryptographic hash from a parametric family based on SHA-1 and indexed over 264 integers. That transformation then determines the key from the passphrase. It's important that the hash is selected at random; hence the salt is randomly generated. But there's no need to hide this choice—in accordance with the cryptography tenet that obscurity is not security for algorithms.

The default algorithms AES-128 and SHA-1 can be replaced with options --s2k-cipher-algo and --s2k-digest-algo, respectively.

Revealing a session key on a multi-user system potentially compromises the privacy of the encrypted message. See entry for option --override-session-key in the man page.

Just for kicks, Alice decrypts her file directly with the session key, which she provides in option --override-session-key. For the preceding example:

-> gpg2 --homedir alice --quiet --override-session-key 9:E0AA7⋯E1B0 --decrypt hello.txt.gpg
Hello there, world.

Because gpg2 has the key in cleartext here, it does not ask for a passphrase. (She types the entire key in the command line, not the cosmetic abbreviation above.)

Should an entitled authority compel Alice to provide access to a file encrypted with her public key, she can surrender the file's session key rather than her private key. The authority can then use the session key to decrypt the message independently and thereby without fear of being duped. Since Alice's private key remains secret, and since the session key is unique to the seized file, all other files encrypted with her public key remain protected. The man-page entry for option --show-session-key describes two forensic scenarios calling for the session key.

GnuPG option --show-session-key prepends a cipher identifier to the actual session key used with encryption, and option --override-session-key requires the same string to decrypt a message. In OpenPGP, "session key" refers solely to the actual key, however; there is no prefix.

Alice Goes into Rabbit Holes

Just because Alice cannot resist poking into rabbit holes when she wonders about how stuff works, she takes a closer look at the structure of her encrypted files. Her guidebook on this adventure is RFC 4880 OpenPGP Message Format, which she frequently consults to interpret what she finds.

Alice uses pgpdump to get a first look at what's inside hello.txt.gpg:

-> pgpdump hello.txt.gpg 
Old: Public-Key Encrypted Session Key Packet(tag 1)(268 bytes)
	New version(3)
	Key ID - 0x45D7DFB60F009D55
	Pub alg - RSA Encrypt or Sign(pub 1)
	RSA m^e mod n(2047 bits) - ...
		-> m = sym alg(1 byte) + checksum(2 bytes) + PKCS-1 block type 02
New: Symmetrically Encrypted and MDC Packet(tag 18)(86 bytes)
	Ver 1
	Encrypted data [sym alg is specified in pub-key encrypted session key]
		(plain text + MDC SHA1(20 bytes))

She sees that her file consists of two packets classified by tags 1 and 18. (Here "Old" and "New" denote different internal versions that OpenPGP uses to encode a packet's type and length.)

The first packet [§5.1] identifies the symmetric cipher and contains the session key together used to encrypt the secret message. These are themselves encrypted with Alice's public key. Because pgpdump does not decrypt any data, it cannot extract the cipher's name or the session key. This packet also includes cleartext information about Alice's public key, namely its ID and algorithm, which gpg2 will need when it comes time to decrypt the session key for Alice. The key ID indicates Alice's encryption/decryption key.

The second packet [§5.13] contains Alice's message encrypted with the symmetric cipher and session key from the first packet. It additionally encrypts the SHA-1 hash of the plaintext message, which serves as Modification Detection Code (MDC). Lacking decryption know-how, pgpdump can't say more about this packet.

pgpdump confines itself to examining the packet sequence of an OpenPGP file. It is neither interested in nor privy to the contents of those packets. As a corollary, Alice does not learn anything more from using pgpdump on a file encrypted to her public key than Bob, say, can also learn about the same file.

Alice can also use gpgsplit (package gnupg), which extracts each packet into a separate file:

-> gpgsplit --verbose --prefix hi- hello.txt.gpg 
gpgsplit: writing `hi-000001-001.pk_enc'
gpgsplit: writing `hi-000002-018.encrypted_mdc'
-> wc --bytes hi-000001-001.pk_enc hi-000002-018.encrypted_mdc
271 hi-000001-001.pk_enc
 88 hi-000002-018.encrypted_mdc
359 total
-> wc --bytes hello.txt.gpg 
 359 hello.txt.gpg

So gpgsplit also finds two packets with tags 1 and 18. It reports each packet's ordinal and tag in the filename and an abbreviated description in the extension. The sum of the sizes in bytes of the split-out files equals the size of the parent file, as would be expected. gpgsplit is of like mind to pgpdump in that it confines itself to a superficial listing of the packet sequence without probing the packets' contents.

As it turns out, Alice discovers there's more to the story when she tries command option --list-packets with gpg2: [§4]:

-> gpg2 --homedir alice --quiet --list-packets hello.txt.gpg 
# off=0 ctb=85 tag=1 hlen=3 plen=268
:pubkey enc packet: version 3, algo 1, keyid 45D7DFB60F009D55
	data: [2048 bits]
# off=271 ctb=d2 tag=18 hlen=2 plen=86 new-ctb
:encrypted data packet:
	length: 86
	mdc_method: 2
# off=292 ctb=a3 tag=8 hlen=1 plen=0 indeterminate
:compressed packet: algo=2
# off=294 ctb=ac tag=11 hlen=2 plen=35
:literal data packet:
	mode b (62), created 1523321471, name="hello.txt",
	raw data: 20 bytes

Here gpg2 uses Alice's private key to decrypt hello.txt.gpg, after prompting for her passphrase. Decryption reveals that the second packet itself contains two other packets with tags 8 and 11. From RFC 4880, she finds that these tags denote a "Compressed Data Packet (Tag 8)" and a "Literal Data Packet (Tag 11)" [§5].

In addition to what pgpdump reported about the first and second packets, Alice learns their header lengths (hlen) and their positions in the file as byte offsets (off). For the first packet, "algo 1" indicates RSA for the encryption algorithm [§9.1]. For the second packet, "mdc_method: 2" indicates SHA-1 for the MDC hash [§9.4]. (See separate note below for ctb and new-ctb.)

The third packet [§5.6] puzzles Alice because it is empty beyond its 1-byte header. She solders on all the same. The header identifies the compression algorithm that would be used if the packet had any data to compress; "algo 2" indicates ZLIB [§9.3].

The fourth packet contains some meta-information about the source data encrypted in the third packet. Here, name identifies the file Alice encrypted. When gpg2 encrypts data provided by redirection or a pipe, name will be empty. Mode "b", for 0x62, tells gpg2 to interpret the packet's data as binary and not to fret about end-of-line bytes. The Unix epoch (created) records the packet's timestamp. [§5.9]

Alice can also use --list-packets to examine the files written by gpgsplit. Since the first packet (hi-000001-001.pk_en) identifies her private key, gpg2 prompts for the passphrase. Since the second packet (hi-000002-018.encrypted_mdc) holds no information about the cipher and session key, Alice needs to provide these:

-> gpg2 --homedir alice --override-session-key 9:3FD8⋯2D1B --list-packets hi-000002-018.encrypted_mdc 
# off=0 ctb=d2 tag=18 hlen=2 plen=86 new-ctb
:encrypted data packet:
	length: 86
	mdc_method: 2
# off=21 ctb=a3 tag=8 hlen=1 plen=0 indeterminate
:compressed packet: algo=2
# off=23 ctb=ac tag=11 hlen=2 plen=35
:literal data packet:
	mode b (62), created 1523730877, name="hello.txt",
	raw data: 20 bytes

Each OpenPGP packet begins with a header encoding the type and length of the packet payload.

Both gpg2 and pgpdump show the type ID labeled "tag". gpgsplit embeds the ID in filenames.

gpg2 uses "hlen" and "plen" to label the lengths of the header and payload, respectively. pgpdump reports only the payload length. The size in bytes of a file created by gpgsplit is the sum of the header and payload lengths.

OpenPGP refers to the first octet of a header as the "packet tag" [§4.2]. gpg2 shows this octet as two hex digits labeled "ctb" for "CTB", which abbreviates "Cipher Type Byte". The latter term was used in defunct RFC 1991, PGP Message Exchange Formats (see §4.1, Packet structure fields). RFC 4880 obsoletes RFC 1991.

There is an old format and a new format for the header encoding, and these formats may be intermixed within a file. gpg2 indicates the new format with "new-ctb" but does not explicitly label the old format. pgpdump explicitly states "Old" or "New". gpgsplit is mum on this point.

When Alice combines symmetric-key and public-key encryption, her message is ultimately encrypted with a symmetric cipher using a randomly-generated session key, as it was when she used only public-key encryption (as above). But now that session key is encrypted twice, once via a passphrase and once via her public key. This dual encryption of the actual session key makes combined encryption possible. For example:

-> gpg2 --homedir alice --yes --symmetric --encrypt --recipient Alice hello.txt
-> gpg2 --show-session-key --decrypt hello.txt.gpg

The resulting file hello.txt.gpg has three packets. The first packets encrypts the session key with Alice's public key. The second packet encrypts the session key with AES-128 using the passphrase Alice gave to gpg2. The third packet encrypts the message using the session key. For example:

-> pgpdump hello.txt.gpg
Old: Public-Key Encrypted Session Key Packet(tag 1)(268 bytes)
	New version(3)
	Key ID - 0x45D7DFB60F009D55
	Pub alg - RSA Encrypt or Sign(pub 1)
	RSA m^e mod n(2044 bits) - ...
		-> m = sym alg(1 byte) + checksum(2 bytes) + PKCS-1 block type 02
Old: Symmetric-Key Encrypted Session Key Packet(tag 3)(46 bytes)
	New version(4)
	Sym alg - AES with 128-bit key(sym 7)
	Iterated and salted string-to-key(s2k 3):
		Hash alg - SHA1(hash 2)
		Salt - 12 be a8 c8 61 90 40 b8 
		Count - 35651584(coded count 241)
	Encrypted session key
		-> sym alg(1 bytes) + session key
New: Symmetrically Encrypted and MDC Packet(tag 18)(86 bytes)
	Ver 1
	Encrypted data [sym alg is specified in sym-key encrypted session key]
		(plain text + MDC SHA1(20 bytes))

When decrypting the file, gpg2 recovers the session key from either the first packet or the second packet and uses it to decrypt the message in the third packet. If gpg2 can find Alice's private keyring, it prompts for the keyring's passphrase and then decrypts the first packet.

-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg 
gpg: encrypted with 1 passphrase
gpg: encrypted with 2048-bit RSA key, ID 45D7DFB60F009D55, created 2018-03-13
      "Alice"
gpg: session key: '9:E0AA77FF33FC0A28096A84E4A9F5D22F07DB5BF37DFC46E6BD8B1C4EA1B7E1B0'
Hello there, world.

If gpg2 cannot find the keyring, it prompts Alice for the passphrase encrypting the second packet and uses that to decrypt the session key.

-> gpg2 --show-session-key --decrypt hello.txt.gpg 
gpg: AES encrypted session key
gpg: encrypted with 1 passphrase
gpg: encrypted with RSA key, ID 45D7DFB60F009D55
gpg: session key: '9:E0AA77FF33FC0A28096A84E4A9F5D22F07DB5BF37DFC46E6BD8B1C4EA1B7E1B0'
Hello there, world.

With only symmetric-key encryption, the session key is encrypted with AES:

-> gpg2 --homedir alice --yes --symmetric hello.txt
-> gpg2 --homedir alice --show-session-key --decrypt hello.txt.gpg 
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
gpg: session key: '7:E902F46D07FD5DE696AADC2EA61412DB'
Hello there, world.
-> gpg2 --homedir alice --override-session-key 7:E902⋯12DB --quiet --decrypt hello.txt.gpg
Hello there, world.

Here's how the packet lengths add up and break down for Alice's Symmetrically Encrypted and MDC Packet. To simplify, she drops compression:

-> gpg2 --homedir alice --yes --compress-algo none --encrypt --recipient Alice hello.txt
-> gpg2 --homedir alice --quiet --list-packets hello.txt.gpg 
⋮
# off=271 ctb=d2 tag=18 hlen=2 plen=78 new-ctb
:encrypted data packet:
	length: 78
	mdc_method: 2
# off=292 ctb=ac tag=11 hlen=2 plen=35
:literal data packet:
	mode b (62), created 1524068955, name="hello.txt",
	raw data: 20 bytes

The payload, or body, of the containing Symmetrically Encrypted and MDC Packet spans 78 octets:

  19   prefatory data
+ 37   Literal Data Packet
+ 22   MDC Packet
――――
= 78   

(Sanity check: This sum and the two sums below count octets. The octets' values don't count here.)

The body of the Symmetrically Encrypted and MDC Packet begins with a sequence of octets unrelated to the ciphertext of the message and its metadata. These octets instead depend on the cipher. With AES, there are 19 octets:

   1   version number
+ 16   random string
+  2   repeated octets
――――
= 19   

The first octet gives the version number of the packet type. A sequence of random octets follows, where the number of octets is the block length of the cipher. Alice's cipher is AES, which operates on blocks of 16 octets (128 bits). The final two octets repeat the previous pair of octets. Here for AES, random octets 15 and 16 repeat and cap this prefatory sequence. [§5.13]

The Literal Data Packet comes next and encrypts message and metadata. Its payload size depends on the size of the message and filename within. For hello.txt, the packet's payload spans 35 octets. The metadata go first and fill 15 octets, like so:

   1   mode
+  1   size of filename hello.txt
+  9   filename hello.txt
+  4   timestamp
――――
= 15

The 20 octets of the message ("Hello there, world.") follow. Adding the header's 2 octets brings the complete packet's size to 37 octets. [§5.9]

The MDC Packet comes last. Its complete size is fixed at 22 octets, regardless of the message and metadata it condenses. The header has two octets, and the payload has 20 octets. The payload holds the 160-bit output of SHA-1 (applied to the combined sequences of random prefix above, plaintext, 0xd3, 0x14). OpenPGP stipulates that the MDC packet be the last packet inside a Symmetrically Encrypted and MDC Packet. [§5.14].