Section 7: Applied Cryptography
You now hold a full toolbox: block ciphers and AEAD modes (Section 4), hash functions, MACs, and KDFs (Section 5), and public-key encryption, signatures, and Diffie-Hellman (Section 6). Individually, each primitive solves one narrow problem. None of them, alone, is a security system. This section is about the step that actually matters in practice: composition — wiring the primitives together into protocols that survive contact with real networks, real adversaries, and real users.
Think of primitives as Lego bricks and protocols as the buildings. The bricks — AES-GCM, SHA-256, X25519, Ed25519 — are astonishingly solid; direct mathematical breaks of modern primitives are vanishingly rare. Real-world cryptographic failures almost always live in the mortar: a protocol that authenticates the wrong thing, a key reused where it must be fresh, a downgrade path an attacker can force, a signature that covers less than everyone assumed. As you read the systems below — TLS, PGP, Signal, blockchains — keep asking the composition questions: which primitive provides which guarantee, what keys exist, who holds them, and what happens when one leaks?
TLS: the protocol that carries the web
TLS (Transport Layer Security) is the S in HTTPS. Every padlock in your browser, every API call your phone makes, most email hops, and a large share of VPN traffic ride on it. TLS gives a client and server three guarantees over a hostile network: confidentiality (eavesdroppers see noise), integrity (tampered records are rejected), and authentication (you are talking to the server named in the certificate — usually one-way; the client typically authenticates later with a password or token inside the tunnel).
A quick genealogy, because the names still cause confusion:
| Version | Year | Status |
|---|---|---|
| SSL 2.0 / 3.0 | 1995 / 1996 | Dead. Broken by design flaws (SSL 3.0 fell to the POODLE attack). Never use. |
| TLS 1.0 / 1.1 | 1999 / 2006 | Formally deprecated (RFC 8996, 2021). Disabled in all major browsers. |
| TLS 1.2 | 2008 | Legacy but still common; secure only with a carefully chosen cipher suite. |
| TLS 1.3 | 2018 (RFC 8446) | Current. Simpler, faster, and with the historical foot-guns removed. |
The TLS 1.3 handshake, step by step
The handshake is a negotiation that must accomplish four things before a single byte of application data flows: agree on algorithms, establish shared secret keys, prove the server’s identity, and confirm nothing was tampered with along the way. TLS 1.3 does all of it in a single round trip:
CLIENT SERVER
| |
| ClientHello ------------------------------------------> |
| - supported TLS versions |
| - supported cipher suites (e.g. TLS_AES_128_GCM_...) |
| - key_share: ephemeral ECDHE public key (e.g. x25519) |
| - server name (SNI), extensions |
| |
| The client speculates: it sends its half of the |
| Diffie-Hellman exchange BEFORE knowing what the |
| server will pick. This is what buys 1-RTT. |
| |
| <------------------------------------------ ServerHello |
| - chosen version + cipher suite |
| - key_share: server ephemeral ECDHE public key |
| |
| * Both sides now compute the ECDHE shared secret and |
| * derive handshake keys with HKDF. EVERYTHING BELOW |
| * THIS LINE IS ALREADY ENCRYPTED. |
| |
| <------------------------------------ {EncryptedExtensions}
| - further parameters (ALPN, etc.) |
| <-------------------------------------------- {Certificate}
| - server certificate chain (X.509, see Section 6) |
| <-------------------------------------- {CertificateVerify}
| - signature over the handshake transcript, made with |
| the private key matching the certificate |
| <------------------------------------------------ {Finished}
| - HMAC over the entire transcript: "here is what I |
| think we said" -- detects any tampering |
| |
| {Finished} -------------------------------------------> |
| - client confirms the same transcript |
| |
| {Application data} <==================================> |
| - HTTP requests/responses under AES-GCM or |
| ChaCha20-Poly1305, keys derived via HKDF |
| |
{ } = encrypted one round trip to first byte
Annotate what each message achieves, and you can see every earlier section of this course click into place:
- ClientHello / ServerHello — key exchange. The
key_sharevalues are ephemeral ECDHE (elliptic-curve Diffie-Hellman, Section 6) public values, usually on x25519 or P-256. Both sides combine them into a shared secret. Because the key pairs are generated fresh per connection and discarded afterward, TLS 1.3 has forward secrecy by construction: an attacker who records today’s traffic and steals the server’s long-term key next year still cannot decrypt the recording — the ephemeral secrets that actually protected it no longer exist. - Certificate + CertificateVerify — authentication. The certificate binds the server’s name to a public key via the CA signature chain from Section 6 (PKI). But possessing a certificate proves nothing — certificates are public. The CertificateVerify message is the proof: a fresh digital signature over the running hash of the handshake transcript, which only the holder of the matching private key could produce. Signing the transcript (not a fixed challenge) also welds the identity proof to this specific handshake, blocking splice-and-replay games.
- HKDF — key derivation. The raw ECDHE shared secret is never used directly. It feeds the HKDF extract-and-expand construction (Section 5) — a carefully layered key schedule that outputs separate keys for client-to-server handshake traffic, server-to-client handshake traffic, application traffic in each direction, and more. One secret in, many independent keys out, so compromise or misuse of one never touches the others.
- Finished — integrity of the negotiation itself. Each side sends an HMAC over everything said so far. If a man-in-the-middle altered a single bit — say, stripping a strong option to force a weaker one — the transcripts diverge and both sides abort. This kills the downgrade attacks that plagued earlier versions.
- Record protection — AEAD only. Application data is encrypted with an authenticated cipher: AES-GCM or ChaCha20-Poly1305 (Section 4). Every record is simultaneously encrypted and authenticated; there is no legal way to run TLS 1.3 without integrity.
Reading a cipher suite
TLS 1.3 shrank cipher suite names dramatically because key exchange and authentication are negotiated separately now. Dissect the most common one:
TLS _ AES_128_GCM _ SHA256
| | |
| | +-- hash for HKDF key derivation
| | and transcript hashing
| +-- record AEAD: AES, 128-bit key, GCM mode
+-- protocol family
(a TLS 1.2 suite, for contrast, packed everything in:
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
^kex ^auth ^cipher ^PRF )
What TLS 1.3 fixed
- 1-RTT handshakes. The client’s speculative key share cuts a full round trip versus TLS 1.2 — a measurable latency win on every connection.
- Pruned algorithms. Gone entirely: RSA key transport (the client encrypting a secret to the server’s long-term key — no forward secrecy, and the root cause of Bleichenbacher-style padding oracle attacks), static Diffie-Hellman, all CBC-mode suites (Lucky 13, POODLE), RC4, MD5 and SHA-1 in handshake signatures, and compression (CRIME). If a TLS 1.3 connection exists at all, it is ephemeral-DH, AEAD-protected, forward secret. There are no unsafe configurations to misconfigure into.
- Encrypted handshake. In TLS 1.2 the server certificate crossed the wire in cleartext, telling any passive observer exactly which site you were visiting. In 1.3 everything after ServerHello is encrypted under the handshake keys.
- 0-RTT resumption. A returning client can send application data in its very first flight, encrypted under a key from the previous session (a pre-shared key). Great for latency; dangerous if misused — see below.
GET /index.html and catastrophic for POST /transfer?amount=5000. RFC 8446 therefore requires applications to restrict 0-RTT to requests that are safe to execute more than once, and careful servers reject early data on anything state-changing.
curl -v https://example.com prints the TLS version and suite, and openssl s_client -connect example.com:443 -tls1_3 dumps the full certificate chain and handshake details. Pair either with the annotated byte-by-byte walkthrough at tls13.xargs.org (linked in Resources) and the diagram above becomes real packets.
PGP/GPG: encrypted email and the web of trust
Pretty Good Privacy (1991) and its open implementation GnuPG (GPG) apply the same primitives to a very different setting: store-and-forward email, where there is no interactive handshake — you must encrypt a message today that the recipient decrypts whenever they get around to it.
PGP is the classic example of hybrid encryption, and the pattern is worth internalizing because nearly every system that "encrypts with RSA" actually does this:
- Generate a random one-time session key (e.g. 256 bits for AES).
- Encrypt the message body with a symmetric cipher under that session key — fast, and works for any size.
- Encrypt the small session key with each recipient’s public key (RSA or ECC). Three recipients means three encrypted copies of the same session key, one message body.
- Optionally, sign: hash the message and sign the digest with the sender’s private key, so recipients get authenticity and integrity too.
random message
session key K plaintext
| |
+----------+----------+ |
| | | v
RSA-enc RSA-enc RSA-enc AES-enc(K)
(Alice pk) (Bob pk) (Carol pk) |
| | | |
+----------+----------+----------+----> one PGP message,
decryptable by any
of the three recipients
Where TLS delegates trust to certificate authorities, PGP proposed the web of trust: users sign each other’s keys, and you accept a stranger’s key if enough people you already trust have vouched for it. Decentralized and CA-free in theory; in practice the web never grew dense enough, key-signing parties stayed a niche ritual, and most users ended up trusting keys on first use with no verification at all.
Honesty requires saying why PGP largely lost, despite three decades of effort:
- Usability. The famous 1999 study "Why Johnny Can’t Encrypt" found most users could not send a correctly encrypted email even when trying; a majority accidentally leaked plaintext. Little changed since.
- No forward secrecy. One long-term key protects years of mail. Steal it once, decrypt everything ever sent to it — the exact failure mode TLS 1.3 and Signal engineered away.
- Metadata in the clear. PGP encrypts the body only. Subject lines, sender, recipients, and timing remain visible to every mail server in the path.
- Efail (2018). Mail clients that rendered HTML could be tricked into exfiltrating decrypted plaintext, partly because classic PGP used unauthenticated CFB encryption — a composition flaw (encryption without mandatory integrity), not a broken primitive.
GPG remains genuinely useful for signing software releases and Git tags, and for file encryption. The commands you will actually use:
# generate a key pair (interactive prompts) gpg --full-generate-key # encrypt a file for a recipient (by email or key ID) gpg --encrypt --recipient alice@example.org secret.txt # decrypt (uses your private key automatically) gpg --decrypt secret.txt.gpg > secret.txt # make a detached signature over a file gpg --detach-sign release.tar.gz # verify a signature gpg --verify release.tar.gz.sig release.tar.gz # sign AND encrypt in one step gpg --sign --encrypt --recipient alice@example.org secret.txt
age deliberately does less than GPG: no key servers, no web of trust, no configuration — just short X25519 keys and ChaCha20-Poly1305, with no way to hold it wrong. age -r <recipient-key> -o secret.age secret.txt and you are done. For messaging, the answer is not better PGP; it is the protocol in the next section.
End-to-end encryption: the Signal protocol
End-to-end encryption (E2EE) means keys exist only on the endpoints: the server that relays your messages stores and forwards ciphertext it mathematically cannot read. TLS alone does not give you this — TLS protects the hop from your phone to the server, but the server sees plaintext. E2EE encrypts through the server. The state of the art is the Signal protocol, designed by Moxie Marlinspike and Trevor Perrin, and it solves two problems PGP never did: starting an encrypted session with someone who is offline, and limiting the damage when keys eventually leak.
X3DH: agreeing on a key with someone who is asleep
Diffie-Hellman needs both parties present; your contact is on a plane. X3DH (Extended Triple Diffie-Hellman) fixes this with prekeys. When Bob installs the app, his device uploads to the server:
- an identity key — his long-term DH public key;
- a signed prekey — a medium-term DH key, signed by the identity key (so the server cannot swap in its own);
- a batch of one-time prekeys — single-use DH keys, each handed out once and deleted.
When Alice wants to message the sleeping Bob, she fetches this "prekey bundle" and performs several DH computations — her identity and ephemeral keys crossed against Bob’s identity, signed prekey, and one one-time prekey — and hashes the results together into a shared secret. Mixing the identity keys authenticates both parties; mixing the ephemeral and one-time keys makes the secret fresh. Her first message carries the public values Bob needs to run the same computation when he wakes up. Asynchronous, mutually authenticated key agreement — no handshake required.
The Double Ratchet: keys that heal
The X3DH secret only seeds the session. From there, every single message is encrypted under a different key, produced by the Double Ratchet — two interlocking mechanisms, both named for their one-way motion:
- Symmetric-key ratchet: each direction of the conversation has a chain key. To send, derive (via a KDF, Section 5) a fresh message key and the next chain key, then delete the old state. Because KDFs are one-way, today’s state cannot be run backward to recover yesterday’s keys: forward secrecy, per message.
- DH ratchet: whenever the conversation turns (Bob replies to Alice), the replier attaches a brand-new ephemeral DH public key. The fresh DH output is mixed into the root key, which spawns entirely new chain keys. An attacker who fully compromised the session state gets locked out again after one round trip of genuine messages, because the new DH secret was never in the stolen state: post-compromise security, or self-healing.
X3DH secret
|
v DH ratchet (turns of conversation)
root key --new DH--> root key' --new DH--> root key'' ...
| | |
v v v
chain key chain key' chain key''
| \ | \ | \
KDF msg key 1 KDF msg key 1 KDF msg key 1
| \ | \ ...
KDF msg key 2 KDF msg key 2
| |
v v each arrow is one-way:
... ... old keys are unrecoverable
Plainly: forward secrecy means a compromise today cannot decrypt the past; post-compromise security means the future re-secures itself once the attacker loses access. PGP offers neither. Signal offers both, automatically, for billions of users — the protocol runs inside Signal itself, WhatsApp, Google Messages (RCS), and Facebook Messenger.
One thing no protocol can automate is knowing you have the right public key rather than an impostor’s — the server hands out prekey bundles, and a malicious server could hand out its own. Signal surfaces this as safety numbers: a short fingerprint of both parties’ identity keys, displayed as digits and a QR code. Compare them over another channel (in person, on a call) and a machine-in-the-middle becomes detectable; the app also warns loudly whenever a contact’s identity key changes.
Blockchain: cryptography as ledger glue
Bitcoin is a composition exercise using almost nothing but Sections 5 and 6: hashes, Merkle trees, and signatures, arranged so that thousands of mutually distrusting strangers can agree on a ledger. There is no encryption anywhere in the core protocol — a point worth dwelling on below.
Hash chains make history tamper-evident. Every block header contains the SHA-256 hash of the previous block’s header. Alter any transaction in an old block and its hash changes; the next block’s stored "previous hash" no longer matches, so that block’s hash changes too, and the mismatch cascades to the tip of the chain. You cannot quietly edit history — you can only fork it, visibly, and then lose the race to re-mine everything after your edit.
+-------------------+ +-------------------+ +-------------------+ | BLOCK N | | BLOCK N+1 | | BLOCK N+2 | | prev: hash(N-1) |<----| prev: hash(N) |<----| prev: hash(N+1) | | merkle root | | merkle root | | merkle root | | nonce, time, ... | | nonce, time, ... | | nonce, time, ... | +-------------------+ +-------------------+ +-------------------+ change one tx in block N => hash(N) changes => N+1's "prev" is wrong => every later block must be re-mined: tamper-evidence
Merkle trees compress a block’s transactions into one hash. Transactions are paired and hashed, the results paired and hashed again, up to a single Merkle root stored in the header. A lightweight wallet can verify that its transaction is inside a block by checking only log₂(n) sibling hashes — for 4096 transactions, just 12 hashes — instead of downloading them all.
Signatures define ownership. A Bitcoin "account" is just an ECDSA key pair on the secp256k1 curve (Section 6). Coins are locked to a public key; spending them means publishing a transaction signed by the matching private key, which every node verifies. Your address is not the public key itself but a hash of it (SHA-256 then RIPEMD-160, plus encoding) — shorter, and the raw public key stays hidden until you first spend. Lose the private key and the coins are frozen forever; there is no CA, no recovery, no one to call.
Proof-of-work is a partial preimage search. To append a block, a miner must find a nonce such that SHA-256 of the block header falls below a target — in practice, a digest starting with many zeros. Because a good hash function gives no shortcut (Section 5), the only strategy is brute force: guess, hash, repeat, quintillions of times per second network-wide. Each extra leading zero bit doubles the expected work; each leading zero hex digit multiplies it by 16. That asymmetry is the whole trick: finding a valid nonce is expensive, verifying one is a single hash — so rewriting history costs more than the network of honest miners spends extending it.