Library · Summary & review

Serious Cryptography

By Jean-Philippe Aumasson, 2nd edition. Which algorithm to choose, which to avoid, and the usage mistakes that ruin a good one.

FR EN
Serious Cryptography, 2nd Edition book cover, Aumasson

Serious Cryptography, 2nd Edition

Serious Cryptography, 2nd Edition: A Practical Introduction to Modern Encryption

6.8 /10

« Rigorous, frank about its limits, silent on Go and PHP: a book that teaches what never to do with a good algorithm. »

  • AuthorJean-Philippe Aumasson
  • OriginalNo Starch Press, 2025 · 2nd edition, 379 pages
  • LanguageEnglish only
  • This page~33 min read
Book rating across 5 dimensionsIdeas7/10Practical6/10Readability7/10Aged well7/10Examples7/10

Which algorithm to choose, which to avoid, and the usage mistakes (nonce, randomness, comparison) that ruin even a good algorithm.

Why this book

A web developer almost never programs an encryption scheme. They call a function, pick a mode, a nonce, a key size, and wonder whether it is safe. This book answers that question, and its message is clear: algorithms are rarely broken, their uses are. A repeated nonce, a language's random generator, a comparison that stops at the first wrong byte are enough. The author, Jean-Philippe Aumasson, is a professional cryptographer.

This note follows the path of a message. First the meaning of "secure" and randomness (ideas 1 and 2), then encryption (3 and 4). Hashing and authentication come next (5 and 6), then their union (7), public keys (8 and 9) and TLS 1.3 (10). Quantum computing (11), two traps and a memory aid (12) close the list.

Two warnings. The author designed several of the functions he compares: "Full disclosure: I’m a designer of BLAKE2" (p. 133). He also co-authored BLAKE3 and SipHash. Then, the examples are mostly in Python and OpenSSL (a few Sage and C++ listings and one Go excerpt). No crypto/rand, hmac.Equal or hash_equals function appears in it, and each "outside the book" box below bridges that gap.

I opened it to fill chapter 7 of The Developer's Great Book, the one on cryptographic primitives. I also wanted to know what to master before a public mTLS repository in Go (mTLS: TLS where the client also authenticates, by certificate). I leave out complexity theory, the internal mathematics of AES and the blockchain.

The ideas that stick

1Against whom, and in how many bits: the 128-bit reflex

You cannot just say that something is secure. The book sets the rule: "you can’t claim that a system is secure without explaining against whom or from what it’s safe" (p. 12). A cipher turns a plaintext into a ciphertext using a secret key. Its attack model describes what the attacker can do.

The book gives four, from weakest to strongest (pp. 13-14). In a COA (ciphertext-only attack), the attacker sees only ciphertexts. In a KPA (known-plaintext attack), they also know some plaintexts. In a CPA (chosen-plaintext attack), they can have anything they like encrypted. In a CCA (chosen-ciphertext attack), they can also have things decrypted. A last family targets the implementation: running time, error messages, power draw (p. 14). These are side channels. A site that answers a tampered message with a padding error is one (idea 3).

The Kerckhoffs principle (1883) draws the consequence: "the security of a cipher should rely only on the secrecy of the key and not on the secrecy of the cipher" (p. 13). Do not invent your own algorithm. The book has only one proof of trust to offer: many experts have failed to break the standards (p. 53). A proof can even be wrong: the one for OAEP (idea 8) was accepted for seven years (p. 56).

To measure strength, the book counts bits of security: n bits means about 2^n operations for the attacker (p. 46). Brute force tries every key. Against a 128-bit key, at 100 billion tries per second, it takes more than 100 billion billion years (p. 45). A system's level is read at its weakest link: a 2,048-bit RSA modulus (n, idea 8) is worth less than 128 bits (p. 46). Another example: "keys of 512 bits offer a security level of approximately 60 bits" (p. 56). In 2015, HTTPS sites and SSH servers still accepted such keys, breakable in two weeks on 72 processors (p. 56).

Several targets lower the bar. Each guess aims at all keys at once, so breaking one of 2^16 128-bit keys costs 2^112 evaluations on average (pp. 48-49). The book settles it: 128 or 256 bits, never 64 or 80 (p. 49).

My reading

Outside the book, decisions. Three habits follow. Aim for at least 128 bits of security. Refuse short keys kept for compatibility. Avoid encrypting data that is already compressed: a secure cipher leaks the length (p. 15), and the compressed length betrays the content (CRIME, idea 10).

2A key is only as good as its randomness: the system's generator, never the language's

A key is just a string of random bytes. If the attacker guesses the randomness, the algorithm is useless. The book distinguishes two stages (pp. 28-29). A source of entropy (raw disorder: noise, keystrokes, system activity) is slow and can be manipulated. A PRNG, a pseudorandom generator, turns it into a long unpredictable stream from a seed (its starting value). When it is designed for security, it is called a CSPRNG.

On Linux, here is the interface: "The device file /dev/urandom is the userland interface to the crypto PRNG" (p. 34). The getrandom() system call is cited as another way in (p. 36). /dev/random, which blocks, is no better: "/dev/random was no better than /dev/urandom and created more problems than it solved" (p. 36). On Windows, BCryptGenRandom() is the simplest interface (p. 37).

The trap is the language's generator: "you should never use noncrypto PRNGs in crypto applications, because they’re insecure" (p. 32). The book names PHP's rand and mt_rand (p. 32). The Mersenne Twister behind mt_rand is linear: each output bit is an XOR of bits of the initial state. XOR, exclusive or, gives 1 if the two bits differ and 0 otherwise. These equations can be solved: "given a few bits produced by MT, one can guess which bits will follow" (p. 32).

Three stories show it. In 2012, researchers predicted the tokens and temporary passwords of MediaWiki, generated with mt_rand() (p. 40). The code even carried this comment: "Could be made more cryptographically sure if someone cares" (p. 40). In 2022, the Trust Wallet extension drew its seeds from a Mersenne Twister limited to 32 bits of entropy. That made 2^32 possible wallets, which a few graphics cards could drain in under a day (p. 305). In 1996, Netscape SSL had a seed of about 47 bits instead of 128 (p. 39).

A good generator is not enough. In 2012, an Internet scan found identical TLS and SSH public keys, or keys sharing a prime factor. The generator was sound, but the key was created at first boot, before enough entropy had accumulated (pp. 39-40). Statistical tests do not reassure: "statistical tests are largely irrelevant to cryptographic security" (p. 33). Finally, bad slicing ruins a good CSPRNG. Cryptocat reduced a byte to a digit with a remainder modulo 10 and accepted <= 250 instead of < 250. The 0 came out once too often, hence a bias (p. 41).

A symmetric key is n bits from the CSPRNG, as in openssl rand -hex 16 (p. 54). An asymmetric key (a public and private key pair, idea 8) requires a generation algorithm (p. 54). To protect it, the book lists three ways: encrypt it with another key, derive it from a password, or use a hardware token (p. 55). Its confession: "I’ve actually found private keys on GitHub" (p. 55).

My reading

Outside the book. In Go, it is crypto/rand and never math/rand. In PHP, random_bytes() for bytes and random_int() for an integer, never rand() or mt_rand(). The book cites none of these functions.

3AES does not break: the mode and the nonce are what ruin you

A block cipher splits the message into fixed-size blocks and encrypts each one with the key. AES has 128-bit blocks, DES 64-bit blocks (p. 62). Small blocks are to be avoided, because their plaintext-to-ciphertext lookup table becomes buildable. At 64 bits it would take 2^70 bits, and the book says "forget about it" (p. 63). On AES itself, the best known attack gains a factor of 4 and needs 2^88 bits of pairs (pp. 73-74). That is not your concern. Yours lies elsewhere: "The biggest threat to block ciphers isn’t in their core algorithms but in their modes of operation." (p. 74).

A mode of operation says how to encrypt a message of several blocks. The ECB mode encrypts each block alone: two identical blocks give two identical ciphertexts, as the book shows with two zero blocks (pp. 75-76). Verdict: "ECB is insecure, and you shouldn’t use it." (p. 74).

The CBC mode mixes each block with the previous ciphertext. The first block gets an IV, an initial value that must be random and travels in the clear (p. 78). The book sighs: "Alas, we often use CBC with a constant IV instead of a random one" (p. 78). CBC requires complete blocks, hence padding: you fill with bytes whose value gives their count. If one byte is missing, add 01. If the plaintext is already aligned, add 16 bytes 10 (pp. 78-79).

Padding opens the door to the padding oracle. An oracle is a system you query whose answers inform the attacker. Here, it is a server that reacts differently depending on whether the padding is valid (p. 83). To decrypt a block C2, the attacker submits it behind a fake block C1. They vary the last byte of C1 until the server accepts. C1 XOR (raw decryption of C2) then ends in 01. Knowing C1, they deduce the raw byte. With the real previous block (public), they then deduce the byte of the real plaintext (p. 84). They repeat this byte by byte: "The attack needs on average 128 queries to the oracle for each of the 16 bytes, which is about 2,000 queries in total." (p. 84). The book cites web application servers hit in 2010 (p. 22). Its cure comes later: authenticate before decrypting (idea 7).

The CTR mode encrypts a nonce (number used once) glued to a counter. It derives a keystream that is combined with the plaintext by XOR. So it turns AES into a stream cipher (idea 4). The book is clear: "unlike CBC’s initial value, CTR’s nonce doesn’t need to be random; it simply needs to be unique." (p. 81). Otherwise, two ciphertexts C1 and C2 from the same stream give C1 XOR C2 = P1 XOR P2. The keystream cancels out and the two plaintexts overlap (p. 81). A random 64-bit nonce repeats around 2^32 messages (birthday rule, idea 5): "you can expect a repetition after approximately 2^32 nonces, which is an unacceptably low number" (p. 82).

My reading

Outside the book. Encryption that does not authenticate calls for the remedy of idea 7. At home, you handle neither ECB nor CBC by hand: take an authenticated mode (AES-GCM) via crypto/cipher in Go, or ChaCha20-Poly1305 via libsodium in PHP.

4A keystream is used once: RC4 is dead, ChaCha20 remains

A stream cipher produces a keystream from a key and a public nonce. It then combines it with the plaintext by XOR (p. 88). Decrypting is the same operation. The rule is the one from idea 3: never reuse the (key, nonce) pair (p. 88). AES in CTR is one case: "one of the most popular stream ciphers is actually a block cipher in disguise" (p. 102).

RC4 (Rivest, 1987) was the most widespread stream cipher, in WEP then TLS. Its state is a 256-byte array that gets permuted (p. 102). Its fate is a lesson in humility: "This looks too simple to be secure, yet it took 20 years for cryptanalysts to find exploitable flaws." (p. 103).

WEP, the original Wi-Fi security, badly patched a gap: RC4 had no nonce. WEP glued a 24-bit one in front of the key. A WEP 128-bit key is worth at best 104 bits (p. 104). With 24 bits, a randomly drawn nonce repeats after about 2^12 packets (p. 104). Worse, the three visible nonce bytes reveal the generator's state after three iterations, which allows key recovery (p. 104).

In TLS, the session key is unique, so RC4 is a little less broken there. But its second output byte is zero with probability 1/128 instead of 1/256 (p. 105). The book marvels: "The amazing thing about RC4 is that it fails where even many noncryptographic PRNGs succeed" (p. 105). In 2013, experts still trusted it, although the biases had been known since 2001 (p. 105). TLS 1.3 removed it (p. 266). On page 105, the book classes the RC4 broadcast attack as CCA, although it only observes ciphertexts (COA). It is a slip, noted at the end of this note.

Salsa20 (Bernstein, 2005) is the software successor. It works with a counter, with no evolving secret state, using additions, rotations and XORs on 32-bit words (pp. 106-107). The book describes Salsa20 and cites ChaCha as its variant (p. 106). ChaCha20, the stream cipher still in use, is not detailed there. It appears only as ChaCha20-Poly1305, one of the three ciphers allowed by TLS 1.3 (p. 265).

Decision: no RC4 and no homemade nonce. A stream cipher alone does not detect tampering: authenticate it (idea 7).

5Hashing: SHA-2, SHA-3 or BLAKE, never MD5 or SHA-1

A hash function takes an input of any size and returns a short fixed-size fingerprint: "hash functions take a long input and produce a short output, called a hash value or digest" (p. 116). Do not confuse it with a non-cryptographic function such as CRC, which detects accidental errors and offers no security (pp. 116, 135).

Three resistances matter (pp. 117-120). A preimage is a message that gives a given digest: finding it must be impossible. A second preimage is another message with the same digest as a known message. A collision is a pair of messages with the same digest. Collision resistance implies the other two: whoever finds a second preimage has found a collision. Yet collisions are easier to find: "Finding collisions is faster than finding preimages" (p. 120). This is the birthday paradox: in a room of 23 people, two share a birthday with a one in two chance (p. 120). The same holds for an n-bit digest: a pair collides after about 2^(n/2) messages, not 2^n. SHA-256 therefore offers 128 bits against collisions.

The book is firm about the old ones. MD5: "it takes only seconds to find a collision for MD5, yet some systems still use or support MD5, often for reasons of backward compatibility." (p. 127). SHA-1: a real collision arrived 12 years after the 2005 estimate (p. 129). Verdict: "You should not use SHA-1." (p. 129), "Use SHA-2, SHA-3, BLAKE2, or BLAKE3 instead." (p. 129).

The three recommended families differ in construction. SHA-2 (SHA-256 and SHA-512 first) chains blocks by compression (state and block become a new state), like MD5 and SHA-1 (pp. 122, 129). The book is honest: "there is no genuine proof that SHA-2 functions are secure; we’re talking about probable security." (p. 130). NIST (the American standards institute) launched the SHA-3 contest as a plan B: 64 candidates, 5 finalists, and Keccak won (pp. 131-132). It rests on a sponge, a permutation (reversible shuffle) with no compression, completely different from SHA-2 (p. 132). But "Unfortunately, SHA-3 isn’t faster than SHA-2" (p. 133), hence little momentum to migrate. BLAKE2 and BLAKE3 aim for speed, with an optional secret key (pp. 133-135).

A secure hash can be misused. Take the proof of storage on page 136 (|| denotes concatenation). The client sends a random challenge C. The server answers Hash(M || C), supposed to prove that it keeps the whole file M. But hashing is iterative: the server can keep the state after the first block of M, throw M away, and still answer. With C first, the state depends on a C unknown in advance, so the server must reread M. The book fixes it: "ask for Hash(C || M) instead of Hash(M || C)" (p. 137).

My reading

Outside the book. crypto/sha256 in Go, hash('sha256', …) in PHP, none of this is in the book. A fast hash does not suit passwords: the book lists "password protection" among the uses (p. 115) without saying more. For that need, see the article Argon2 and the timing oracle of the dummy hash.

6Authenticating a message: HMAC, never H(key || message), and compare in constant time

A recipient wants to know that the message really comes from the sender and was not modified. So you attach a tag, computed by a MAC (message authentication code) from a secret key: T = MAC(K, M) (p. 140). The recipient recomputes and compares. A MAC does not block replay, the resending of a captured authentic message. The cure: "To prevent such replay attacks, protocols include a message number in each message." (p. 141).

The obvious temptation: hash the key glued to the message. The book warns: "It might seem obvious that you can produce a keyed hash function by feeding an (unkeyed) hash function a key and a message, but this is easier said than done." (p. 142). With SHA-2, H(K || M) is forgeable by length extension (pp. 136, 142). The digest of an iterative hash is the internal state after the last block. The attacker resumes from that state and adds blocks, without knowing K. They obtain Hash(K || M1 || M2) from Hash(K || M1). The book judges the flaw avoidable: "This flaw could have been avoided simply by making the last compression function call different from all others" (p. 136). BLAKE2 and SHA-3 are not subject to it (p. 135).

Hash(M || K) is no better. If two messages have the same digest (an MD5 collision, say), they have the same tag whatever the key (p. 143). H(K || M) is also ambiguous: K = 123abc with M = def gives the same input as K = 123a with M = bcdef (p. 143).

The answer is HMAC: Hash((K XOR opad) || Hash((K XOR ipad) || M)), two nested calls with padding constants (p. 144). It is used by IPsec, SSH and TLS (the HTTPS protocol, idea 10). The book draws a rule: "That means if someone asks you to use HMAC, you should always ask, “Which hash function?”" (p. 145). HMAC-SHA-256 is a full name. HMAC-MD5 and HMAC-SHA-1 are not totally broken, but the risk is judged high (p. 155). Poly1305 is a very fast MAC (TLS 1.3, OpenSSH), but its nonce must never repeat (pp. 148-151).

Last fault, the most banal: verification. A byte-by-byte comparison stops at the first wrong byte: "The problem occurs when a server compares the correct tag with an incorrect one by comparing the two strings byte per byte, in order, until the bytes differ." (p. 153). Over 100,000 calls, the book measures about 67 ms for identical strings and 26 ms when they differ at the third byte (p. 154). An attacker who times the calls guesses the tag byte by byte. The cure is "code that takes exactly the same time to complete for any secret input value" (p. 154).

My reading

Outside the book, uncompiled sketch. The book does not name these functions, which you call like this.

mac := hmac.New(sha256.New, key)
mac.Write(msg)
ok := hmac.Equal(mac.Sum(nil), received) // constant time
$ok = hash_equals(hash_hmac('sha256', $msg, $key, true), $received);

Never == or bytes.Equal on a secret.

7Encrypting without authenticating protects nothing: take authenticated encryption (AEAD), and a nonce that never comes back

Encrypting hides the content but does not prevent modifying it. Combining a cipher and a MAC can be done in three ways (p. 158). Encrypt-and-MAC encrypts and authenticates the plaintext in parallel (SSH). MAC-then-encrypt authenticates the plaintext then encrypts the whole (TLS up to 1.2). Encrypt-then-MAC encrypts first, then authenticates the ciphertext (IPsec). Encrypt-and-MAC has a flaw: "the authentication tag (T) of a plaintext (P) could still leak information even though the MAC is considered secure!" (p. 159). MAC-then-encrypt has another: the recipient must decrypt before knowing whether the packet is corrupt (p. 159). That is the door to the padding oracle of idea 3: the server decrypts first, and its reaction betrays the padding. With encrypt-then-MAC, it rejects without ever decrypting (pp. 159-160). The condition: "as long as each is secure in its own right and the MAC and cipher use distinct keys" (p. 158).

An AEAD (authenticated encryption with associated data) does both in a single call. It returns the ciphertext and a tag. On authenticated decryption (written AD in the book): "If the tag validation fails, AD returns an error to prevent the recipient from processing a plaintext that may have been forged." (p. 160). The associated data is authenticated without being encrypted, like a packet header (p. 160). The book adds: "Such cleartext data must be encoded and serialized in a secure way to prevent ambiguous interpretation of its content." (p. 161). And the nonce remains your job: "It’s up to the encryption operation to pick a nonce that has never been used before with the same key." (p. 161).

AES-GCM is the most widespread AEAD. It is standardized (NIST SP 800-38D) and recognized for IPsec, SSH and TLS 1.2 and 1.3 (p. 164). It combines the CTR mode of idea 3 with a tag T = GHASH(H, A, C) XOR AES(K, N || 0) (pp. 164-166). A denotes the associated data, C the ciphertext, N the nonce and H an internal authentication key. GHASH is a fast hash that mixes A and C with H through multiplications, with no other secret (p. 165). Its fragility is the repeated nonce: the attacker obtains the XOR of the plaintexts (idea 3). With two tags at the same nonce, the AES term cancels in their XOR. That XOR is GHASH(H, A1, C1) XOR GHASH(H, A2, C2), and everything in it is known except H. GHASH's inputs and outputs are linearly related, so this equation yields H. The attacker can then forge tags at will (p. 166). This is not theoretical: in 2016, 184 HTTPS servers repeated their nonces, 23 of them with a nonce always equal to zero (p. 166).

Another trap, short tags. With an n-bit tag, the forgery chance is 2^m/2^n, where 2^m is the number of blocks of the longest observed message. Take a 48-bit tag and 4 GB messages (2^28 blocks of 16 bytes). That gives 2^28/2^48 = 1/2^20, about one chance in a million: "That’s a relatively high chance as far as cryptography is concerned." (p. 173). Keep 128 bits. One value in the book needs correcting. It gives 1/2^16 for a 32-bit tag and a 2 MB message. Its formula, with 2 MB = 2^17 blocks of 16 bytes, gives 1/2^15.

The book presents other choices. OCB is simpler and faster, patent-free since 2021 (p. 167). SIV and GCM-SIV stay secure if a nonce repeats: "SIV is secure even if you use the same nonce twice" (p. 169). But they cannot encrypt on the fly: the tag depends on the whole plaintext, which must therefore be kept in memory (p. 169). Note that the book nowhere says to take GCM by default. It observes that it is the most widespread, and that TLS 1.3 accepts only AEADs (p. 265).

My reading

Outside the book, uncompiled sketch, errors ignored. In Go, with the crypto/cipher package.

gcm, _ := cipher.NewGCM(block)          // block = aes.NewCipher(key)
nonce := make([]byte, gcm.NonceSize())  // 12 bytes
rand.Read(nonce)                        // crypto/rand
sealed := gcm.Seal(nonce, nonce, plaintext, header) // nonce || ciphertext || tag

Let us count: a randomly drawn 96-bit nonce repeats around 2^48 messages per key (birthday rule of idea 5, p. 82). NIST SP 800-38D (§8.3), cited p. 164, also requires at most 2^32 authenticated encryptions per key as soon as the IV is drawn at random. That limit is much lower than the 2^48 of the birthday rule.

8RSA today: 2,048 bits minimum, OAEP to encrypt, PSS or FDH to sign

RSA rests on a secret: two large primes p and q, whose product n = pq (the modulus) is public. A public exponent e encrypts or verifies, and a private exponent d decrypts or signs (p. 198). Breaking RSA amounts to factoring n. The book sets the bar: "n should be at least 2,048 bits long" (p. 199), about 90 bits of security, and 4,096 bits for about 128 (p. 199).

First rule: "you shouldn’t use the RSA trapdoor permutation directly for encryption or signing" (p. 199). The RSA trapdoor permutation is x → x^e mod n, invertible only with the private key. Used as is, so-called textbook RSA, it is vulnerable: "textbook RSA encryption is deterministic: if you encrypt the same plaintext twice, you’ll get the same ciphertext twice." (p. 200). It is also malleable: the product of two ciphertexts is the ciphertext of the product of the plaintexts (p. 200). In practice, RSA encrypts a symmetric key, not a message (p. 199).

To encrypt, add random padding that makes encryption probabilistic: "You should use OAEP when encrypting with RSA, rather than its predecessor, the standard PKCS#1 v1.5." (p. 201). With a 2,048-bit modulus and SHA-256, one OAEP block carries at most 190 bytes of symmetric key (p. 202).

Signing is not encrypting backwards: "Signing with RSA isn’t the same as encrypting with the private key." (p. 202). The raw signature of x is x^d mod n: forgeable for x = 1, whose signature is always 1 (p. 203). It is also vulnerable to blinding. To get a victim to sign M when they would refuse, you have them sign R^e·M, which looks harmless. Its signature is R·M^d: dividing it by R gives that of M (p. 203). Two schemes exist. PSS adds a salt (random bytes mixed into the message). "OAEP has been proven secure only for encryption, not for signatures." (p. 204), so PSS to sign. FDH signs Hash(M)^d: "It’s boringly simple, deterministic, yet secure." (p. 205). The book finds PSS complex and prone to implementation errors (p. 204).

Two implementation traps, finally. The fault attack (Bellcore, 1996) causes an error during a signature. That signature is sped up by the Chinese remainder theorem (separate computation modulo p and modulo q). If the error hits only the computation modulo p, the faulty signature stays correct modulo q. The GCD of n and (faulty signature)^e − Hash(M) then gives q, hence the private key (pp. 211-212). The book states the consequence: "meaning it works on FDH but not on PSS, which is probabilistic." (p. 211). And two keys never share the same modulus, since whoever shares n knows p and q, hence the other's private key: "different key pairs should have different n modulus values" (p. 212).

The book adds that RSA signatures are "less and less used" (p. 205) against ECDSA and EdDSA.

My reading

Outside the book. The book names only one API, Go's. It cites EncryptOAEP from the crypto/rsa package and says PSS is in OpenSSL and in the Go language's cryptography module (pp. 204, 206). Nothing on PHP (openssl_*).

9Elliptic curves: ephemeral exchange, Ed25519 signature, an ECDSA nonce never to be lost

Two strangers want to share a secret over a network that is being listened to. Diffie-Hellman (DH) makes it possible. With a public number g, Alice keeps a secret a and publishes A = g^a. Bob keeps b and publishes B = g^b. Alice computes B^a, Bob computes A^b: both obtain g^ab, the shared secret, without sending it (p. 216). The eavesdropper who sees A and B must recover a: this is the discrete logarithm, believed hard. But the book flags a catch: "But gab is not a random string" (p. 229). In the book's example (the numbers 1 to 12 on 4 bits), the first bit is zero in 7 cases out of 12. That is 0.58 instead of 0.5 (p. 229). A key must be uniform. So you pass the secret through a KDF (key derivation function), such as HKDF (RFC 5869, p. 229).

Bare DH is, in the book's words: "A simple protocol, secure against only the laziest of attackers." (p. 223). A man in the middle steps in and shares a secret with each side. The cure is authenticated DH: each party signs its message with its long-term key (pp. 224-225). It brings forward secrecy: if the signing keys ever leak, past sessions stay unreadable. The reason: "they’d learn the private signing keys but not any of the ephemeral DH exponents" (p. 225). The exponents here are the secrets a and b. Knowing whether you hold the right public key remains to be solved: that is the subject of certificates (idea 10).

An elliptic curve is the set of points (x, y) that satisfy an equation, modulo a large prime. The equation looks like y² = x³ + ax + b. Two points can be added, through the line joining them (p. 237). So a point can be multiplied by an integer k, by adding it to itself k − 1 times (p. 238). DH is played with these points: G is public, Alice publishes aG (G added a times) and Bob publishes bG. The shared secret is abG: each multiplies the other's point by their own secret (p. 241). A 256-bit curve offers about 128 bits of security (p. 241). Two curves dominate. NIST curves have constants derived from a SHA-1 hash whose origin only the NSA knows. Still, most experts do not believe in a hidden flaw (p. 247). Curve25519 (Bernstein, 2006) has no opaque constant and is faster. The exchange it enables is called X25519 (pp. 248, 290). It is used in WhatsApp, TLS 1.3 and OpenSSH (p. 248).

For signing, the book prefers curves to RSA: "You should prefer ECDSA to RSA except when signature verification is critical" (p. 244). ECDSA is the Elliptic Curve Digital Signature Algorithm. With 4,096-bit RSA, one measures 303 signatures per second, against 63,709 for ECDSA on P-256. The signature is 512 bits against 4,096 (pp. 243-244). But ECDSA has a deadly trap: the signature nonce k. A signature contains s = (h + rd)/k. Here h is the message hash and d the private key. The value r is the other half of the signature. It depends only on k, so it is identical in both signatures. With the same k for two messages, s1 − s2 = (h1 − h2)/k. So k = (h1 − h2)/(s1 − s2), then d = (s·k − h)/r (p. 249). Unlike RSA, a bad random generator is enough to lose the key. A real case, the PlayStation 3: "They discovered that the same k was reused to sign different games" (p. 249).

Ed25519 removes this risk: its nonce is derived from the message and the key, "eliminating the risks related to flawed randomness" (p. 244). It is in the US standard FIPS 186-5 since February 2023 and serves OpenSSH (p. 246). Its only flaw is that signature validation is not uniform: implementations do not all apply the same criteria (p. 250).

Two validations also save the exchange. DH parameters must be checked. In 2016, OpenSSL (CVE-2016-0701) accepted a non-safe prime as a DH parameter, the modulus of the computations. That leaked a private key (pp. 229-230). Received curve points must also be checked, because "You can elegantly break ECDH if input points are not properly validated." (p. 249). The addition formulas do not use the curve's coefficient b: the attacker sends a point from another curve, of small order. The victim's computation then stays within a small set of values, which the attacker enumerates to recover the secret (pp. 249-250). The test is to check that the coordinates satisfy the curve's equation (p. 250).

My reading

Outside the book. In Go, use crypto/ecdh (X25519) and crypto/ed25519. In PHP, use sodium_crypto_kx_* and sodium_crypto_sign_*. The book names none of them in the pages read, and its code is mostly Python and OpenSSL.

10TLS 1.3 seen from the crypto side: one round trip, three suites, and what the book does not say about mTLS

TLS is the S of HTTPS: it encrypts and authenticates a connection. It has known the famous flaws Heartbleed, BEAST, CRIME and POODLE (p. 256). Verdict on the current version: "Essentially, TLS 1.3 is mature TLS." (p. 257). It has two protocols: the handshake, which negotiates the keys, and the record, which uses them for the data (p. 257).

The handshake fits in one round trip (p. 267). The client sends its preferred suites (sets of algorithms), an ephemeral DH public key and a 32-byte random value. The server answers with its own public key, its certificate (see below), a signature over everything and a MAC. Both sides compute the DH secret (idea 9) and derive the keys with HKDF (pp. 263-265). The gain over TLS 1.2: "The time saved can be in the hundreds of milliseconds." (p. 267).

What TLS 1.3 removed overlaps with this note (p. 266): MD5, SHA-1, RC4, AES-CBC, MAC-then-encrypt and compression (the CRIME attack). It accepts only three AEADs: AES-GCM, AES-CCM and ChaCha20-Poly1305 (p. 265). It adds a protection against downgrade, which means forcing both sides toward an old vulnerable version by tampering with the client's message. If that message asks only for TLS 1.2, the 1.3 server puts a fixed pattern in its random value. This pattern is signed, so an intermediary cannot erase it, and the client that had asked for 1.3 sees the deception (pp. 266-267). The book writes 8 bits for the eight bytes left random when the client asks for TLS 1.3 This slip is noted at the end of this note.

Authentication goes through certificates. A certificate binds a public key to a domain. It is signed by a certificate authority (CA), whose key is hard-coded in the browser or the system (pp. 258-260). The book is not gentle: "security experts and cryptographers often claim the whole certificate system is inherently broken" (p. 262), while judging it one of the best solutions available (p. 262). The limits are stated: "TLS 1.3 security relies on the assumption that all three parties (the client, the server, and the certificate authority) will behave honestly" (p. 269). DigiNotar, compromised in 2011, is the example (p. 269). An injection or an XSS goes through a legitimate tunnel: "TLS won’t save you in this case." (p. 270).

For the future mTLS repository, here is what the book brings and what it leaves out. It brings the crypto: signature, ephemeral DH, HKDF, AEAD. On the client certificate, it says only: "you will rarely use client certificates because they complicate things for both clients and the server" (p. 268). It adds that the handshake can require a client certificate (p. 265). The words "mutual TLS" and "mTLS" do not appear (0 occurrences in the 380 pages). Revocation removes a certificate before its expiry: CRL is a list of revoked certificates, OCSP an online query of a certificate's status. The book cites them only as field names in a listing (p. 261). HSTS (HTTPS imposed on a site) and certificate pinning (accepting only one specific certificate) are at 0 occurrences. 0-RTT sends data from the very first message of a resumed session (a client already known, with no full handshake). The book names it only in the label of figure 13-2 (p. 267), without explaining it and without a word on replay. An attacker can resend that first data as is (RFC 8446, section 8, outside the book), so 0-RTT is not without danger.

My reading

Outside the book. In Go, it is crypto/tls with ClientAuth: tls.RequireAndVerifyClientCert and a ClientCAs. 0-RTT data can be replayed (see above): put in it only requests that are harmless if executed twice (RFC 8446, section 8). For the rest, my articles mTLS, SNI and certificate-session binding and Double-gate CRL cover what is missing.

11Quantum breaks RSA and curves, not AES: double the keys, go hybrid

A quantum computer computes with qubits, quantum bits that are 0 and 1 at once, which speeds up certain calculations. One large enough does not exist yet, but it would threaten "all the public-key crypto deployed or standardized as of this writing" (p. 273). Two algorithms decide everything. Shor's algorithm (1995) factors and computes discrete logarithms (recovering a from g^a, idea 9) in polynomial time, without a cost explosion. RSA, DH and curves fall. The book says that "you could reduce the security of RSA or elliptic curve cryptography to that of Caesar’s cipher" (p. 281).

Grover's algorithm (1996) searches among n items in the square root of n tries. For AES-128, "you’ll find the key in time proportional to 2^64, rather than 2^128 if you had only classical computers" (p. 283). The conclusion is reassuring: "The bottom line is that you can salvage symmetric crypto algorithms from quantum computers by doubling the key or hash value size." (p. 283). Hence AES-256 and a hash twice as long. Public-key algorithms, for their part, must be replaced.

No date is promised. In mid-2024, the record was 1,121 qubits. The book states: "we’d need to keep millions of qubits stable for weeks to break any crypto" (p. 284).

On the replacement side, NIST launched a contest in 2017 (69 submissions), then announced four standards in July 2022 (pp. 273, 290). CRYSTALS-Kyber is a KEM (key encapsulation mechanism: it establishes a shared key, like DH). CRYSTALS-Dilithium and Falcon sign. SPHINCS+ signs from hashes only, so as not to depend on a single family of mathematical problems (p. 290). The book still calls them draft standards (FIPS, the US federal information processing standards: 203, 204, 205). It expects them to be used in hybrid mode, alongside a classical algorithm. The book says they "are expected to first be used in hybrid modes" (p. 290), for example Kyber with X25519 in TLS.

The priority is key exchange, not signature. If a signature falls, you revoke the key (declare it invalid) and re-sign. But for a vulnerable cipher, "all transmitted ciphertext could be compromised" (p. 292). This is the idea now called harvest now, decrypt later, though the book does not use the expression (0 occurrences). Another caution, about the finalist SIKE: "SIKE was found to be completely broken shortly after the announcement" (p. 290). And early post-quantum implementations can be less secure than classical ones (p. 292).

My reading

Outside the book, NIST source. The names ML-KEM, ML-DSA and SLH-DSA, absent from the book (0 occurrences), designate Kyber, Dilithium and SPHINCS+. They are the standards FIPS 203, 204 and 205, approved by NIST on 13 August 2024 (csrc.nist.gov). A hybrid exchange X25519 plus ML-KEM (X25519MLKEM768) is specified by the IETF for TLS 1.3. It is enabled by default in crypto/tls since Go 1.24 (February 2025).

12Hash without ambiguity, hash the whole context, and the memory aid

Two traps from the book's last chapter, devoted to cryptocurrencies, apply everywhere. First trap, domain separation: two different inputs must never give the same string to hash. Hashing the concatenation of two fields is ambiguous: "you’ll get the same hash value for A = COL and B = LISION as for A = CO and B = LLISION" (p. 305). This is not a collision of the function, but of the application. The remedy is to "encode the application’s input values into a string that’s unique per input, with no ambiguous encoding" (p. 305), for example with a separator: COL$LISION against CO$LLISION. It is the same fault as the variable-length keys of idea 6 and the associated data of idea 7.

Second trap, Fiat-Shamir, the technique that makes an interactive proof (the verifier sends a random challenge) non-interactive by computing the challenge with a hash. If that hash forgets a value, the proof can be forged. The book shows it with Schnorr's proof, which proves you know a behind the public key A = aG. The prover draws a nonce r and publishes R = rG. They compute the challenge c = H(G || R || A) and answers s = r + ca. The verifier recomputes c and checks sG = R + cA, true since sG = rG + caG (p. 329). Without R in the hash, c = H(G || A) no longer depends on R. The attacker fixes c and chooses s. Then they compute R = sG − cA, and the equality holds without knowing a (p. 330). The lesson: "the importance of including all necessary values in the hash function input" (p. 330). Against replay, the book advises putting the prover's identity, a session identifier or a timestamp in the hash (p. 330).

My reading

Outside the book. In practice, the signed token user_id|amount, where 1|23 and 12|3 give the same string, is the everyday case. Encode with a length in front of each field, or in canonical JSON. Applied to a token: include the audience and the validity period in what is signed, otherwise a token signed for one service works for another.

Memory aid: what the book makes you choose and avoid, with pages. The "encrypt" and "sign" rows mix an explicit rule and a deduction of mine, flagged by the reading line.

NeedPickAvoidPages
HashSHA-2, SHA-3, BLAKE2 or BLAKE3MD5, SHA-1, CRC for security116, 127, 129
AuthenticateHMAC with a named hash, constant-time comparisonH(key, message), byte-by-byte comparison142-145, 153-154
EncryptAEAD (AES-GCM, ChaCha20-Poly1305), nonce never repeatedECB, RC4, CBC with constant IV, encryption without MAC74, 78, 102, 160, 265
SignEd25519 or ECDSA, or 2,048-bit RSA with PSS or FDHTextbook RSA, repeated ECDSA nonce199, 204, 244-249
Exchange keysAuthenticated ephemeral DH on X25519, then KDFAnonymous DH, unchecked parameters and points225, 229, 249
RandomnessSystem CSPRNGrand, mt_rand, homemade seed32, 34

My honest take

Two questions mattered to me. Does this book teach me to choose and use crypto without making it my job? Yes, and that is its best argument. Can it serve me directly in Go or PHP? No: the examples are mostly in Python and OpenSSL, so you must translate yourself. I read it as you listen to a sincere cryptographer talking to a developer: you keep the verdicts, you save the proofs for later.

Its strength: it confesses. The NSA and NIST knew about the length extension of SHA-2 (p. 136). You come away convinced that the fault lies in the use.

Two reservations. The author judges functions he designed: for an ordinary website, SHA-256 is enough, so keep BLAKE for when speed matters. Then, three figures or rankings are wrong (list at the end of this note): redo a calculation before relying on them.

My advice: read chapters 1 and 2 on attack models and randomness. Then read chapters 4, 6, 7 and 8 on modes, hashing, MACs and AEAD. Finish with chapter 13 on TLS. Skim chapters 10 to 12 for the rules, skip the math proofs, and skim chapter 14.

Odilon

Still valid in 2026?

The verdicts last: MD5 and SHA-1 to avoid, AEAD mandatory, Ed25519 to sign, TLS 1.3. What changes the reading:

Who is it for?

Read it if

  • you write a PHP or Go backend that signs tokens, encrypts data or speaks TLS, and you want to know what to choose.
  • you have a few years on the job and have used openssl, a JWT or a certificate without knowing why it held.
  • you are preparing an mTLS deployment and want the cryptographic basics before the configuration.
  • you like an author who proves every piece of advice with a real case.

Skip it if

  • you are looking for Go or PHP recipes to copy: the code is mostly Python and OpenSSL.
  • you expect a guide to passwords, client certificates or revocation: the book does not cover them.
  • math proofs put you off: the passages on RSA, curves and proofs are full of them.

Going further

Note based on: pages 12-22, 25-41, 44-56, 62-63, 71-85, 88-89, 102-109, 112-113, 115-145, 148-173, 198-213, 216-230, 241-250, 256-273, 281-293, 297-299, 304-305, 309-310 and 330-331 read in detail. Still triaged but not read: the internal structure of AES, ChaCha20 detail, curve arithmetic, chapter 9 and most of chapter 15. The "outside the book" boxes, the flagged calculations and the absence checks are mine.

Comments (0)

On the same shelf

Networking and security

See the whole shelf (8 notes) →

Browse the whole library

More book notes coming: one book at a time, the marrow only.