Encryption, TLS, and Cryptography Deep Dive
The Big Picture: Four Primitives
| Primitive | Purpose | Examples | Key Property |
|---|---|---|---|
| Symmetric encryption | Confidentiality | AES-GCM, ChaCha20-Poly1305 | Fast; same key for enc/dec |
| Asymmetric encryption | Key exchange, digital signatures | RSA, ECDH, ECDSA | Slow; public/private key pair |
| Hashing | Integrity, fingerprinting | SHA-256, SHA-3, BLAKE3 | One-way; fixed output length |
| MAC/HMAC | Authenticity + integrity | HMAC-SHA256, Poly1305 | Requires shared secret |
Rule of thumbAsymmetric is used to establish a shared secret, then symmetric takes over for bulk data. This is TLS in a nutshell.
AES (Advanced Encryption Standard)
WhatBlock cipher operating on 128-bit blocks. Key sizes: 128, 192, or 256 bits.
Why AESSelected by NIST in 2001 after open competition. Hardware acceleration (AES-NI instruction) makes it extremely fast on modern CPUs. Considered quantum-resistant for 256-bit keys (would need 2^128 operations with Grover's algorithm).
WhereEverywhere — TLS, disk encryption (FileVault, BitLocker, LUKS), VPNs, WiFi (WPA2/WPA3), SSH, cloud KMS.
AES Modes of Operation
| Mode | Type | Auth? | IV reuse safe? | Use Case |
|---|---|---|---|---|
| ECB | Block | No | N/A | NEVER (identical blocks → identical ciphertext → pattern leak) |
| CBC | Block | No | No (IV must be random) | Legacy TLS, disk encryption (with separate HMAC) |
| CTR | Stream | No | No (nonce must be unique) | Fast streaming encryption |
| GCM | Stream + AEAD | Yes (128-bit tag) | No | Recommended — TLS 1.3, SSH, IPSec |
| CCM | Block + AEAD | Yes | No | IoT, IEEE 802.11 (WiFi) |
| SIV | Stream + AEAD | Yes | Yes (nonce misuse resistant) | Key wrapping, deterministic enc |
AES-GCM deep dive
- Combines CTR mode (encryption) with GHASH (authentication tag)
- Produces:
ciphertext + 128-bit authentication tag - Decryption MUST verify tag before decrypting (otherwise oracle attacks)
- Critical weakness: nonce reuse → catastrophic (reveals authentication key AND plaintext XOR)
- Used in: TLS 1.2 ECDHE-RSA-AES256-GCM-SHA384, TLS 1.3 AES_256_GCM_SHA384
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = os.urandom(32) # 256-bit key
nonce = os.urandom(12) # 96-bit nonce (never reuse with same key!)
data = b"secret message"
aad = b"authenticated but not encrypted"
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, data, aad) # ciphertext + 16-byte tag
plaintext = aesgcm.decrypt(nonce, ciphertext, aad)Advantages
- AEAD: encryption and authentication in one pass
- Hardware-accelerated (PCLMULQDQ + AES-NI)
- Widely supported and well-studied
Disadvantages
- Nonce misuse is fatal (unlike AES-SIV)
- Nonce is 96 bits — if randomly generated, risk of collision after ~2^48 messages with same key (birthday bound)
- Side-channel vulnerable in software without constant-time implementation
ChaCha20-Poly1305
WhatStream cipher (ChaCha20) + MAC (Poly1305). AEAD.
Why ChaCha20Designed by D.J. Bernstein as an alternative to AES for systems without hardware AES acceleration (e.g., older ARM chips). Resistant to timing side-channels in software because it uses only XOR, addition, rotation.
WhereTLS 1.3 (TLS_CHACHA20_POLY1305_SHA256), WireGuard VPN, Signal Protocol, OpenSSH (chacha20-poly1305@openssh.com).
| AES-GCM | ChaCha20-Poly1305 | |
|---|---|---|
| Speed (with HW) | Faster | Slower |
| Speed (no HW) | Slower | Faster |
| Side-channel | Risky in SW | Safe (no table lookups) |
| Nonce reuse | Catastrophic | Still bad but less catastrophic |
| Nonce size | 96 bits | 96 bits |
| Key size | 128 or 256 bits | 256 bits |
Advantages
- Software performance on mobile/IoT
- Inherently constant-time
Disadvantages
- No hardware acceleration (slower on AES-NI chips)
- Less deployment history than AES
RSA
WhatAsymmetric encryption based on integer factoring hardness. Key sizes: 2048, 3072, 4096 bits.
Why RSAFirst practical public-key system (1977). Used for key exchange (RSA key transport in TLS 1.2) and digital signatures.
WhereTLS certificates (signing), S/MIME, PGP, SSH host keys (legacy), JWT (RS256).
How it works (simplified):
Key generation:
p, q = large primes
n = p × q (public modulus)
e = 65537 (public exponent, standard)
d = e^-1 mod φ(n) (private exponent)
Encryption: c = m^e mod n
Decryption: m = c^d mod n
Signature: sig = hash(m)^d mod n
Verification: hash(m) = sig^e mod nRSA vs ECC key strength comparison
| RSA bits | ECC bits | Security level |
|---|---|---|
| 1024 | 160 | 80-bit (broken) |
| 2048 | 224 | 112-bit (minimum) |
| 3072 | 256 | 128-bit (recommended) |
| 4096 | 384 | 192-bit |
Advantages
- Well-understood, widely deployed
- Simple conceptually
- Large ecosystem of implementations
Disadvantages
- Large key and signature sizes (vs ECDSA)
- Slow operations vs ECC
- Quantum-vulnerable (Shor's algorithm breaks RSA)
- Vulnerable to timing attacks without blinding
- PKCS#1 v1.5 padding attacks (Bleichenbacher); use OAEP/PSS
Padding schemes
| Scheme | Use | Notes |
|---|---|---|
| PKCS#1 v1.5 | Legacy enc/sig | Vulnerable (Bleichenbacher for enc, ROBOT) |
| OAEP | Encryption | Recommended; probabilistic; provably secure |
| PSS | Signing | Recommended; probabilistic |
Elliptic Curve Cryptography (ECC)
WhatAsymmetric cryptography using elliptic curves over finite fields. Smaller keys for equivalent security.
Why ECCSmaller keys = faster operations, less bandwidth, better for constrained devices. Also no known sub-exponential attacks (unlike RSA factoring).
WhereTLS (ECDHE, ECDSA), Bitcoin/Ethereum (secp256k1), SSH (ed25519), Signal (Curve25519), TLS certificates (ECDSA), JWT (ES256).
Common Curves
| Curve | Size | Use | Notes |
|---|---|---|---|
| P-256 (secp256r1) | 256-bit | TLS, ECDSA, ECDH | NIST curve; some concern about backdoor |
| P-384 (secp384r1) | 384-bit | High-security TLS | Government/compliance use |
| secp256k1 | 256-bit | Bitcoin, Ethereum | Non-NIST; performance-optimised |
| Curve25519 (X25519) | 255-bit | ECDH key exchange | Designed by Bernstein; fast, safe |
| Ed25519 | 255-bit | Signatures (EdDSA) | Fast, constant-time, safe to implement |
ECDH (key exchange)
Alice: ephemeral private key a, public key A = a·G
Bob: ephemeral private key b, public key B = b·G
Shared secret: S = a·B = b·A = ab·GECDSA (signing)
- Requires a random
kper signature - Critical: reusing
kwith same private key reveals the private key (PlayStation 3 attack, Bitcoin key extractions) - EdDSA (ed25519) uses deterministic
k→ immune to this
Advantages
- Smaller keys and signatures than RSA
- Faster operations
- Ed25519: deterministic, constant-time, immune to k-reuse
Disadvantages
- More complex to implement correctly (side-channel risks)
- Quantum-vulnerable (Shor's algorithm breaks ECDLP too)
- Curve choice matters (NIST curves have some controversy)
Diffie-Hellman Key Exchange (DH / ECDH)
WhatTwo parties derive a shared secret over an untrusted channel without transmitting the secret.
WhyFoundation of forward secrecy. Neither party needs to encrypt the key — it's derived from ephemeral values.
Classic DH (finite field):
Public: prime p, generator g
Alice: secret a → A = g^a mod p → sends A to Bob
Bob: secret b → B = g^b mod p → sends B to Alice
Alice: S = B^a mod p = g^ab mod p
Bob: S = A^b mod p = g^ab mod p
Both have S, attacker only saw A, B, g, pECDH uses the same concept over elliptic curves (much smaller key for same security).
Forward SecrecyUse ephemeral DH (DHE or ECDHE) — generate new keypair per session. If long-term private key is compromised later, past sessions cannot be decrypted because ephemeral keys are discarded.
| Protocol | Key Exchange | Forward Secrecy? |
|---|---|---|
| TLS 1.2 with RSA | RSA key transport | No |
| TLS 1.2 with ECDHE | Ephemeral ECDH | Yes |
| TLS 1.3 | ECDHE (mandatory) | Yes (always) |
Hashing Algorithms
| Algorithm | Output | Speed | Status |
|---|---|---|---|
| MD5 | 128 bits | Fast | Broken (collision attacks) — only for non-cryptographic checksums |
| SHA-1 | 160 bits | Fast | Broken (SHAttered collision 2017) — deprecated in TLS, certificates |
| SHA-256 | 256 bits | Medium | Secure — widely used |
| SHA-384 | 384 bits | Medium | Secure — higher security margin |
| SHA-512 | 512 bits | Fast on 64-bit | Secure |
| SHA-3/Keccak | 224-512 bits | Slower | Secure — different construction (sponge) |
| BLAKE2 | 256/512 bits | Very fast | Secure — faster than SHA-2 in software |
| BLAKE3 | 256 bits | Fastest | Secure — parallelisable, very fast |
| bcrypt | ~60 chars | Intentionally slow | Password hashing — adaptive cost |
| Argon2 | Variable | Intentionally slow | Recommended for passwords — memory-hard |
| scrypt | Variable | Intentionally slow | Password hashing — memory-hard |
Where each is used
- SHA-256: TLS signatures, certificates, Bitcoin PoW, Git commit IDs, HMAC-SHA256
- SHA-512: HMAC in SSH (hmac-sha2-512), high-security signatures
- SHA-3: Available in TLS 1.3, government applications requiring post-SHA-2 algorithm
- BLAKE3: Fast checksums, general-purpose (Cargo, Zig, Cloudflare)
- Argon2id: Password hashing (OWASP recommendation), key derivation
- bcrypt: Legacy password hashing (web apps) — work factor 10-12 typical
TLS in Depth
Last verified2026-10
What is this?TLS (Transport Layer Security, the successor to SSL) is the protocol that puts the "S" in HTTPS, SMTPS, IMAPS, LDAPS and most service-to-service traffic. It sits between TCP (or QUIC) and the application, and turns an untrusted network path into a private, tamper-evident channel to a server whose identity has been checked. The sections below go from first principles to byte-level detail: the cryptography it uses, certificates and PKI, cipher suites, the record layer, both handshakes, the defenses built around TLS, the attacks that shaped it, and the TLS 1.3 key schedule.
Why it mattersAlmost every security question about the web eventually lands on TLS: "how do you know you're talking to the real bank?", "what does a MITM proxy actually do?", "why did we disable TLS 1.0?", "why can't the SOC see inside this traffic anymore?". Interviewers use TLS to probe whether you understand cryptography as a system: which primitive does which job, and what fails when one is missing.
The primitives themselves (AES, ChaCha20, RSA, ECC, Diffie–Hellman, hashing, HMAC, HKDF) are covered earlier on this page. This part is about how TLS assembles them.
What TLS guarantees (and doesn't), who the players are, how versions evolved.
Which primitive does which job in the handshake and the record layer.
X.509 structure, extensions, private keys, CSRs, file formats.
Chains, constraints, DV/OV/EV, CRL, OCSP, CRLSets, CRLite, short-lived certs.
Key exchange, authentication, bulk encryption, hashing; forward secrecy; what to allow.
The record layer, TLS 1.2 handshake variants, resumption, mutual TLS, extensions.
HSTS, CAA, Certificate Transparency; BEAST to Raccoon and what each one taught.
What changed, why, the key schedule, PSK, 0-RTT, and the 1.3 extensions.
TLS Fundamentals: Goals, Players and Versions
What TLS protects
Only the two endpoints can read the data. Provided by symmetric encryption (AES-GCM, ChaCha20-Poly1305) with keys agreed during the handshake.
Any change to the data in transit is detected. Provided by a MAC (TLS 1.2 CBC suites) or an AEAD tag (GCM / Poly1305).
The client knows it's talking to the real server (and optionally vice versa). Provided by certificates + digital signatures.
A recorded record can't be re-injected later. Every record has an implicit sequence number mixed into the MAC/nonce, so a replayed or reordered record fails verification.
WarningTLS does not give non-repudiationBoth sides hold the same symmetric session keys, so either side could have produced any given record. A packet capture of a TLS session can't prove to a third party that the server sent a particular message. That needs an application-level signature (e.g. signed JWTs, S/MIME, document signing). Interviewers like this trap.
The players
Starts the connection, offers its capabilities, validates the server's certificate. Usually a browser, an SDK or a service.
Picks the parameters, proves its identity with a certificate and a signature made with its private key.
A third party both sides trust. It verifies the server controls the domain, then signs a certificate binding the domain name to the server's public key.
The list of CA roots a client trusts out of the box (Chrome Root Store, Mozilla NSS, Apple, Microsoft). Root program policy is what actually governs CAs.
Public, append-only logs every publicly trusted certificate must be submitted to, so mis-issuance can be spotted.
Versions
| Version | Year | Spec | Wire value | Status |
|---|---|---|---|---|
| SSL 2.0 | 1995 | Netscape | 0x0002 | Prohibited (RFC 6176, 2011) — broken by design |
| SSL 3.0 | 1996 | RFC 6101 (historic) | 0x0300 | Deprecated (RFC 7568, 2015) after POODLE |
| TLS 1.0 | 1999 | RFC 2246 | 0x0301 | Deprecated (RFC 8996, 2021) — BEAST, weak PRF |
| TLS 1.1 | 2006 | RFC 4346 | 0x0302 | Deprecated (RFC 8996, 2021) — no AEAD, SHA-1/MD5 PRF |
| TLS 1.2 | 2008 | RFC 5246 | 0x0303 | Acceptable with AEAD + ECDHE suites only |
| TLS 1.3 | 2018 | RFC 8446 | 0x0304 | Preferred everywhere |
Memory hook"TLS 1.0 is SSL 3.1"The wire version for TLS 1.0 is
3,1(0x0301), TLS 1.2 is3,3, TLS 1.3 is3,4. The rename from SSL to TLS was mostly political (Netscape → IETF), not a redesign. That's why "SSL certificate" and "SSL offload" are still everyday terms, even though no one should be running SSL.
How TLS Uses Cryptography
TLS is a hybrid system. Asymmetric cryptography is slow but solves the "how do two strangers agree on a secret and know who they're talking to" problem. Symmetric cryptography is fast and protects the bulk data. Hashes glue everything together.
-
Key exchange — Diffie–Hellman (ECDHE)Handshake
Both sides send a public value, and each combines the other's with its own private value to reach the same shared secret. The secret itself never crosses the wire. (TLS 1.2 also allowed RSA key transport: the client encrypted a secret to the server's RSA key.)
-
Authentication — digital signaturesHandshake
The server signs handshake data with the private key matching its certificate (RSA-PSS, ECDSA or Ed25519). The CA's signature on the certificate binds that key to the domain name.
-
Key derivation — hash-based KDFHandshake
The raw shared secret is stretched into separate keys and IVs for each direction: the TLS 1.2 PRF (HMAC-SHA256/384) or TLS 1.3 HKDF.
-
Transcript integrity — hashingHandshake
A running hash of every handshake message is signed (CertificateVerify) and MACed (Finished), so tampering with any earlier message is caught.
-
Bulk protection — symmetric AEADRecords
Application data is encrypted and authenticated with AES-GCM or ChaCha20-Poly1305, using the derived keys and a per-record nonce.
| Primitive | TLS 1.2 uses it for | TLS 1.3 uses it for |
|---|---|---|
| RSA | Key transport or signatures | Signatures only (RSA-PSS for handshake signatures) |
| (EC)DHE | Optional key exchange | The only full-handshake key exchange (plus PSK) |
| ECDSA / EdDSA | Signatures (ECDSA) | Signatures (ECDSA, Ed25519, Ed448) |
| DSA | Rare, allowed | Removed |
| Hash | PRF, Finished, signatures | HKDF, transcript hash, signatures |
| Symmetric | AES-CBC+HMAC, AES-GCM, ChaCha20, (RC4, 3DES) | AEAD only: AES-GCM, ChaCha20-Poly1305, AES-CCM |
Memory hook"RSA wears two hats; TLS 1.3 took one away." RSA can encrypt a secret (key transport) or sign (authentication). Key transport means whoever later steals the server's private key can decrypt every recorded session, so TLS 1.3 kept only the signing hat. That single change is why TLS 1.3 always has forward secrecy.
X.509 Certificates, Keys and CSRs
What's in a certificate
A certificate is a signed statement from a CA: "the holder of this public key is entitled to use these names, from this date to that date". The structure is ASN.1, encoded in DER (binary) and usually shipped as PEM (base64 between -----BEGIN CERTIFICATE----- lines).
- tbsCertificate
- "To Be Signed": version, serial, issuer, validity, subject, public key, extensions
- signatureAlgorithm
- Which algorithm the CA used, e.g. ecdsa-with-SHA384, sha256WithRSAEncryption
- signatureValue
- The CA's signature over the DER bytes of tbsCertificate
Certificate:
Version: 3
Serial Number: 04:a1:... ← unique per CA; ≥ 64 bits of CSPRNG output (CA/B Forum rule)
Signature Algorithm: ecdsa-with-SHA384
Issuer: C=US, O=Let's Encrypt, CN=E6 ← must byte-match the issuer cert's Subject
Validity:
Not Before: Jun 1 00:00:00 2026 GMT
Not After : Aug 30 00:00:00 2026 GMT
Subject: CN=example.com ← informational only; browsers ignore CN for matching
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey (P-256)
Public Key: 04:6b:... (65 bytes)
X509v3 extensions:
Subject Alternative Name: DNS:example.com, DNS:www.example.com
Basic Constraints: critical, CA:FALSE
Key Usage: critical, Digital Signature
Extended Key Usage: TLS Web Server Authentication
Authority Key Identifier: 93:27:...
Subject Key Identifier: 1f:c2:...
Authority Information Access: CA Issuers - URI:http://e6.i.lencr.org/
Certificate Policies: 2.23.140.1.2.1 ← CA/B Forum "Domain Validated" policy OID
CT Precertificate SCTs: (2 SCTs from different log operators)Certificate extensions that matter
| Extension | What it says | Security relevance |
|---|---|---|
| Subject Alternative Name | DNS names / IPs the cert is valid for | The only field used for hostname matching |
| Basic Constraints | CA:TRUE/FALSE, optional pathLenConstraint | A leaf with CA:TRUE could mint certificates; old IE bugs ignored this |
| Key Usage | Low-level operations: digitalSignature, keyEncipherment, keyCertSign, cRLSign | Stops a leaf key being used to sign certificates |
| Extended Key Usage | Purpose: serverAuth, clientAuth, codeSigning, emailProtection | A code-signing cert must not authenticate a web server |
| Name Constraints | Permitted / excluded name subtrees for a CA | Lets an org get a CA restricted to *.corp.example |
| AKI / SKI | Key identifiers of issuer / subject | Helps path building pick the right issuer when names collide |
| Authority Information Access | URL of the issuer cert (and formerly OCSP) | Lets clients fetch missing intermediates ("AIA chasing") |
| CRL Distribution Points | Where to download the CRL | Revocation |
| Certificate Policies | OIDs for DV / OV / EV and CA policy | How a client knows the validation level |
| SCT list | Signed Certificate Timestamps from CT logs | Required by Chrome and Apple for public trust |
| TLS Feature (must-staple) | Server must staple an OCSP response | Hard-fail revocation (RFC 7633); rarely deployed |
Critical flagAn extension marked critical must be understood by the client, or the certificate must be rejected. Basic Constraints and Key Usage are normally critical; SAN is critical only when the Subject is empty.
What's in a private key
Modulus n, public exponent e (65537), private exponent d, primes p and q, plus CRT helpers dP, dQ, qInv that speed up signing ~4×. Knowing p or q is as good as knowing d.
A single random integer (the scalar) and the curve name, e.g. P-256. The public key is that scalar × the curve's base point.
A 32-byte seed, hashed to produce the signing scalar. No curve parameters to choose, no per-signature randomness to get wrong.
PKCS#1 (BEGIN RSA PRIVATE KEY, RSA only), PKCS#8 (BEGIN PRIVATE KEY, any algorithm), encrypted PKCS#8 (BEGIN ENCRYPTED PRIVATE KEY), SEC1 (BEGIN EC PRIVATE KEY).
Matching a certificate to its private key. They match if they contain the same public key. Compare hashes of the public key, not of the files:
openssl x509 -in cert.pem -noout -pubkey | openssl sha256
openssl pkey -in key.pem -pubout | openssl sha256
openssl req -in req.csr -noout -pubkey | openssl sha256 # the CSR should match tooWhat's in a CSR
A Certificate Signing Request (PKCS#10) is what you send a CA: your subject, your public key, the extensions you'd like (usually SANs), all signed with your private key. That self-signature is proof of possession: you can't request a certificate for someone else's public key. The private key never leaves your machine.
# New P-256 key + CSR with SANs in one step
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout example.key -out example.csr \
-subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
openssl req -in example.csr -noout -text -verify # inspect and check the self-signatureFile formats
| Format | Encoding | Typical extensions | Holds |
|---|---|---|---|
| PEM | Base64 + header lines | .pem .crt .cer .key | One or more certs, or a key |
| DER | Raw binary ASN.1 | .der .cer | Exactly one cert or key |
| PKCS#7 | PEM or DER | .p7b .p7c | Certificates/chain only, no private key (common on Windows/Java) |
| PKCS#12 | Binary, password-protected | .pfx .p12 | Private key + cert + chain in one bundle |
| JKS | Java proprietary | .jks | Java keystore (PKCS#12 is now the Java default) |
openssl x509 -in cert.pem -outform der -out cert.der # PEM → DER
openssl x509 -in cert.der -inform der -out cert.pem # DER → PEM
openssl pkcs12 -export -inkey key.pem -in cert.pem -certfile chain.pem -out bundle.pfx
openssl pkcs12 -in bundle.pfx -nodes -out everything.pem # PFX → PEM (key unencrypted!)
openssl pkcs7 -in chain.p7b -print_certs -out chain.pem # P7B → PEMInspecting real certificates
# Show the chain a server actually sends, with SNI
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# Decode the leaf
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName,basicConstraints,extendedKeyUsageBuilding a private CA (the lab version)
# 1. Root CA: self-signed, CA:TRUE, long-lived, keep offline
openssl req -x509 -new -newkey ec -pkeyopt ec_paramgen_curve:P-384 -nodes -days 3650 \
-keyout root.key -out root.pem -subj "/CN=Lab Root CA" \
-addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
# 2. Issuing CA: signed by the root, pathlen:0 = may only sign leaves
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout int.key -out int.csr -subj "/CN=Lab Issuing CA"
openssl x509 -req -in int.csr -CA root.pem -CAkey root.key -days 1825 -out int.pem \
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign")
# 3. Leaf: CA:FALSE, serverAuth, SANs
openssl x509 -req -in example.csr -CA int.pem -CAkey int.key -days 90 -out example.pem \
-extfile <(printf "basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:example.com,DNS:www.example.com")
openssl verify -CAfile root.pem -untrusted int.pem example.pem # → example.pem: OKCertificate Validation and Chains
Why there is a chain
Root CA keys are too valuable to use online, so roots sign a handful of intermediate (issuing) CAs, and those sign the end-entity ("leaf") certificates. If an intermediate is compromised, it can be revoked without replacing the root in every device on earth.
flowchart TB R["Root CA — self-signed<br/>lives in the client root store<br/>key kept offline in an HSM"] I1["Intermediate CA E6<br/>CA:TRUE, pathlen 0"] I2["Intermediate CA R11<br/>CA:TRUE, pathlen 0"] L1["Leaf: example.com<br/>CA:FALSE, EKU serverAuth"] L2["Leaf: api.example.net<br/>CA:FALSE, EKU serverAuth"] R --> I1 --> L1 R --> I2 --> L2
The server sends leaf + intermediates (never needs to send the root: the client must already have it). The client builds a path from the leaf to any root it trusts and validates every link.
-
Issuer name and key identifier match the next certificate upLink
Subject of the issuer = Issuer of the child; AKI of the child = SKI of the issuer.
-
Signature verifies with the issuer's public keyLink
With an allowed algorithm: no MD5 or SHA-1 for certificate signatures; RSA ≥ 2048 bits.
-
Inside its validity periodLink
Every certificate in the path, including the intermediates.
-
Issuer is allowed to be an issuerLink
basicConstraints CA:TRUE,keyUsage keyCertSign, and the path length so far doesn't exceed anypathLenConstraintabove it. -
Names are inside any name constraintsLink
If an intermediate is constrained to
.corp.example, a leaf forbank.combelow it is invalid. -
Leaf-only checksLeaf
Hostname in SAN, EKU
serverAuth, not revoked, enough SCTs, lifetime within root-program limits.
Path building is harder than it looks
A newer root can be signed by an older, widely trusted root so old devices still trust it. That creates multiple valid paths, and the client must pick one that works.
When the cross-signing DST Root CA X3 expired (30 Sep 2021), clients with broken path building (old OpenSSL 1.0.x) picked the expired path and failed, even though a valid path via ISRG Root X1 existed.
Browsers often hide this misconfiguration by caching intermediates or fetching them via AIA. curl, Java, Go and mobile apps don't, so the same site "works in Chrome, fails in the API client".
Servers should send leaf first, then each issuer. Sending the root is harmless but wastes bytes. Sending an unrelated intermediate can confuse strict clients.
DV, OV and EV
| Type | What the CA verifies | How it shows | Notes |
|---|---|---|---|
| DV (Domain Validated) | You control the domain: HTTP file, DNS TXT record, or TLS-ALPN challenge (ACME) | Padlock only | The vast majority of certificates; free and automated |
| OV (Organisation Validated) | DV + the organisation legally exists | Padlock only; org name visible in cert details | Policy OID 2.23.140.1.2.2 |
| EV (Extended Validation) | OV + stricter identity vetting | Was a green bar with the company name | Chrome 77 / Firefox 70 (2019) removed the EV UI: research showed users didn't notice its absence, and names could be registered to collide |
Memory hook"DV proves the domain, not the company." Every certificate proves control of a name. Only the name is cryptographically checked by the browser.
paypa1.comcan get a perfectly valid DV certificate. That's why phishing sites have padlocks, and why "look for the padlock" is no longer security advice.
Revocation
Last verified2026-10
What is this?Revocation is how a CA says "stop trusting this certificate before its expiry date", for example because the private key leaked or the certificate was mis-issued. It is the weakest part of web PKI: every mechanism has failed on privacy, reliability or scale, which is why the industry is moving to short-lived certificates instead.
| Method | How it works | Pros | Cons |
|---|---|---|---|
| CRL (Certificate Revocation List) | CA publishes a signed list of revoked serial numbers; client downloads it | Simple, cacheable | Lists grow large; refresh lag of hours to days |
| OCSP (Online Certificate Status Protocol, RFC 6960) | Client asks the CA's responder about one serial | Small, near real-time | Privacy leak (CA sees every site you visit); adds latency; usually soft-fail |
OCSP stapling (RFC 6066 status_request) | Server fetches its own signed OCSP response and sends it in the handshake | No privacy leak, no extra client round trip | Server must keep the staple fresh; clients still soft-fail if it's missing |
| OCSP Must-Staple (RFC 7633) | Cert extension: "reject me if there's no staple" | Turns soft-fail into hard-fail | Outages if the server's stapling breaks; rarely deployed |
| CRLSets (Chrome) | Google crawls CRLs and pushes a curated subset with browser updates | Local, private, fast | Only covers high-impact revocations, not every certificate |
| CRLite (Firefox) | Compresses all revocations of publicly trusted certs into a filter cascade, pushed daily | Complete, local, private | Browser-specific infrastructure |
| Short-lived certificates | Certificates expire before revocation would matter | No revocation infrastructure needed | Requires fully automated issuance (ACME) |
sequenceDiagram participant C as Client participant S as Server participant O as CA OCSP responder Note over S,O: Background, every few hours S->>O: OCSP request for my serial O-->>S: Signed response "good", valid for days Note over C,S: During the handshake C->>S: ClientHello + status_request S->>C: Certificate + stapled OCSP response Note over C: Verify CA signature<br/>on the staple. No call<br/>to the CA, no leak
ImportantThe soft-fail problemIf an OCSP check times out, almost every client continues rather than breaking the site. But an attacker who has a revoked certificate and sits on the network path can simply block the OCSP request. So plain OCSP stops nothing in exactly the scenario revocation exists for. (Adam Langley's classic line: soft-fail revocation is "like a seat belt that snaps when you crash".)
Where things stand (2026)The CA/Browser Forum made OCSP optional for CAs and requires CRLs. Let's Encrypt removed OCSP URLs from its certificates and shut its OCSP responders down in 2025. Browsers check revocation locally (CRLSets, CRLite). Maximum certificate lifetimes are being cut under ballot SC-081:
-
Until 15 March 2026Before398 days
Roughly 13 months, in force since 2020 (Apple led by policy, the Forum followed).
-
From 15 March 2026Now200 days
Domain-validation reuse also shrinks.
-
From 15 March 2027Next100 days
-
From 15 March 2029Final47 days
Domain-validation reuse drops to 10 days. Manual certificate renewal effectively stops being possible, so automation (ACME) becomes mandatory.
Cipher Suites
What is this?A cipher suite is a named bundle of algorithms the two sides agree to use. In TLS 1.2 the name spells out all four jobs: key exchange, authentication, bulk encryption, and the hash used for the PRF/MAC. TLS 1.3 slimmed it down to just the bulk cipher and hash, because key exchange and authentication are negotiated separately.
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 │ │ │ │ │ │ │ │ │ └── Hash for the PRF (and HMAC in non-AEAD suites) │ │ │ └────────── Symmetric cipher + mode │ │ └─────────────── Certificate / signature algorithm (authentication) │ └──────────────────── Key exchange └──────────────────────────── Protocol TLS_AES_128_GCM_SHA256 ← TLS 1.3: only AEAD cipher + HKDF hash
The four components
RSA (client encrypts a secret to the server's key: no forward secrecy), DH/ECDH static (no FS), DHE/ECDHE ephemeral (fresh key per handshake: forward secrecy). TLS 1.3: (EC)DHE or PSK.
How the server proves its identity: RSA or ECDSA signature (TLS 1.3 adds Ed25519/Ed448), or a pre-shared key. anon suites have none and are trivially MITM'd.
AES-GCM and ChaCha20-Poly1305 (AEAD, preferred), AES-CCM (IoT); legacy AES-CBC + HMAC; broken RC4, 3DES, DES, export ciphers, NULL.
SHA-256 or SHA-384 drives the PRF (1.2) or HKDF (1.3), and the HMAC in CBC suites. MD5 and SHA-1 suites are obsolete.
Forward secrecy, visually
Forward secrecy (often called "perfect forward secrecy", PFS) means that stealing the server's long-term private key later does not let you decrypt traffic you recorded earlier.
-
2026: an adversary records your encrypted TLS sessionsAttacker
They can't read them yet, but storage is cheap.
-
2028: the server's RSA private key leaksAttacker
Heartbleed-style bug, stolen backup, insider, or a legal order.
-
With RSA key exchange: every recorded session is now readableRSA kex
The pre-master secret was encrypted with that RSA key in each ClientKeyExchange. Decrypt it, re-derive the session keys, and read everything.
-
With ECDHE: nothing is recoverableECDHE
Each session's secret came from ephemeral DH keys that were thrown away after the handshake. The long-term key only signed the exchange. Stealing it lets you impersonate the server in future, but not decrypt the past.
Avoid, accept, prefer
| Verdict | TLS 1.2 suites | Why |
|---|---|---|
| Prefer | ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-*-AES256-GCM-SHA384, ECDHE-*-CHACHA20-POLY1305 | Forward secrecy + AEAD |
| Accept (legacy clients only) | ECDHE-*-AES128-CBC-SHA256, DHE-RSA-AES128-GCM-SHA256 (≥ 2048-bit group) | FS but CBC (timing-attack history) or slow/weak-group DHE |
| Avoid | RSA-kex suites (AES128-GCM-SHA256, etc.), anything with CBC-SHA (SHA-1 HMAC) on new deployments | No forward secrecy; ROBOT-style oracles; Lucky13 |
| Never | RC4, 3DES/DES, EXPORT, NULL, anon, MD5 | Broken: RC4 biases, Sweet32, FREAK/Logjam, no encryption, no authentication |
TLS 1.3 needs no such table. It defines only five suites, all AEAD:
| Suite | Code | Use |
|---|---|---|
TLS_AES_128_GCM_SHA256 | 0x1301 | Default everywhere; mandatory to implement |
TLS_AES_256_GCM_SHA384 | 0x1302 | Common; required by some compliance regimes |
TLS_CHACHA20_POLY1305_SHA256 | 0x1303 | Fast without AES hardware (older phones, embedded) |
TLS_AES_128_CCM_SHA256 | 0x1304 | IoT / constrained devices |
TLS_AES_128_CCM_8_SHA256 | 0x1305 | IoT only (short 8-byte tag); not for the web |
Enumerating what a server supports
nmap --script ssl-enum-ciphers -p 443 example.com # graded list per protocol version
testssl.sh --protocols --ciphers --vulnerable example.com
openssl s_client -connect example.com:443 -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' # does this one work?
openssl s_client -connect example.com:443 -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256TipIn TLS 1.2 the server usually decides from the client's list (with "server cipher preference" enabled). Modern guidance (Mozilla "intermediate") lets the client pick among AEAD suites: a phone without AES hardware will choose ChaCha20, which is faster for it.
The Record Layer
What is this?Everything TLS sends — handshake messages, alerts, application data — is wrapped in records. The record layer splits data into chunks of up to 16 KB, protects each chunk with the current keys, and tags it with a content type.
- Content type
- 20 change_cipher_spec · 21 alert · 22 handshake · 23 application_data · 24 heartbeat
- Version
- 0x0303 for TLS 1.2 and (frozen) for TLS 1.3; 0x0301 allowed in the first ClientHello
- Length
- Up to 2^14 (16,384) bytes of plaintext; up to 2^14 + 256 of ciphertext in TLS 1.3
- Fragment
- The payload: plaintext before keys exist, ciphertext + tag afterwards
Handshake messages have their own small header inside handshake records. One handshake message can span several records, and one record can carry several handshake messages.
- msg_type
- 1 ClientHello · 2 ServerHello · 4 NewSessionTicket · 8 EncryptedExtensions · 11 Certificate · 12 ServerKeyExchange · 13 CertificateRequest · 14 ServerHelloDone · 15 CertificateVerify · 16 ClientKeyExchange · 20 Finished · 24 KeyUpdate
- length
- Length of the message body (up to 16 MB)
- body
- The message itself
What you'd see in a hex dump of a ClientHello:
16 03 01 02 00 01 00 01 fc 03 03 a3 4f 9c ... (32 random bytes) │ │ │ │ │ │ │ │ │ │ │ └─ legacy_version 0x0303 │ │ │ │ └──────────── handshake length 0x0001fc = 508 │ │ │ └─────────────── msg_type 1 = ClientHello │ │ └─────────────────────── record length 0x0200 = 512 │ └───────────────────────────── record version 0x0301 (compat value) └──────────────────────────────── content type 0x16 = 22 = handshake
TLS 1.3 hides the real content type
- Outer type
- Always 23 (application_data) once keys are in use
- Version
- Always 0x0303
- Length
- Ciphertext length
- Encrypted: content
- Handshake, alert or application data
- Encrypted: real type
- The true content type, inside the encryption
- Encrypted: padding
- Optional zeros to hide the true length
- AEAD tag
- Authenticates everything (header bytes are the additional data)
The nonce for each record is the per-direction IV XORed with a 64-bit sequence number that both sides count but never send. Drop, replay or reorder a record and the tag check fails. This is TLS's anti-replay mechanism within a connection.
Alerts
Alerts are two bytes: level (1 warning, 2 fatal) and description. In TLS 1.3 everything except close_notify and user_canceled is fatal.
| Code | Alert | Usually means |
|---|---|---|
| 0 | close_notify | Clean shutdown. Missing it allows truncation attacks |
| 10 | unexpected_message | State machine violation |
| 20 | bad_record_mac | Decryption/tag failure: tampering or a key mismatch |
| 40 | handshake_failure | No common parameters |
| 42 / 45 / 48 | bad_certificate / certificate_expired / unknown_ca | Peer rejected the certificate |
| 70 | protocol_version | No common TLS version |
| 71 | insufficient_security | Offered parameters too weak |
| 86 | inappropriate_fallback | Downgrade attempt detected (TLS_FALLBACK_SCSV) |
| 112 | unrecognized_name | No certificate for that SNI |
| 116 | certificate_required | mTLS server got no client certificate (1.3) |
| 120 | no_application_protocol | ALPN mismatch |
The TLS 1.2 Handshake
With RSA key exchange (no forward secrecy)
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello<br/>version, client random, suites, extensions S->>C: ServerHello<br/>chosen suite, server random, session ID S->>C: Certificate (RSA public key) S->>C: ServerHelloDone Note over C: Pick 48-byte<br/>pre-master secret,<br/>encrypt to RSA key C->>S: ClientKeyExchange (encrypted pre-master) C->>S: ChangeCipherSpec C->>S: Finished (encrypted) S->>C: ChangeCipherSpec S->>C: Finished (encrypted) Note over C,S: 2 round trips before application data
With ECDHE (forward secrecy)
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello<br/>suites, supported_groups, sig algs S->>C: ServerHello (ECDHE_RSA suite) S->>C: Certificate S->>C: ServerKeyExchange<br/>curve + ephemeral public key<br/>signed with cert private key S->>C: ServerHelloDone Note over C: Verify signature,<br/>make own ephemeral<br/>key, derive secret C->>S: ClientKeyExchange (client ephemeral public key) C->>S: ChangeCipherSpec + Finished S->>C: ChangeCipherSpec + Finished
The signature in ServerKeyExchange is the authentication. It covers both randoms and the server's DH public key, so an attacker can't swap in their own DH key without the certificate's private key.
From secret to keys (TLS 1.2)
-
Pre-master secretSecret
RSA: 48 random bytes chosen by the client. ECDHE: the shared x-coordinate.
-
Master secret = PRF(pre-master, "master secret", client_random + server_random)PRF48 bytes
With the Extended Master Secret extension (RFC 7627) the seed is the session hash instead, which binds the master secret to the whole handshake (fixes the Triple Handshake attack).
-
Key block = PRF(master, "key expansion", server_random + client_random)PRF
Sliced into client/server MAC keys (CBC suites), client/server encryption keys, and client/server IVs.
-
Finished = PRF(master, "client finished" or "server finished", hash(all handshake messages))Check12 bytes
Proves both sides saw the same handshake and derived the same keys.
Session resumption
A full handshake costs 2 round trips plus public-key operations. Resumption reuses the master secret from an earlier session in an abbreviated handshake (1 round trip, no certificate, no key exchange).
The server keeps a cache of master secrets keyed by a session ID. The client sends the ID in its next ClientHello. Hard to scale across a server fleet (shared cache needed).
The server encrypts the session state with a ticket key only it knows, and hands the blob to the client. Stateless for the server, but the ticket key becomes a crown jewel: whoever steals it can decrypt every session resumed with it, which breaks forward secrecy until the key is rotated.
sequenceDiagram participant C as Client participant S as Server C->>S: ClientHello + session ticket (or session ID) S->>C: ServerHello (same session) S->>C: ChangeCipherSpec + Finished C->>S: ChangeCipherSpec + Finished Note over C,S: 1 RTT, no Certificate, no key exchange
Mutual TLS (client certificates)
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello S->>C: ServerHello, Certificate, ServerKeyExchange S->>C: CertificateRequest<br/>acceptable CAs + sig algs S->>C: ServerHelloDone C->>S: Certificate (client cert, in the clear in 1.2) C->>S: ClientKeyExchange C->>S: CertificateVerify<br/>signature over handshake so far C->>S: ChangeCipherSpec + Finished S->>C: ChangeCipherSpec + Finished
WarningIn TLS 1.2 the client certificate is sent unencrypted, so a passive observer learns the client's identity (name, email, device ID). TLS 1.3 encrypts it. This matters for mTLS-based device identity and zero-trust deployments.
Renegotiation
TLS 1.2 allowed a new handshake inside an existing connection, used for "this URL needs a client certificate" or rekeying. It caused CVE-2009-3555 (see attacks below) and DoS issues. The renegotiation_info extension (RFC 5746) fixed the injection flaw. TLS 1.3 removed renegotiation entirely.
TLS Extensions
Extensions are typed fields at the end of ClientHello/ServerHello that let TLS grow without new versions. Each side ignores extension types it doesn't recognise, which is what makes them safe to add. A server may only send an extension the client offered.
| Extension (code) | What it does | Security note |
|---|---|---|
server_name (0) | SNI: which hostname the client wants | Plaintext before ECH; used by censors and firewalls |
status_request (5) | Ask the server to staple OCSP | Privacy-preserving revocation |
supported_groups (10) | Curves / DH groups offered | Dropping weak groups stops Logjam-style downgrades |
signature_algorithms (13) | Which signature + hash combos are acceptable | Prevents being forced into SHA-1 or PKCS#1 v1.5 |
application_layer_protocol_negotiation (16) | ALPN: h2, http/1.1, … | Prevents cross-protocol attacks (ALPACA) when enforced |
signed_certificate_timestamp (18) | Deliver CT SCTs in the handshake | Alternative to embedding SCTs in the cert |
encrypt_then_mac (22) | MAC the ciphertext, not the plaintext, in CBC suites | Kills padding-oracle classes (RFC 7366) |
extended_master_secret (23) | Bind master secret to the session hash | Fixes Triple Handshake (RFC 7627) |
session_ticket (35) | Stateless resumption | Ticket key compromise breaks FS |
heartbeat (15) | Keep-alive for DTLS | The extension Heartbleed lived in; disable it |
renegotiation_info (65281) | Bind renegotiations to the previous handshake | Fix for CVE-2009-3555 |
One IP can host thousands of HTTPS sites because the server reads SNI to pick the certificate. No SNI → default certificate → name mismatch. Firewalls and CDNs route and filter on SNI, and domain fronting abused the gap between SNI and the HTTP Host header.
Configure the server (ssl_stapling on in nginx) and check with openssl s_client -status. Look for OCSP Response Status: successful.
Rotate ticket keys often (hours), share them securely across the fleet, and never persist them to disk. A ticket key that lives for months quietly turns months of traffic into one decryptable archive.
Decrypting and Inspecting TLS
There are exactly two ways to read TLS traffic you captured: have the session keys, or be in the middle of the connection.
Load the server's private key into Wireshark; it decrypts each ClientKeyExchange to get the pre-master secret. Useless against ECDHE and TLS 1.3 — the private key never touched the session secret.
Browsers, curl and most TLS libraries write per-session secrets when SSLKEYLOGFILE is set. Wireshark reads the file and decrypts everything, including TLS 1.3 and QUIC.
A proxy terminates TLS with its own certificate (trusted via an installed root) and opens a second TLS connection to the real server. Used by corporate inspection, Burp, mitmproxy — and by malware.
EDR or eBPF hooks read plaintext before encryption (e.g. uprobes on SSL_write). No crypto broken, but full visibility on that host.
# SSLKEYLOGFILE format (NSS key log) — one line per secret
CLIENT_RANDOM <client_random_hex> <master_secret_hex> ← TLS 1.2
CLIENT_HANDSHAKE_TRAFFIC_SECRET <client_random_hex> <secret_hex> ← TLS 1.3
SERVER_HANDSHAKE_TRAFFIC_SECRET <client_random_hex> <secret_hex>
CLIENT_TRAFFIC_SECRET_0 <client_random_hex> <secret_hex>
SERVER_TRAFFIC_SECRET_0 <client_random_hex> <secret_hex>
EXPORTER_SECRET <client_random_hex> <secret_hex>SSLKEYLOGFILE=$HOME/tls-keys.log curl -s https://example.com -o /dev/null
# Wireshark: Preferences → Protocols → TLS → (Pre)-Master-Secret log filename
# Useful display filters:
# tls.handshake.type == 1 ClientHellos
# tls.handshake.extensions_server_name == "example.com"
# tls.record.content_type == 21 alerts
# tls.handshake.ja4 JA4 fingerprints (recent Wireshark)CautionA key log file is the session's crown jewels. Malware that sets
SSLKEYLOGFILEsystem-wide gets silent decryption of every browser session. Hunt for that environment variable on endpoints, and never leave it set on a workstation after debugging.
TLS Defenses: HSTS, CAA and Certificate Transparency
HSTS (HTTP Strict Transport Security, RFC 6797)
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadHow long (seconds) the browser must use HTTPS only for this host. Two years is the preload requirement.
Applies the policy to every subdomain. Dangerous if any internal subdomain can't do HTTPS.
Consent to be hard-coded into browsers' preload list (hstspreload.org), which removes the first-visit gap.
Under HSTS, certificate errors have no "proceed anyway" button: users can't click through a MITM warning.
HPKP (HTTP Public Key Pinning, RFC 7469) tried to go further by pinning keys, but sites bricked themselves and attackers could pin hostile keys ("ransom pinning"). Chrome removed it in 2018–2019. Certificate Transparency replaced pinning as the web's defence against rogue certificates.
CAA (Certification Authority Authorization, RFC 8659)
A DNS record that says which CAs may issue for your domain. Since September 2017 every public CA must check it before issuing.
example.com. CAA 0 issue "letsencrypt.org" ; only Let's Encrypt may issue
example.com. CAA 0 issuewild ";" ; nobody may issue wildcards
example.com. CAA 0 iodef "mailto:security@example.com" ; report violations here
example.com. CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123" ; RFC 8657: only *our* accountCAA is a check on honest CAs, not on attackers: a compromised CA can ignore it. Combine it with CT monitoring.
Certificate Transparency (RFC 6962, v2 RFC 9162)
What is this?A set of public, append-only logs that every publicly trusted certificate must be submitted to. Anyone can search them, so a domain owner can see every certificate ever issued for their names. Browsers refuse certificates that can't prove they were logged.
-
CA builds a precertificateCA
The final certificate plus a "poison" extension that makes it unusable for TLS.
-
Submit to several independent logsCT logs
Each log returns an SCT (Signed Certificate Timestamp): a signed promise to add it within the Maximum Merge Delay (24 h).
-
Embed the SCTs and issue the real certificateCA
SCTs can also be delivered via the TLS extension or a stapled OCSP response.
-
Browser checks the SCTsBrowser
Valid signatures from logs it trusts, from distinct operators. Chrome requires 2 or 3 depending on lifetime.
-
Monitors watch the logsMonitors
Domain owners (or services such as crt.sh, Cloudflare, Facebook) alert on unexpected certificates for their names.
-
Auditors check the logs are honestAuditors
Inclusion proofs (is this cert in the tree?) and consistency proofs (did the log only append?).
Why a Merkle tree?The log is a binary hash tree. Each leaf is a hash of one certificate, and each parent is the hash of its two children. The root hash (signed as the Signed Tree Head, STH) commits to every entry.
Root = H(H12 ‖ H34) ← signed tree head (STH)
/ \
H12 = H(H1 ‖ H2) H34 = H(H3 ‖ H4)
/ \ / \
H1 H2 H3 H4
cert A cert B cert C cert D
Inclusion proof for cert C: give the client H4 and H12.
Client computes H3 = hash(C) → H34 = H(H3 ‖ H4) → Root = H(H12 ‖ H34), compares with the signed STH.
Proof size = log2(n) hashes: ~20-30 hashes even for a billion certificates.
Consistency proof: shows tree of size 4 is a prefix of tree of size 8, i.e. the log only appended
and never rewrote history.Memory hook"CT doesn't prevent mis-issuance; it makes it impossible to hide." A rogue CA can still sign a certificate for your domain. But to be accepted by browsers it has to be logged publicly, and your monitor fires within hours. DigiNotar (2011) went undetected for weeks. Under CT, the same attack is visible to the world.
Major TLS Attacks and Failures
Each of these broke something specific, and each fix is now a rule in modern configs.
| Year | Attack | CVE | What broke | Fix |
|---|---|---|---|---|
| 2009 | Insecure renegotiation | CVE-2009-3555 | Attacker's data spliced before the victim's renegotiated session | renegotiation_info (RFC 5746); removed in 1.3 |
| 2011 | BEAST | CVE-2011-3389 | Predictable CBC IVs in TLS 1.0 let a chosen-plaintext attacker decrypt cookies | TLS 1.1+ explicit IVs, 1/n-1 record splitting |
| 2012 | CRIME | CVE-2012-4929 | TLS compression leaked secrets through ciphertext length | Disable TLS compression (removed in 1.3) |
| 2013 | BREACH | CVE-2013-3587 | Same idea via HTTP-level gzip | Don't compress secrets with attacker input; token masking |
| 2013 | Lucky13 | CVE-2013-0169 | Timing side channel in CBC MAC-then-encrypt padding checks | Constant-time code; AEAD suites |
| 2013–15 | RC4 biases | — | Statistical keystream biases recover plaintext | RC4 prohibited (RFC 7465) |
| 2014 | Heartbleed | CVE-2014-0160 | OpenSSL heartbeat over-read leaked up to 64 KB of memory: keys, cookies | Patch, revoke and reissue certificates, rotate keys |
| 2014 | POODLE | CVE-2014-3566 | SSL 3.0 CBC padding not checked; downgrade dance forced SSL 3.0 | Disable SSL 3.0; TLS_FALLBACK_SCSV |
| 2014 | Triple Handshake | — | Resumption + renegotiation let a MITM sync master secrets across connections | Extended Master Secret (RFC 7627) |
| 2015 | FREAK | CVE-2015-0204 | Clients accepted 512-bit export RSA when not asked | Remove export suites |
| 2015 | Logjam | CVE-2015-4000 | Downgrade to 512-bit export DHE; precomputation on common 1024-bit groups | ≥ 2048-bit DH groups, prefer ECDHE |
| 2016 | DROWN | CVE-2016-0800 | SSLv2 on any server sharing the RSA key enabled decryption of TLS sessions | Disable SSLv2 everywhere; don't share keys |
| 2016 | Sweet32 | CVE-2016-2183 | 64-bit block ciphers (3DES) collide after ~32 GB (birthday bound) | Remove 3DES |
| 2017 | ROBOT | CVE-2017-13099 et al. | Bleichenbacher padding oracle in RSA key exchange, still alive 19 years later | Disable RSA key exchange |
| 2020 | Raccoon | CVE-2020-1968 | Timing leak from leading-zero stripping of DH secrets | Ephemeral, never-reused DH keys; TLS 1.3 |
| 2021 | ALPACA | — | Cross-protocol: redirect HTTPS traffic to an FTP/SMTP server sharing the certificate | Strict ALPN and SNI checking |
Insecure renegotiation, step by step
sequenceDiagram participant V as Victim client participant A as Attacker (MITM) participant S as Server A->>S: Full TLS handshake (as the attacker) A->>S: "GET /transfer?to=attacker" + "X-Ignore: " (no newline) V->>A: ClientHello (victim starts TLS) A->>S: Forwards it as a RENEGOTIATION in the attacker's session S-->>V: Handshake completes, encrypted end to end with the victim V->>S: "GET /home" + victim's cookie Note over S: Server sees ONE<br/>request: attacker's<br/>line + victim's<br/>cookie
The server glued the attacker's plaintext prefix onto the victim's authenticated request, because nothing tied the renegotiated handshake to the connection it happened inside. RFC 5746 fixed this by making each renegotiation include the previous handshake's Finished values.
Compression, renegotiation, export suites, SSLv2 compatibility, heartbeat: almost every major attack lived in a feature most deployments didn't need.
FREAK, Logjam, POODLE and DROWN all forced a connection onto the weakest thing both sides still supported. Removing weak options beats negotiating safely around them.
Padding oracles and timing leaks kept recurring until AEAD made encryption and authentication one operation.
Heartbleed needed no cryptanalysis, just a missing bounds check. Memory-safe TLS stacks (rustls, Go) are partly a response.
TLS 1.3: What Changed and Why
What is this?TLS 1.3 (RFC 8446, 2018) is less an upgrade than a cleanup. It removes every feature that produced a major attack, makes forward secrecy mandatory, encrypts most of the handshake, and cuts a full handshake to one round trip.
RSA key exchange, static DH, CBC, RC4, 3DES, SHA-1/MD5 in signatures, compression, renegotiation, custom DHE groups, ChangeCipherSpec, DSA, export suites.
Full handshakes always use (EC)DHE. Resumption can still use (EC)DHE with the PSK (psk_dhe_ke).
Everything after ServerHello is encrypted, including the server certificate and (for mTLS) the client certificate.
The client sends its key share in the first message. Resumed sessions can send data in the first flight (0-RTT).
HKDF-based key schedule with separate secrets per phase, replacing the 1.2 PRF.
CertificateVerify signs a hash of the entire handshake, not just the key exchange parameters (1.2's weakness exploited by Logjam-style attacks).
TLS 1.2 vs TLS 1.3
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Handshake RTT | 2 (1 with resumption) | 1 (0 with 0-RTT) |
| Forward secrecy | Optional | Mandatory (full handshakes) |
| Cipher suites | 300+ defined (including weak) | 5, all AEAD |
| Suite name covers | Kex + auth + cipher + hash | Cipher + hash only |
| Certificate in handshake | Plaintext | Encrypted |
| Client certificate (mTLS) | Plaintext | Encrypted |
| RSA key transport | Allowed | Removed |
| RC4, 3DES, CBC, export ciphers | Allowed | Removed |
| Compression / renegotiation | Allowed | Removed (KeyUpdate + post-handshake auth instead) |
| Downgrade protection | Weak (Finished only, SCSV) | Transcript signature + ServerHello.random sentinel |
| Key derivation | PRF(master_secret) | HKDF key schedule |
| Session resumption | Session IDs / tickets | PSK (tickets carry PSK identities) |
| Record content type | Plaintext | Hidden inside encryption |
Memory hookTLS 1.3 is 1-RTT because the client "guesses" and is almost always right. In TLS 1.2 the client first asks which key-exchange to use, waits, then sends its key — two round trips of negotiating. TLS 1.3 collapses this: there's only one key-exchange family (ECDHE), so the client just sends its ECDHE key share in the very first message (the ClientHello) without asking. The server replies with its own share and is already able to encrypt the certificate. Mnemonic: 1.2 negotiates-then-acts; 1.3 acts immediately because there's nothing left to negotiate. That same "send key share upfront" is also why the certificate is now encrypted (a passive eavesdropper can't see which site you visited from the cert) and why forward secrecy is automatic (ECDHE every time, RSA key transport deleted).
The full handshake
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello<br/>supported_versions, key_share,<br/>signature_algorithms, SNI, ALPN S->>C: ServerHello<br/>key_share, supported_versions Note over C,S: Handshake keys derived — all below is encrypted S->>C: EncryptedExtensions (ALPN, ...) S->>C: CertificateRequest (only for mTLS) S->>C: Certificate S->>C: CertificateVerify S->>C: Finished Note over S: May already send<br/>data (0.5-RTT) C->>S: Certificate + CertificateVerify (only for mTLS) C->>S: Finished Note over C,S: Application traffic keys in use S-->>C: NewSessionTicket (any time after)
When the client guessed wrong: HelloRetryRequest
If the client's key share is for a group the server won't use (say the client sent only X25519MLKEM768 and the server only does P-256), the server replies with a HelloRetryRequest. It's a ServerHello whose random field is the fixed value SHA-256("HelloRetryRequest"). The cost is one extra round trip.
sequenceDiagram participant C as Client participant S as Server C->>S: ClientHello, key_share X25519MLKEM768 S->>C: HelloRetryRequest<br/>"use secp256r1" + cookie C->>S: ClientHello again<br/>key_share secp256r1 + cookie S->>C: ServerHello ... Finished C->>S: Finished
The cookie extension lets the server stay stateless during the retry (useful against DoS, and essential in DTLS/QUIC).
Renegotiation is gone — what replaced it
Either side can send a KeyUpdate message to roll its sending keys forward (traffic_secret_N+1 = HKDF-Expand-Label(secret_N, "traffic upd", "", L)). Used for long-lived connections; no new handshake.
If the client advertised post_handshake_auth, the server can send a CertificateRequest later in the connection (e.g. when the user reaches /admin). HTTP/2 forbids it, so in practice it's used with HTTP/1.1.
Middleboxes, GREASE and the version number that lies
When TLS 1.3 was tested on the real internet, a few percent of connections failed because firewalls and inspection boxes choked on anything unfamiliar. The fix was to make TLS 1.3 look like a TLS 1.2 session resumption:
Record version and legacy_version stay 0x0303 (TLS 1.2). The real version is negotiated in the supported_versions extension.
The client sends a random 32-byte legacy_session_id and the server echoes it, as in 1.2 resumption.
Both sides may send a meaningless CCS record, which middleboxes expect to see (RFC 8446 appendix D.4, "middlebox compatibility mode").
Clients sprinkle random reserved values into cipher, extension, group and version lists. Servers that crash on unknown values get caught now, so the protocol stays extensible ("don't let the joints rust").
Downgrade protectionA TLS 1.3 server forced down to TLS 1.2 puts the bytes 44 4F 57 4E 47 52 44 01 ("DOWNGRD\x01") at the end of ServerHello.random (...00 for TLS 1.1 or lower). The random is covered by the signature, so an attacker can't remove it, and a 1.3-capable client that sees it aborts.
Forward secrecy and the visibility debate
Mandatory forward secrecy broke a long-standing enterprise practice: passive decryption by loading the server's RSA private key into a monitoring appliance. With ECDHE there's nothing to load. Organisations now choose between:
A load balancer or proxy ends TLS, inspects, and starts a new TLS connection. The usual answer.
Servers log session secrets (key log format) to a secure collector. Works, but key handling is now your problem.
EDR / eBPF reads plaintext on the host.
ETSI's Enterprise Transport Security (ETS, formerly eTLS) reuses a static DH key to restore passive decryption. The IETF rejected the idea; it deliberately removes forward secrecy.
Decrypting TLS 1.3
The only options are the key log file or being an active MITM. The RSA-private-key trick does not work, not even with an RSA certificate, because RSA only signs in TLS 1.3.
TLS 1.3 Under the Hood
The key schedule
TLS 1.3 derives every key from three chained secrets using HKDF (RFC 5869). HKDF-Extract concentrates entropy into a fixed-size secret. HKDF-Expand-Label stretches a secret into labelled outputs (all labels are prefixed with "tls13 "). Each stage mixes in new input and hashes of the handshake transcript so far.
PSK (or zeros)
│
0 ──▶ HKDF-Extract ══▶ EARLY SECRET ──┬──▶ binder_key
│ ├──▶ client_early_traffic_secret ← 0-RTT
│ └──▶ early_exporter_master_secret
▼
Derive-Secret("derived")
│
(EC)DHE ─▶ HKDF-Extract ══▶ HANDSHAKE SECRET ──┬──▶ client_handshake_traffic_secret
│ └──▶ server_handshake_traffic_secret
▼
Derive-Secret("derived")
│
0 ──▶ HKDF-Extract ══▶ MASTER SECRET ──┬──▶ client_application_traffic_secret_0
├──▶ server_application_traffic_secret_0
├──▶ exporter_master_secret
└──▶ resumption_master_secret| Secret | HKDF label | Transcript hashed in | Protects |
|---|---|---|---|
| binder_key | ext binder / res binder | (empty) | PSK binder in the ClientHello |
| client_early_traffic_secret | c e traffic | ClientHello | 0-RTT early data |
| client / server handshake traffic | c hs traffic / s hs traffic | ClientHello … ServerHello | EncryptedExtensions → Finished |
| client / server application traffic | c ap traffic / s ap traffic | ClientHello … server Finished | All application data |
| exporter_master_secret | exp master | ClientHello … server Finished | Keying material for applications |
| resumption_master_secret | res master | ClientHello … client Finished | PSKs in future NewSessionTickets |
Derive-Secret(Secret, Label, Messages) = HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length), and every label is prefixed with "tls13 " on the wire.
-
write_key = HKDF-Expand-Label(traffic_secret, "key", "", key_length)Keys
16 bytes for AES-128-GCM, 32 for AES-256-GCM / ChaCha20.
-
write_iv = HKDF-Expand-Label(traffic_secret, "iv", "", 12)Keys
-
per-record nonce = write_iv XOR (64-bit sequence number, left-padded)Nonce
Unique for every record without sending anything; the sequence number resets to 0 when keys change.
-
AEAD-Encrypt(write_key, nonce, additional_data = record header, plaintext = content ‖ type ‖ padding)Seal
What the signatures and MACs cover
Signature over: 64 bytes of 0x20, the string "TLS 1.3, server CertificateVerify", a 0x00 byte, then the transcript hash up to the Certificate. The fixed prefix stops a signature from being reused in another context (or by TLS 1.2).
finished_key = HKDF-Expand-Label(handshake_traffic_secret, "finished", "", Hash.length), then verify_data = HMAC(finished_key, transcript hash). Proves key possession and that both sides saw identical messages.
exporter_master lets applications derive extra keying material bound to the session (RFC 5705/8446), e.g. for token binding or channel binding in authentication protocols.
Session resumption with PSKs
After the handshake the server sends one or more NewSessionTicket messages. Each ticket identifies a PSK derived from resumption_master_secret. The ticket blob itself is opaque to the client: usually the server's encrypted session state.
- ticket_lifetime
- Seconds the ticket may be used; at most 604,800 (7 days)
- ticket_age_add
- Random value added to the client's reported ticket age, so passive observers can't link resumptions by age
- ticket_nonce
- Makes each ticket's PSK unique: PSK = HKDF-Expand-Label(resumption_master, "resumption", nonce, L)
- ticket
- The opaque identity the client sends back (often server-encrypted state)
- extensions
- e.g. early_data with max_early_data_size (0-RTT allowed)
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello<br/>key_share + psk_key_exchange_modes psk_dhe_ke<br/>pre_shared_key (ticket identity + binder) S->>C: ServerHello<br/>pre_shared_key (selected) + key_share S->>C: EncryptedExtensions S->>C: Finished C->>S: Finished Note over C,S: No Certificate / CertificateVerify<br/>PSK proves identity, (EC)DHE keeps forward secrecy
Resume with the PSK alone. Cheapest, but the session keys depend only on the PSK: no forward secrecy for the resumed session if the ticket key leaks.
PSK + a fresh (EC)DHE exchange. Forward secrecy preserved. What browsers use.
An HMAC over the ClientHello (minus the binders) keyed from the PSK. Proves the client really holds the PSK and binds it to this exact hello. pre_shared_key must be the last extension because of it.
0-RTT early data
A returning client can encrypt application data with client_early_traffic_secret (derived from the PSK) and send it in the same flight as the ClientHello.
sequenceDiagram autonumber participant C as Client participant S as Server C->>S: ClientHello + early_data + pre_shared_key C->>S: Early data: GET /home (0-RTT keys) S->>C: ServerHello + EncryptedExtensions (early_data accepted) S->>C: Finished S-->>C: Response to GET /home (0.5-RTT) C->>S: EndOfEarlyData + Finished
Caution0-RTT data can be replayedIt's encrypted under keys derived only from the PSK and the ClientHello, with no fresh server input. An attacker can capture that first flight and send it again, to the same server or another node in the fleet. It also has no forward secrecy against PSK compromise.
The server remembers which tickets were used and rejects repeats. Hard across a global fleet.
Remember recent ClientHello randoms within a time window and reject duplicates.
Reject early data whose obfuscated ticket age doesn't match expectations (limits the replay window).
Only accept 0-RTT for safe, idempotent requests (GET without side effects). CDNs mark requests with Early-Data: 1, and origins can answer 425 Too Early (RFC 8470) to force a retry after the handshake.
Mutual TLS in 1.3
The server's CertificateRequest comes inside the encrypted flight, and the client's Certificate + CertificateVerify are encrypted too, so a passive observer no longer learns client identities. The request can carry signature_algorithms, certificate_authorities (acceptable CAs) and oid_filters (required certificate extensions). A client with no suitable certificate sends an empty Certificate; the server decides whether to abort (certificate_required, alert 116).
TLS 1.3 Extensions
In TLS 1.3, extensions carry most of the negotiation, and each extension may only appear in specific messages: CH (ClientHello), SH (ServerHello), EE (EncryptedExtensions), CT (Certificate), CR (CertificateRequest), NST (NewSessionTicket), HRR (HelloRetryRequest).
| Extension (code) | Appears in | Purpose |
|---|---|---|
supported_versions (43) | CH, SH, HRR | The real version negotiation (0x0304) |
key_share (51) | CH, SH, HRR | (EC)DHE public values; HRR names the group the server wants |
signature_algorithms (13) | CH, CR | Algorithms acceptable for CertificateVerify signatures |
signature_algorithms_cert (50) | CH, CR | Algorithms acceptable for signatures inside certificates (if different) |
supported_groups (10) | CH, EE | Groups the client supports (server may list its preferences in EE) |
pre_shared_key (41) | CH, SH | PSK identities and binders; must be last in CH |
psk_key_exchange_modes (45) | CH | psk_ke or psk_dhe_ke |
early_data (42) | CH, EE, NST | Request / accept 0-RTT; max size in NST |
cookie (44) | CH, HRR | Stateless HelloRetryRequest; DoS protection |
server_name (0) | CH, EE | SNI (EE carries an empty ack) |
application_layer_protocol_negotiation (16) | CH, EE | ALPN, now encrypted in the server's answer |
status_request (5) | CH, CR, CT | OCSP staple now rides per certificate in the Certificate message |
signed_certificate_timestamp (18) | CH, CR, CT | SCTs per certificate entry |
post_handshake_auth (49) | CH | Client allows a later CertificateRequest |
certificate_authorities (47) | CH, CR | Hint which CAs are acceptable |
encrypted_client_hello (0xfe0d) | CH, EE, HRR | ECH: the real ClientHello encrypted to the server's public key |
The first governs the live handshake signature (proof of key possession). The second governs what CA signatures the client can verify in the chain. A client might sign-check RSA-PSS in the handshake while accepting PKCS#1 v1.5 signatures in certificates.
In a HelloRetryRequest the server can pack its state into a cookie, forget the connection, and rebuild state when the client echoes it. A return-routability check: spoofed source addresses never see the cookie.
The client offers h2, http/1.1 (or h3 in QUIC); the server picks one in EncryptedExtensions. Strict ALPN enforcement is a defence against cross-protocol attacks (ALPACA).
Lets a server require a client certificate only for sensitive paths, without TLS 1.2-style renegotiation. Not allowed in HTTP/2, so modern designs authenticate the client up front or at the application layer.
The client fetches an ECHConfig from the HTTPS DNS record, encrypts its real ClientHello (with the real SNI) to that key, and wraps it in an outer ClientHello naming a shared front-end. Clients without a config send GREASE ECH so that real ECH traffic doesn't stand out.
TLS Fingerprinting (JA3 / JA4)
Last verified2026-06
What is this?When a client opens a TLS connection, its very first message — the ClientHello — advertises how it wants to talk: which TLS version, which cipher suites, which extensions, which elliptic-curve groups, and so on. Different software builds that list in its own distinctive order. A Python requests script, Chrome, and a piece of malware each produce a recognisably different ClientHello. TLS fingerprinting turns that ClientHello into a short hash so you can identify the client software without decrypting anything.
The key point: the ClientHello is sent in the clear, before encryption starts (it has to be — it's negotiating the encryption). So a network sensor that can't read your encrypted traffic can still read the fingerprint. You're identifying the tool, not the content.
Why it mattersEncryption blinds the defender to payload, but the handshake metadata still leaks. A real browser and a malware C2 client both speak TLS, but they build their ClientHello differently — so the fingerprint distinguishes "Chrome on macOS" from "Cobalt Strike's default profile" or "a Go program" even though both connections are encrypted. It's a top-of-Pyramid-of-Pain signal: it targets the attacker's tooling, not an IP or hash they can trivially change.
How it works
JA3 (client): hash of TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats
taken from the ClientHello, joined with "-" and "," then MD5'd.
ClientHello fields ──▶ "771,4865-4866-...,0-23-65281-10-11-...,29-23-24,0"
──▶ MD5 ──▶ e7d705a3286e19ea42f587b344ee6865
JA3S (server): the same idea applied to the ServerHello (version, chosen cipher,
extensions). JA3 + JA3S together fingerprint *both ends* of a session.was published by Salesforce in 2017. Its weakness: it MD5s an ordered list, and TLS 1.3 clients are allowed to shuffle/pad extensions (GREASE values, the padding extension), which makes the raw JA3 unstable and easy to randomise — so a single piece of software can emit many JA3 hashes, and attackers deliberately "JA3-randomise."
(and the JA4+ family, released by FoxIO in 2023) is the modern successor. It fixes JA3's fragility by sorting the cipher and extension lists before hashing (so GREASE/reordering doesn't change the result) and by using a human-readable, structured format instead of one opaque MD5. A JA4 fingerprint looks like t13d1516h2_8daaf6152771_b186095e22b6 — the first chunk is readable (t=TCP, 13=TLS 1.3, d=domain/SNI present, counts of ciphers/extensions, ALPN), the rest are truncated hashes of the sorted lists. JA4+ extends the idea to other protocols: JA4H (HTTP), JA4S (server response), JA4L (latency/location), JA4X (X.509 certificate).
JA4 fingerprint, decoded: t 13 d 15 16 h2 _ 8daaf6152771 _ b186095e22b6 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ hash of sorted extensions │ │ │ │ │ │ └──────────────── hash of sorted cipher suites │ │ │ │ │ └─ ALPN (h2 = HTTP/2) │ │ │ │ └──── extension count (16) │ │ │ └─────── cipher count (15) │ │ └────────── SNI present (d = domain) │ └──────────── TLS version (13 = TLS 1.3, read from the supported_versions │ extension — not the legacy record version, which 1.3 pins to 1.2) └─────────────── transport (t = TCP, q = QUIC)
Security angle
maintain a set of fingerprints for known-bad tooling (Cobalt Strike, Sliver, Metasploit, common Go/Python malware stacks) and alert on them, or invert it — baseline the fingerprints normal in your environment and hunt the rare ones. A ClientHello claiming to be "Chrome" (by User-Agent) whose JA4 matches a Python client is a classic lie-detector for malware and scrapers.
the same trick spots automated clients that forge a browser User-Agent but can't forge the TLS stack underneath — a high-value signal for a consumer app fighting credential-stuffing and scraping. (This is why some bot frameworks now ship full browser TLS stacks specifically to match a real JA3/JA4.)
JA3 is easy to randomise, which is exactly why JA4's sorting matters. Even JA4 only identifies the stack, not intent — a benign tool and malware that both use Go's standard library share a fingerprint, so treat it as one pivot, not a verdict. Combine it with destination, beaconing regularity, and JA3S/JA4S on the server side.
Hash-based MAC (HMAC)
HMAC(key, message) = Hash((key XOR opad) ∥ Hash((key XOR ipad) ∥ message))- Prevents length extension attacks (which plain
Hash(key ∥ message)is vulnerable to) - Security depends entirely on key secrecy (not on collision resistance of underlying hash)
HMAC-SHA256is the current standard (used in HKDF, JWT HS256, AWS request signing)
Key Derivation Functions (KDF)
| KDF | Use Case | How |
|---|---|---|
| HKDF (RFC 5869) | TLS 1.3, WireGuard | Extract entropy then expand |
| PBKDF2 | Password hashing (legacy) | HMAC-based; iteration count for cost |
| bcrypt | Password hashing | Blowfish-based; work factor |
| Argon2id | Password hashing (recommended) | Memory-hard + time-hard |
| scrypt | Password hashing / disk encryption | Memory-hard |
Why memory-hard functions matterGPU/ASIC attacks on simple iteration (PBKDF2) are viable because parallel hardware is cheap. Memory-hard functions require large memory per hash, limiting parallelism.
Quantum Threat and Post-Quantum Cryptography
Grover's algorithmSpeeds up brute-force search. Halves effective key length.
- AES-128 → 64-bit security (borderline)
- AES-256 → 128-bit security (still safe)
- SHA-256 → 128-bit collision resistance (safe)
Shor's algorithmEfficiently solves integer factoring and ECDLP.
- Breaks RSA, DH, ECDH, ECDSA completely when large-scale quantum computer exists
NIST Post-Quantum Standards (2024)
| Algorithm | Type | Standard |
|---|---|---|
| CRYSTALS-Kyber (ML-KEM) | Key encapsulation (KEM) | FIPS 203 |
| CRYSTALS-Dilithium (ML-DSA) | Digital signature | FIPS 204 |
| SPHINCS+ (SLH-DSA) | Hash-based signature | FIPS 205 |
In TLS todaythe hybrid group X25519MLKEM768 (classical X25519 combined with ML-KEM-768) is on by default in Chrome (since Chrome 131, November 2024), Firefox, Safari and major CDNs, so a large and growing share of web traffic is already protected against "harvest now, decrypt later". It's a hybrid on purpose: the session stays secure as long as either component is unbroken. The key share is ~1.2 KB, which pushes the ClientHello past one packet and has broken a few middleboxes. Post-quantum signatures (ML-DSA) in certificates are not yet deployed on the public web: certificate chains would grow by several kilobytes.
Quick Reference: Where Algorithms Are Used
| Context | Algorithm |
|---|---|
| TLS 1.3 bulk encryption | AES-256-GCM or ChaCha20-Poly1305 |
| TLS 1.3 key exchange | ECDHE (X25519 or P-256) |
| TLS certificate signature | ECDSA-P256 or RSA-PSS-2048 |
| TLS HKDF | HMAC-SHA384 |
| SSH bulk encryption | ChaCha20-Poly1305 or AES-GCM |
| SSH key exchange | ECDH (curve25519) |
| SSH host key | Ed25519 |
| GPG | RSA-4096 or Ed25519 |
| HTTPS HSTS preload | SHA-256 pinning |
| JWT HS256 | HMAC-SHA256 |
| JWT RS256 | RSA-PSS |
| JWT ES256 | ECDSA-P256 |
| Bitcoin | secp256k1 ECDSA |
| Signal Protocol | Double Ratchet (X25519 + AES-256-CBC + HMAC-SHA256) |
| WireGuard | ChaCha20-Poly1305 + Curve25519 + BLAKE2s |
| FileVault / BitLocker | AES-XTS-256 |
| LUKS | AES-XTS-256 |
| Password storage | Argon2id (OWASP recommended) |
| AWS request signing | HMAC-SHA256 |
Interview Questions: Encryption & Cryptography
CBC only provides confidentiality — it chains blocks with XOR and needs a random IV, but it has no built-in integrity, so it's typically paired with a separate HMAC and is historically prone to padding-oracle attacks if implemented carelessly. GCM is an AEAD mode: it encrypts with counter mode and produces an authentication tag in the same pass, so it gives confidentiality and integrity together, and it can authenticate associated data that isn't encrypted. You prefer GCM because it's authenticated by design, hardware-accelerated, and removes the encrypt-then-MAC footguns of CBC. The one rule with GCM is never reuse a nonce with the same key.
It's catastrophic. GCM derives a keystream from the key and nonce, so two messages under the same key and nonce are XORed against the same keystream — meaning the XOR of the two ciphertexts equals the XOR of the two plaintexts, leaking content. Worse, nonce reuse also lets an attacker recover the GHASH authentication subkey, which breaks integrity and lets them forge valid tags for arbitrary messages. So a single nonce reuse compromises both confidentiality and authenticity. The fix is a unique nonce per encryption — a random 96-bit nonce or a non-wrapping counter — or AES-GCM-SIV if reuse is a real risk, since it's nonce-misuse-resistant.
Forward secrecy means a future compromise of the server's long-term private key can't decrypt past sessions, and it requires ephemeral key exchange. TLS 1.2 allowed ephemeral ECDHE but also still permitted RSA key transport, where the client encrypts the session secret to the server's RSA public key — if that key later leaks, all recorded sessions decrypt, so no forward secrecy. TLS 1.3 removed RSA key transport entirely and mandates ephemeral ECDHE for every handshake, so a fresh, discarded key pair protects each session by design. So it's not that 1.2 can't have forward secrecy — it's that 1.2 made it optional and 1.3 made it the only option.
Because TLS 1.3 only supports ephemeral ECDHE key exchange, there's nothing to negotiate about how to exchange keys, so the client sends its ECDHE key share immediately in the ClientHello rather than first asking the server. The server responds with its own key share in the ServerHello, and at that point both sides can derive the shared secret via HKDF, so the server already encrypts the rest — certificate, CertificateVerify, and Finished. The client verifies and sends its Finished, and data flows after one round trip. As a bonus, sending the key share upfront is what lets the certificate be encrypted, hiding it from passive observers, and there's an optional 0-RTT mode that sends early data with the first flight at the cost of replay risk.
ECDSA needs a fresh random nonce k for every signature, and the math is unforgiving: if k repeats across two signatures with the same key, the two signature equations share unknowns and the private key can be solved directly — this is how the PS3 was hacked and how Bitcoin keys have been stolen. Even a slightly biased or predictable k leaks the key over many signatures. Ed25519 eliminates the dependency by deriving k deterministically — it hashes the message together with the private key to produce the nonce — so there's no RNG to fail or repeat, and identical messages produce identical signatures without ever exposing the key. So Ed25519 removes the single most dangerous implementation pitfall of ECDSA.
ECDH is elliptic-curve Diffie-Hellman key exchange; the E on the end, ECDHE, means ephemeral — a fresh key pair generated for each session and thrown away afterward. The distinction matters for forward secrecy. With static ECDH, the same long-term key derives every session secret, so compromising it later decrypts all past traffic. With ECDHE, each session's secret comes from throwaway keys that no longer exist, so a future key compromise can't unlock recorded sessions. That's why modern TLS insists on ECDHE. Mnemonic: the extra E is for ephemeral, and ephemeral is what buys forward secrecy.
Both are deliberately slow, which is the point for password hashing, but bcrypt is only CPU-hard and uses a small fixed amount of memory, so attackers can parallelize it cheaply on GPUs and ASICs. Argon2id is memory-hard — it forces each guess to consume a configurable, significant amount of RAM, which makes massive parallel cracking far more expensive — and the id variant combines resistance to both GPU attacks and side-channel attacks. It also won the Password Hashing Competition and is OWASP's current recommendation. bcrypt is still acceptable, but for new deployments Argon2id raises the attacker's cost more, especially against well-funded hardware. Both must be salted.
Certificate Transparency requires every publicly trusted certificate to be recorded in public, append-only logs, and browsers expect proof of logging, so any issued certificate becomes visible to the world. Domain owners can monitor the logs and detect certificates issued for their domains that they didn't request. The driver was DigiNotar in 2011: that CA was hacked and issued a fraudulent wildcard certificate for Google, which was used to intercept the traffic of hundreds of thousands of users in Iran, and nobody could detect it because mis-issuance was invisible. CT makes that kind of secret mis-issuance immediately detectable, and DigiNotar itself was distrusted and collapsed. The trade-off is that CT publicly exposes all your subdomains.
Shor's algorithm efficiently solves integer factorization and the discrete logarithm problem on a large quantum computer, which completely breaks the asymmetric primitives we rely on — RSA, Diffie-Hellman, ECDH, and ECDSA — because their security rests on exactly those hard problems. Grover's algorithm is a generic search speedup that effectively halves the security of symmetric primitives, so AES-128 drops to about 64-bit security (borderline) while AES-256 stays at a safe 128-bit, and hash collision resistance is similarly halved. The practical implication is that symmetric crypto just needs bigger keys, but asymmetric crypto needs replacing — which is why NIST standardized post-quantum algorithms like ML-KEM (Kyber) and ML-DSA (Dilithium), now being deployed in hybrid TLS.
Because hash functions built on the Merkle–Damgård construction, like SHA-256, are vulnerable to length-extension attacks. The digest of Hash(key ∥ message) is the function's internal state after processing that input, so an attacker who sees the digest — without knowing the key — can resume from that state and compute a valid digest for key ∥ message ∥ extra, forging a MAC for a message they partly control. HMAC defeats this with its nested structure, hashing an inner keyed hash again with the outer key, so the internal state is never directly exposed. So the rule is to use HMAC, or a natively-MAC primitive like Poly1305, rather than naive concatenation.
HKDF is a key derivation function with two stages: extract, which takes input keying material that may not be uniformly random — like a Diffie-Hellman shared secret — and a salt, and condenses it into a uniformly random pseudorandom key; and expand, which stretches that key into as many independent output keys as you need, bound to context labels. TLS 1.3 uses HKDF throughout its key schedule: it feeds the ECDHE shared secret through extract and then derives all the distinct keys the connection needs — separate keys for handshake traffic, application traffic in each direction, exporters, and resumption — each with its own label so they're cryptographically separated. It replaced TLS 1.2's ad-hoc PRF with this clean, analyzed construction.
Certificate revocation checking via plain OCSP has the client contact the CA's responder to ask whether a certificate is still valid, which has two problems: it leaks the user's browsing to the CA, since the CA learns every site you visit, and it adds latency and a hard dependency on the responder being up — many clients "soft-fail" and skip the check if it's slow, undermining it. OCSP stapling moves the work to the server: the server periodically fetches a signed, timestamped OCSP response from the CA and staples it into the TLS handshake, so the client gets fresh revocation proof without ever contacting the CA. That fixes the privacy leak and the latency, and Must-Staple can require it so an attacker can't strip the staple. It's better because it delivers the same revocation assurance without the privacy and availability costs.
It's a hash of the client's ClientHello — the first handshake message, which is sent in the clear and lists the TLS version, cipher suites, extensions, and curves the client supports. Different software builds that list differently, so the fingerprint identifies the client tool without decrypting anything: Chrome, a Python script, and Cobalt Strike each produce a recognisable fingerprint. That's powerful because encryption hides the payload but not the handshake metadata, so a sensor can flag known-bad tooling or spot a connection whose TLS stack contradicts its User-Agent — a browser User-Agent over a Python TLS fingerprint is a classic malware and scraper tell. It's a Pyramid-of-Pain tooling signal, so treat it as a strong pivot, not a verdict.
JA3 MD5s an ordered list of ClientHello fields, but TLS 1.3 lets clients shuffle and pad extensions — GREASE values and the padding extension — so the same software emits many different JA3 hashes and attackers deliberately randomise it, which breaks matching. JA4, from FoxIO in 2023, fixes this by sorting the cipher and extension lists before hashing so reordering no longer changes the result, and it uses a structured, human-readable format instead of one opaque MD5 — you can read the TLS version, SNI presence, cipher and extension counts, and ALPN right off the front. It also generalises into the JA4+ family for other protocols, like JA4H for HTTP and JA4X for certificates. So JA4 is the more robust, less evadable, more readable successor.
TLS gives confidentiality through symmetric AEAD encryption, integrity through the AEAD tag or MAC, server authentication through certificates and a handshake signature, and anti-replay within a connection because every record's nonce includes an implicit sequence number. What it doesn't give is non-repudiation: both endpoints hold the same session keys, so either could have produced any record, and a capture can't prove to a third party what the server said. It also doesn't hide metadata like IPs, timing, sizes or — without ECH — the SNI hostname.
The client builds a path from the leaf through the intermediates the server sent — or ones it caches or fetches via AIA — up to a root in its trust store. For every link it checks the issuer name and key identifier match, the signature verifies with an allowed algorithm and key size, the certificate is within its validity dates, and issuers have CA:TRUE, keyCertSign and respect any path-length and name constraints. Then the leaf-specific checks: the hostname is in the SAN, the EKU includes serverAuth, it isn't revoked, and it carries enough valid CT timestamps. Finally the CertificateVerify signature in the handshake proves the server holds the matching private key right now.
Almost always the server isn't sending its intermediate certificate. Browsers paper over that by caching intermediates from other sites or fetching them via the AIA URL in the leaf, but curl, OpenSSL, Java, Go and most mobile stacks only use what the server sends plus their root store, so they can't build a path. You confirm it with openssl s_client -showcerts and fix it on the server by serving the full chain — leaf first, then each intermediate — rather than by disabling verification in the client.
Forward secrecy means a later compromise of the server's long-term private key doesn't expose past sessions, because each session's secret came from ephemeral Diffie–Hellman keys that were discarded — the long-term key only signed the exchange. RSA key exchange lacked it: the pre-master secret was encrypted to the server's RSA key, so stealing that key decrypts every recording. Session tickets reintroduce the problem quietly: in TLS 1.2 the ticket contains the master secret encrypted under a server ticket key, so whoever steals the ticket key can decrypt every session resumed with it. The fix is rotating ticket keys every few hours and, in TLS 1.3, resuming with psk_dhe_ke so a fresh ECDHE exchange is mixed in.
Because of middleboxes. During deployment testing, firewalls and inspection appliances broke a few percent of connections that showed an unfamiliar version number or message flow, so TLS 1.3 freezes the record and legacy_version fields at 0x0303, negotiates the real version in the supported_versions extension, sends a fake session ID and even a dummy ChangeCipherSpec so the exchange looks like a TLS 1.2 resumption. GREASE adds random reserved values to every list so implementations that choke on unknown values get caught early and the protocol stays extensible.
Three layers. Within 1.3, CertificateVerify signs a hash of the entire handshake transcript, and Finished MACs it, so altering the offered groups or suites breaks verification. Across versions, a 1.3-capable server that ends up negotiating 1.2 or lower writes the sentinel "DOWNGRD" plus a version byte into the last eight bytes of ServerHello.random, which is covered by the signature, so a 1.3 client that sees it knows an attacker stripped supported_versions and aborts. And there's simply less to downgrade to, because the weak suites and groups don't exist in 1.3.
In TLS 1.3 the client guesses a key-exchange group and sends a key share for it in the ClientHello. If the server doesn't accept any of the shares offered — for instance the client sent only the hybrid post-quantum X25519MLKEM768 and the server only supports P-256 — it replies with a HelloRetryRequest naming the group it wants, and the client sends a second ClientHello with the right share. It costs one extra round trip, and the server can include a cookie so it doesn't have to keep state between the two hellos, which helps against DoS.
0-RTT lets a client resuming a session send application data in its very first flight, encrypted with a key derived from the PSK in the session ticket, so a returning visitor saves a whole round trip. The risk is replay: that early data has no fresh server input, so an attacker can capture it and resend it to the same or another server in the fleet, and it has no forward secrecy against the PSK. So servers either reject replays with single-use tickets or ClientHello caches, or — more realistically — only accept idempotent requests in 0-RTT and answer anything else with HTTP 425 Too Early.
It's a chain of three HKDF-Extract steps producing the Early Secret from the PSK, the Handshake Secret from the (EC)DHE shared secret, and the Master Secret. From each stage, HKDF-Expand-Label derives labelled secrets bound to a hash of the transcript so far: early traffic keys for 0-RTT, handshake traffic keys that encrypt the certificate and Finished, and application traffic keys plus exporter and resumption secrets. Traffic secrets are then expanded into an AEAD key and IV, and each record's nonce is the IV XORed with a sequence number. The design gives clean key separation between phases and directions, which is what made it formally analysable.
You either need the session secrets or you need to be in the middle. Clients and many servers can write a key log file when SSLKEYLOGFILE is set, and Wireshark uses it to decrypt TLS 1.2, 1.3 and QUIC. The old trick of loading the server's RSA private key only works for TLS 1.2 RSA key exchange, because there the pre-master secret was encrypted to that key; with ECDHE — and always in TLS 1.3 — the private key only signs, so it reveals nothing about session keys. For production visibility, organisations terminate and re-inspect at a proxy or read plaintext on the endpoint.
CAA is a preventive DNS record saying which CAs may issue for your domain, optionally down to a specific ACME account; honest CAs must check it before issuing, but a compromised or malicious CA can simply ignore it. Certificate Transparency is detective: every publicly trusted certificate must be logged in public append-only logs and carry signed timestamps proving it, so browsers reject unlogged certificates and domain owners can monitor for anything unexpected. You want both — CAA to stop accidental or social-engineered issuance, CT monitoring to catch the cases CAA can't stop.
The log stores certificates as leaves of a binary hash tree and signs the root hash as the Signed Tree Head, which commits to every entry. An inclusion proof shows a certificate is in the tree using only about log2(n) sibling hashes — around 30 for a billion entries — and a consistency proof shows an older tree is a prefix of a newer one, meaning the log only appended and never rewrote history. So a log can't secretly show different contents to different people or remove a mis-issued certificate without auditors noticing.
An attacker in the middle opened their own TLS session to the server and sent a partial HTTP request with no final newline, then forwarded the victim's ClientHello as a renegotiation inside that session. The victim completed a handshake directly with the server, but the server treated it as a continuation of the attacker's connection, so the victim's request — including their cookie — was appended to the attacker's prefix and executed with the victim's authority. The flaw was that the renegotiated handshake wasn't bound to the connection it occurred in; RFC 5746's renegotiation_info fixed that by including the previous Finished values, and TLS 1.3 removed renegotiation altogether.
DV certificates only prove control of the domain, via an HTTP file, DNS record or TLS-ALPN challenge; OV adds verification that the organisation exists; EV adds stricter identity vetting. Browsers check all three identically for the TLS connection — only the name binding is cryptographic. Chrome and Firefox removed the green EV bar in 2019 because research showed users didn't notice when it was missing, and researchers registered companies with colliding names to get misleading EV certificates, so the indicator added cost without meaningfully stopping phishing.
In both, the server sends a CertificateRequest and the client answers with its certificate and a CertificateVerify signature over the handshake to prove it holds the private key. In TLS 1.2 the client certificate travels in plaintext, so a passive observer learns the client's identity, and requesting a certificate mid-connection required renegotiation. In TLS 1.3 the CertificateRequest and client certificate are inside the encrypted flight, and a later request uses post-handshake authentication instead of renegotiation, although HTTP/2 forbids that, so modern designs authenticate the client up front.