Chapter 10 // Protocols

When Cryptography
Fails

No modern cryptographic breach happened because someone broke AES or factored an RSA key. What fails is everything around the algorithm: the code that calls it, the system that manages the keys, the humans who configure it all.

Scroll to explore
02 // Side Channels

Timing side channels

A side channel leaks information through something other than the algorithm's output: execution time, power consumption, electromagnetic emissions. Timing is the most common side channel in software, and the easiest to exploit remotely.

Why early-exit comparison is dangerous

A naive string comparison returns false on the first mismatched byte. That means the comparison takes different amounts of time depending on how many leading bytes are correct. An attacker who can measure response time can recover a secret token byte by byte. For an NN-byte secret with BB possible values per byte, brute force requires BNB^N attempts. A timing attack requires only N×BN \times B. Exponential vs. linear.

Brute Force

168=4,294,967,29616^8 = 4{,}294{,}967{,}296

4.3 billion guesses for an 8-character hex token.

Timing Attack

8×16=1288 \times 16 = 128

128 guesses maximum. Test each position independently.

Timing Attack Simulator

A vulnerable server compares API tokens byte by byte and returns early on the first mismatch. Measure the response time to figure out the secret, one character at a time.

????????
Guesses used
0
Brute force needs
16816^8 = 4.3 billion
Timing attack needs
8×16\approx 8 \times 16 = 128 max

The constant-time fix

A constant-time comparison checks every byte regardless of where the first mismatch occurs. XOR each byte pair, OR all results into an accumulator, then check if the accumulator is zero.

let result = 0;
for (let i = 0; i < a.length; i++) {
  result |= a[i] ^ b[i];
}
return result === 0; // same time regardless of match position
03 // Padding Oracle

Padding oracle attacks

A padding oracle is any system that tells an attacker whether a decrypted ciphertext has valid padding. This seemingly minor information leak lets an attacker decrypt an entire message without knowing the key, one byte at a time.

PKCS#7 padding

Block ciphers operate on fixed-size blocks (16 bytes for AES). When the plaintext is not a multiple of the block size, PKCS#7 padding fills the remaining bytes. The value of each padding byte equals the number of padding bytes needed.

1 byte padding
01
2 bytes padding
02 02
3 bytes padding
03 03 03
4 bytes padding
04 04 04 04

The attack, step by step

The server decrypts, checks padding, and returns either "padding valid" or "padding invalid." This one-bit oracle is devastating.

01

The attacker targets the last byte of a ciphertext block. They modify the corresponding byte in the preceding block, trying all 256 possible values.

02

When the server responds "valid padding," the attacker knows the decrypted last byte is 0x01 (valid single-byte padding). From this, they derive the intermediate decryption value: intermediate=guess0x01\text{intermediate} = \text{guess} \oplus \texttt{0x01}.

03

The original plaintext byte is recovered: plaintext=intermediateoriginal ciphertext byte\text{plaintext} = \text{intermediate} \oplus \text{original ciphertext byte}. One byte decrypted without the key.

04

Repeat for the second-to-last byte, now targeting 0x02 0x02 padding. Then the third byte with 0x03 0x03 0x03. Byte by byte, right to left, the entire block is recovered.

The POODLE attack (2014) exploited exactly this oracle in SSL 3.0. The Lucky Thirteen attack (2013) exploited timing differences in TLS padding validation. These attacks are why TLS 1.3 removed all CBC cipher suites entirely. The only cipher modes allowed in TLS 1.3 are AEAD constructions that cannot produce padding oracles.

04 // Key Management

Key management failures

You can use AES-256-GCM with perfect nonce handling and constant-time code. None of it matters if the key is hardcoded in your source code, committed to Git, logged in plaintext, or never rotated.

The hierarchy of key management failures

Each of these has caused a real-world breach. They are listed roughly in order of how often they occur.

1
Hardcoded keys in source code

Found by static analysis, leaked via open-source repositories, visible in decompiled binaries.

2
Keys in environment variables without protection

Visible in process listings, crash dumps, container inspection, and CI/CD logs.

3
Keys in log output

Debug logging that prints request bodies, connection strings, or configuration objects containing key material.

4
No key rotation

A single key compromise exposes all data ever encrypted with that key. Without rotation, the blast radius is unlimited.

5
No key separation

Using the same key for encryption and authentication, or for different services. One compromised context exposes everything.

Spot the Vulnerability

Six code snippets, six cryptographic bugs. Can you find them all?

0 / 6
1/6Hardcoded KeyCritical
code.js
1
const ENCRYPTION_KEY = "super-secret-key-12345678901234";
2
const cipher = crypto.createCipheriv("aes-256-gcm", ENCRYPTION_KEY, iv);

GitHub's secret scanning has found millions of private keys, API tokens, and encryption keys committed to public repositories. Key management is not a feature you add later. It is a requirement from day one.

05 // Case Studies

A brief history of cryptographic disasters

Every failure in this chapter has happened in production, at scale, affecting millions of users. These are not theoretical attacks. They are engineering mistakes made by skilled teams under real-world constraints.

2006Debian OpenSSLRandomness failure

A maintainer commented out two lines that seeded the random number generator, reducing all key generation to roughly 32,000 possible keys for two years. Every SSL certificate, SSH key, and VPN key generated on Debian during this period was trivially breakable.

2010PlayStation 3 ECDSANonce reuse

Sony used a constant value for the random nonce k in every ECDSA signature. Two signatures with the same k and basic algebra were enough to extract the private key. Hackers used it to sign their own code and run it on any PS3.

2014HeartbleedImplementation bug

A buffer over-read in OpenSSL's heartbeat extension leaked up to 64 KB of server memory per request, including private keys and session tokens. Not a cryptographic flaw. A bounds-checking bug in C code.

2014Goto FailImplementation bug

Apple's TLS implementation had a duplicated "goto fail;" line that skipped certificate signature verification entirely. Any certificate was accepted as valid. The fix was a single line deletion.

2014POODLEPadding oracle

A padding oracle in SSL 3.0's CBC implementation allowed decryption of HTTPS session cookies. The attack required roughly 256 requests per byte. This led to the deprecation of SSL 3.0 entirely.

2017WPA2 KRACKNonce reuse

Replaying message 3 of Wi-Fi's four-way handshake forced the client to reinstall an already-used key with a reset nonce counter. This turned WPA2 encryption into a nonce-reuse attack, allowing decryption of Wi-Fi traffic.

Notice the pattern. Not a single one of these was caused by someone breaking AES, RSA, or SHA-256. The algorithms work. The implementations do not.

06 // Defense

The defensive checklist

You cannot eliminate all bugs, but you can build systems that are hard to misuse. Make the right thing easy and the wrong thing impossible.

📦
Use High-Level Libraries

Use libsodium, Web Crypto, or your language's standard crypto library. Never implement primitives yourself.

🛡️
Authenticate Everything

Use AEAD modes (AES-GCM, ChaCha20-Poly1305). Never use a cipher mode without authentication.

⏱️
Constant-Time Everything

Use constant-time comparison for MACs, tokens, and passwords. Your language's == operator is not constant-time.

🔑
Manage Keys Like Secrets

Use a KMS. Rotate keys. Separate keys by purpose. Never log, hardcode, or commit keys.

🚨
Fail Closed

If decryption fails, reject the data entirely. Do not return partial results or detailed error messages that reveal why.

🔄
Keep Dependencies Updated

Subscribe to CVE feeds for your crypto libraries. Heartbleed persisted on patched systems for months because operators did not update.

The common thread: reduce the surface area for mistakes. Use AEAD instead of separate encrypt-and-MAC. Use a KMS instead of manual key files. Use constant-time comparison by default. The best defense against cryptographic failure is an API that makes misuse difficult.

Up Next

Chapter 11 // Cryptocurrency and Blockchain

Continue Learning