How PGP works: hybrid encryption, signatures and packets
PGP does not encrypt your message with your recipient’s public key directly. It makes a fresh random key for each message, encrypts the message with that key using fast symmetric encryption, and then encrypts only that small key with the recipient’s public key. This page walks through that idea and the other steps around it.
Two kinds of encryption
Symmetric encryption uses one key to lock and unlock, like a house key. It is very fast and works on data of any size. OpenPGP software today uses AES for it. The catch: the sender and the recipient both need the same key, and you cannot safely send it over the same channel as the message.
Public-key encryption uses a pair of keys. Anyone can use your public key to encrypt something for you, but only your private key can decrypt it. That solves the key-sharing problem, because you can publish the public key anywhere. But public-key operations are slow, and they can only handle small amounts of data. Current keys typically use Curve25519 (X25519) for this. Older keys use RSA.
PGP combines the two. This is called hybrid encryption.
Hybrid encryption step by step
RFC 9580, the OpenPGP standard, describes what happens when you encrypt a message to Bob:
- The software generates a random session key, used for this one message only.
- It encrypts the message with the session key, using a symmetric cipher such as AES.
- It encrypts the session key with Bob’s public key.
- It sends both: the encrypted session key first, then the encrypted message.
Bob’s software reverses this. His private key decrypts the session key, and the session key decrypts the message.
This design has a useful side effect: one message can have many recipients. Say you send a 10 MB file to three people. The file is encrypted once, about 10 MB, and the session key is encrypted three times, once with each recipient’s public key. With the Curve25519 keys GnuPG makes, each encrypted session key takes about 100 bytes, so three recipients add about 300 bytes, not another 20 MB.
Encrypting with only a passphrase (gpg --symmetric) works in a similar way. The session key is protected with a key derived from the passphrase, not with a public key.
Hashes and signatures
A hash function turns any amount of data into a short fixed-size value, like a very strong checksum. SHA2-256, the hash every RFC 9580 program must support, always gives 256 bits. Change one letter of the input and the hash changes completely. Finding two inputs with the same hash is meant to be impossible in practice.
To sign, your software hashes the data and then signs the hash with your private key. To verify, the recipient’s software hashes the data it received and checks the signature against that hash using your public key. If even one byte changed, verification fails. A good signature shows two things: the data has not changed, and it was signed by whoever holds the private key. Whether that key really belongs to the person it names is a separate question, covered in Key signatures.
Ed25519 is the signature algorithm every RFC 9580 program must support. MD5, which PGP 2.x used, and SHA-1 must no longer be used to make signatures.
Signing and encrypting together
When you sign and encrypt, the signature goes on the inside. The software signs the plain message, may compress the result, and then encrypts everything. Only the recipient can see the signature, and it still checks out after decryption. It also means an eavesdropper cannot tell from the outside who signed the message.
Inside a message: packets
An OpenPGP message is a sequence of packets, each with a type number and a length. Keys, signatures and messages are all built from packets. You can look at them with gpg --list-packets. Here is a short signed-and-encrypted message made with GnuPG (output shortened):
gpg --list-packets message.asc
:pubkey enc packet: version 3, algo 18, keyid EBD243B6F125D26C
:encrypted data packet:
mdc_method: 2
:compressed packet: algo=2
:onepass_sig packet: keyid F2C9F13D0062B815
:literal data packet:
mode b (62), created 1793775997, name="msg.txt",
raw data: 14 bytes
:signature packet: algo 22, keyid F2C9F13D0062B815
digest algo 10, begin of digest 7d e6
| Packet (type number) | What it holds |
|---|---|
| Public-Key Encrypted Session Key (1) | The session key, encrypted to one recipient’s key. There is one per recipient. |
| Symmetrically Encrypted and Integrity Protected Data (18) | Everything below, encrypted with the session key, plus a check that detects tampering. |
| Compressed Data (8) | The inner packets, compressed (here with ZLIB). |
| One-Pass Signature (4) | Tells the reader a signature follows, so it can hash the data as it reads it, in one pass. |
| Literal Data (11) | The actual 14-byte message, with a file name and date. |
| Signature (2) | The signature over the literal data. |
A public key file has its own packets: Public Key (6), User ID (13), Signature (2) for each self-signature and certification, and Public Subkey (14).
Compression
Encrypted data looks random and does not compress, so compression, when it happens at all, comes before encryption. The standard defines ZIP, ZLIB and BZip2, and requires only that programs handle uncompressed data. Compression is optional. RFC 9580 also warns that a malicious message could be built to expand without limit, so programs should cap how many layers of compression they will unpack.
ASCII armor
Packets are binary. Email and web forms handle plain text better, so OpenPGP can wrap binary data in ASCII armor: base64 text, in lines of at most 76 characters, between header lines such as these:
-----BEGIN PGP MESSAGE-----
hF4D69JDtvEl0mwSAQdAXUJjpoK3a9U4ZGtgGGAPZUA4sZiGlqsGiNyzCrwv7kcw
…
=6gOV
-----END PGP MESSAGE-----
The other headers are PUBLIC KEY BLOCK, PRIVATE KEY BLOCK and SIGNATURE. The short line starting with = is an optional CRC24 checksum. RFC 9580 now says programs should not add it, and must not add it for the new version 6 formats. In GnuPG, --armor (or -a) produces armored output. Without it you get binary.
A cleartext signature (gpg --clearsign) leaves the text readable above an armored signature. Trailing spaces are ignored and line endings are normalized before signing, and any line that starts with a dash gets “- ” in front of it. That is why the method suits plain text but not files where whitespace matters, such as patches.
Keys, subkeys and fingerprints
What people call a “public key” is really a bundle, an OpenPGP certificate. It holds a primary key (used for signing and certifying), one or more user IDs, self-signatures that tie them together, and usually subkeys for other jobs. Since version 2.3, GnuPG by default makes an Ed25519 primary key marked [SC] (sign, certify) and a Curve25519 subkey marked [E] (encrypt). Since 2.4.3, new keys expire after three years unless you choose otherwise.
The fingerprint is a hash of the primary public key. For the common version 4 keys it is a 160-bit SHA-1 hash, shown as 40 hex digits, and the 64-bit key ID is its last 16 digits. Version 6 keys from RFC 9580 use SHA2-256, which gives a 256-bit fingerprint, and their key ID is its first 64 bits. More in Keys.
Your private key is stored encrypted with a key derived from your passphrase. RFC 9580 adds Argon2 for this, a method designed to make guessing passphrases slow and memory-hungry.
What changed since PGP 2.x
The old FAQ’s appendix described PGP 2.x as three parts: RSA, IDEA and MD5. The design is the same today. The parts have changed:
| Job | PGP 2.x (1990s) | RFC 9580 (2024) |
|---|---|---|
| Public-key algorithm | RSA | X25519 and Ed25519 required; new RSA keys must be at least 3072 bits |
| Symmetric cipher | IDEA | AES-128 required, AES-256 recommended; IDEA must not be used to encrypt |
| Hash | MD5 | SHA2-256 required; MD5 and SHA-1 must not be used for new signatures |
| Tamper detection | None in the old encrypted-data packet (type 9) | Integrity-protected packet (type 18); version 2 uses authenticated encryption (OCB required) |
| Fingerprint | MD5 (version 3 keys) | SHA-1 (version 4), SHA2-256 (version 6) |
Not every program uses every part of RFC 9580 yet. OpenPGP standards explains where implementations differ.
Sources
- RFC 9580: OpenPGP (sections 2.1, 5, 5.5.4, 6, 7, 9, 12.4, 13.14), RFC Editor, July 2024
- The GNU Privacy Handbook: Concepts (symmetric, public-key and hybrid ciphers, digital signatures)
- GnuPG manual: Operational commands (list-packets)
- GnuPG manual: Input and output (armor)
- GnuPG NEWS file (default key algorithms since 2.3.0, default expiry since 2.4.3)