Chapter 02 · Part I · Foundations

Cryptography that matters

Almost every security promise a computer makes, from the padlock in your browser to a software update you can trust, rests on four tools: hash functions, ciphers, key exchange and digital signatures. This chapter is about what each one actually guarantees, what it quietly does not, and the handful of mistakes that break real systems far more often than anyone breaks the mathematics.

256 bitsthe length of every SHA-256 fingerprint, whether the input is one letter or a whole disk image
2128 ≈ 3.4 × 1038the number of possible AES-128 keys; trying them all is not a matter of better hardware
3072 ≈ 256 ≈ 128NIST rates RSA-3072, 256-bit elliptic curves and AES-128 at the same 128-bit security strength

Common mix-up: encrypted does not mean tamper-proof. Plain encryption hides what a message says, but on its own it does not stop someone from changing it on the way, sometimes in precise, predictable ways. Secrecy and integrity are separate jobs. Modern systems use authenticated encryption, which does both at once, and the tamper test below shows why that matters.

Background first: VPNs, tunnels & overlays on networks.jelia.nyc shows where encrypted tunnels sit in a network and what they carry. This chapter assumes that picture and opens up the machinery inside the tunnel.

The rules of the game

Four tools, and one principle

Cryptographers tell the story with three characters. Alice wants to send a message to Bob. Between them is a network neither controls, and on it sits Eve, who can read everything that passes and sometimes change it. Every router, Wi-Fi access point and cable on the path is a possible Eve. The goal is not to stop Eve from seeing packets, which is impossible, but to make what she sees useless and what she changes detectable.

In 1883 the Dutch cryptographer Auguste Kerckhoffs set down a rule that still governs the field: a cipher must stay secure even if everything about it except the key falls into enemy hands. This is Kerckhoffs's principle. It is why AES, SHA-256 and ChaCha20 are published in full, in standards anyone can download, and attacked in public by researchers for years before anyone relies on them. A secret algorithm is not stronger; it is merely unexamined. When it leaks, and algorithms always leak because they ship inside software, you cannot change it the way you can change a key.

The same idea explains the most repeated advice in security engineering: don't roll your own crypto. Designing a cipher is hard, but the bigger danger is subtler. Most real-world breaks come from building a system out of perfectly good primitives and wiring them together wrongly: the right cipher in the wrong mode, a good key generated from a weak random number, a sound signature checked by code that never actually looks at the result. The safe path is a well-reviewed library with a high-level interface, such as libsodium or your platform's TLS stack, that makes these decisions for you.

ToolWhat it guaranteesWhat it does not doNames you will meet
Hash functionA short, fixed-size fingerprint; nobody can find two inputs with the same oneHide anything (no key), or slow down password guessingSHA-256, SHA-3, BLAKE2
Symmetric cipher (AEAD)Secrecy and integrity, for anyone holding the shared keyGet the key to the other side in the first placeAES-GCM, ChaCha20-Poly1305
Key exchangeTwo strangers agree on a shared secret over a public wireTell you who you agreed it withX25519, ECDH, ML-KEM
Digital signatureThis exact data was approved by the holder of one private keyProve that key belongs to the person you thinkEd25519, ECDSA, RSA-PSS, ML-DSA

Each tool covers the previous one's blind spot, and real protocols stack them. A TLS connection (Chapter 3) uses key exchange to agree on keys, a hash-based function to stretch them, authenticated encryption to protect the data, and signatures to prove the server is genuine. WireGuard does the same with Curve25519, ChaCha20-Poly1305 and BLAKE2s.

Fingerprints

Hash functions and the avalanche

A cryptographic hash function takes any input, a word or a whole hard disk, and grinds it into a fixed-size output called a digest. SHA-256, standardized by NIST in FIPS 180-4, always produces 256 bits, usually written as 64 hexadecimal characters. There is no key. Anyone can compute the hash of anything, and the same input always gives the same output.

What makes it cryptographic is three kinds of resistance, from weakest to strongest requirement:

  • Preimage resistance. Given a digest, you cannot find any input that produces it. The function only runs forward.
  • Second-preimage resistance. Given one input, you cannot find a different input with the same digest. Nobody can swap your file for a lookalike.
  • Collision resistance. You cannot find any two different inputs with the same digest, even when you get to choose both. This is the hardest property to keep, and the first to fall when a hash ages.

Collisions must exist, since infinitely many inputs share 2256 outputs. The point is that nobody can find one. A good hash behaves like a perfect scrambler: change one character of the input and about half of the output bits flip, with no visible relationship between the two digests. That is the avalanche effect, and you can watch it below.

expected collision work ≈ 2n/2  →  SHA-256: 2128
Because of the birthday paradox, finding any two inputs with the same n-bit digest takes about the square root of the output space. That is why a 256-bit hash gives 128-bit collision security, and why 128-bit hashes like MD5 were always marginal.
Instrument 1

The avalanche lab

Type in either box. The page computes the real SHA-256 of both strings on every keystroke and draws the second digest as a 16 × 16 grid of its 256 bits. A filled square is a 1, an empty one a 0; squares marked with an orange ring are bits that differ from the first digest. Change a single letter and count how many flip. It hovers near 128, half of them, every time.

Bits flipped–
Share of 256–
Running average–
Check–

The hash is computed by a SHA-256 written out in this page, so it works offline and over plain http. Where your browser offers the built-in crypto.subtle.digest, the Check readout confirms both give the same answer.

When fingerprints age

Why MD5 and SHA-1 were retired

Hash functions don't fail all at once. They erode, and collision resistance goes first. MD5, with its 128-bit output, was already showing cracks in the mid-1990s, and practical collisions were demonstrated in 2004. The IETF's RFC 6151 records that MD5 must not be used where collision resistance is required, such as digital signatures. SHA-1 followed: in 2017 researchers from CWI Amsterdam and Google published two different PDF files with the same SHA-1 digest, the "SHAttered" result. NIST's transition guidance, SP 800-131A, disallows SHA-1 for generating digital signatures.

Why does a collision break signatures? Because systems sign the hash of a document, not the document. If an attacker can prepare two documents with the same hash, one harmless and one not, and get the harmless one signed, the signature is equally valid on the other. The defense is to use SHA-256 or SHA-3 for anything new and to treat any remaining MD5 or SHA-1 in a signing path as a defect to fix. MD5 survives harmlessly as a checksum against accidental corruption, where nobody is trying to collide it.

One more thing a hash is not: a password store. SHA-256 is designed to be fast, billions of hashes a second on a graphics card, which is exactly wrong when the attacker is guessing passwords. Passwords need deliberately slow, salted functions such as Argon2id, scrypt or bcrypt. That story belongs to Chapter 4.

Go deeper: how SHA-256 actually scrambles

SHA-256 pads the message to a multiple of 512 bits (appending a 1 bit, zeros, and the original length), splits it into 64-byte blocks, and feeds them one at a time through a compression function. The function keeps eight 32-bit words of state. For each block it expands the 16 input words into 64, then runs 64 rounds that mix the state with additions, bitwise choices, majorities and rotations. The eight starting words are the fractional parts of the square roots of the first eight primes, and the 64 round constants come from the cube roots of the first 64 primes, "nothing up my sleeve" numbers that anyone can check were not chosen to hide a weakness.

The construction, called Merkle–Damgård, has a known quirk: knowing H(m) and the length of m lets you compute the hash of m followed by extra data, without knowing m. That is why you must not build a keyed check as H(secret ‖ message). HMAC, below, exists precisely to avoid this. SHA-3, standardized in FIPS 202, uses a different "sponge" design that doesn't have the quirk.

One key, both ends

Ciphers, modes, and the pattern that leaks

A symmetric cipher uses the same secret key to lock and unlock. It is fast, which is why every byte of bulk data on the internet is protected this way. Ciphers come in two shapes. A block cipher such as AES (FIPS 197) is a keyed scrambler for fixed-size chunks: 128 bits in, 128 bits out, with a 128-, 192- or 256-bit key. A stream cipher such as ChaCha20 (RFC 8439) generates a long, unpredictable keystream from the key and a nonce (a "number used once"), and XORs it with the data.

A block cipher on its own only handles one 16-byte block. To encrypt a whole message you need a mode of operation, a recipe for applying it block after block. The obvious recipe, encrypting each block independently, is called ECB, Electronic Codebook. It is also the classic mistake. Because the same key turns the same plaintext block into the same ciphertext block every time, ECB preserves patterns. Encrypt a picture with large flat areas and the outline of the picture survives, drawn in a different texture.

The fix is to make every block's encryption depend on something that changes. CBC mode XORs each plaintext block with the previous ciphertext block before encrypting, starting from a random initialization vector. CTR mode encrypts a counter (nonce plus block number) and XORs the result with the data, which turns a block cipher into a stream cipher. Either way, identical plaintext blocks no longer produce identical ciphertext. The instrument below lets you see the difference rather than take it on trust.

Instrument 2

Watch ECB leak the picture

The left panel is a small padlock image, 96 × 64 pixels, drawn by the page. The right panel is the same pixels encrypted 8 bytes at a time with a keyed block cipher, shown as one shade per byte. Switch modes. In ECB the padlock is still plainly there. In CBC and CTR it dissolves into noise. The last mode encrypts a second picture with the same CTR nonce and XORs the two ciphertexts, which cancels the keystream and shows what nonce reuse gives away.

Mode–
Ciphertext blocks–
Distinct blocks–
Blocks that repeat–

Toy cipher, for illustration only. To keep the page small and instant, the block cipher here is a four-round 64-bit Feistel network invented for this demonstration. It is not AES, it is not secure, and nothing about it should be reused. The mode behavior it shows is exactly what AES does in the same modes.

Secrecy plus integrity

Authenticated encryption and the nonce rule

CBC and CTR fix the pattern leak, but they still only provide secrecy. Modern protocols use AEAD, authenticated encryption with associated data, which encrypts the message and computes a cryptographic checksum over it in one operation. The two you will meet everywhere are AES-GCM (NIST SP 800-38D), which pairs AES in counter mode with a polynomial authenticator called GHASH, and ChaCha20-Poly1305 (RFC 8439), which pairs the ChaCha20 stream cipher with the Poly1305 authenticator. Both attach a 128-bit tag. On decryption, if a single bit of the ciphertext, the tag or the associated data has changed, the operation fails and returns nothing.

The "associated data" is information that must be authenticated but not hidden, such as a packet header or a record number. Binding it in means an attacker can't take a valid encrypted body and staple it to a different header.

AEAD has one hard rule: never reuse a nonce with the same key. Both GCM and ChaCha20 are counter-mode designs underneath, so the nonce selects the keystream. Encrypt two messages with the same key and nonce and the keystreams are identical; XOR the two ciphertexts and the keystream cancels, leaving the XOR of the two plaintexts, which is often enough to read both. The last mode in Instrument 2 shows exactly that. For GCM it is worse still: a repeated nonce can expose the internal authentication key, after which forged messages pass the check. SP 800-38D therefore limits a key used with random 96-bit nonces to 232 messages.

How do real systems avoid repeats? TLS 1.3 and WireGuard use a counter: each message gets the next number, and the key is replaced long before the counter could wrap. Where a counter is awkward, for example many machines sharing a key, use a nonce-misuse-resistant mode such as AES-GCM-SIV, or a design with a longer random nonce such as XChaCha20-Poly1305, so that collisions are vanishingly unlikely.

AEADKeyNonceTagWhere it shines
AES-128-GCM / AES-256-GCM128 / 256 bits96 bits128 bitsCPUs with AES instructions; most server and desktop traffic
ChaCha20-Poly1305256 bits96 bits128 bitsFast in plain software; phones and devices without AES hardware; WireGuard
Integrity

MACs, and why encryption alone is malleable

A message authentication code (MAC) is a keyed checksum. Whoever holds the key can compute a short tag for a message; whoever holds the same key can recompute it and compare. Without the key you can't produce a valid tag for any message you haven't seen tagged. The most widely used construction is HMAC (RFC 2104), which wraps a hash function in two passes with the key mixed in differently each time, sidestepping the length-extension quirk mentioned above.

Why does encryption need it? A stream cipher encrypts by XOR, and XOR has no memory of what was intended. If Eve flips bit 3 of byte 5 of the ciphertext, Bob's decryption flips bit 3 of byte 5 of the plaintext, and nothing else. She can't read the message, but if she can guess its layout (and protocols have predictable layouts) she can change a known field to a value of her choosing. That property is called malleability, and it applies even to a perfectly strong cipher. The tamper test lets you try it with real ChaCha20.

Instrument 3

The tamper test

Alice sends Bob a 20-character instruction encrypted with ChaCha20, the real RFC 8439 cipher, under a demo key. The 20 boxes are the ciphertext bytes as they cross the wire. Click any box to flip its lowest bit, or use the preset that rewrites the amount. Below, see what Bob decrypts. Then switch on the tag: Alice adds an HMAC-SHA-256 over the ciphertext, Bob recomputes it before decrypting, and any change at all is rejected.

Bytes changed0
Tag sent–
Tag recomputed–

ChaCha20-Poly1305 does this job with the Poly1305 authenticator instead of HMAC; HMAC-SHA-256 is used here because it is built from the hash you met above. The key, nonce and message are made up for this page.

Go deeper: encrypt-then-MAC, and comparing tags safely

When people combined a cipher and a MAC by hand, there were three orders to choose from: MAC the plaintext and encrypt both, MAC the plaintext and send it alongside, or encrypt first and MAC the ciphertext. Only the last, encrypt-then-MAC, is safe in general, because the receiver can reject a forged message before decrypting anything. Older protocols chose differently and paid for it with a long series of "padding oracle" problems, where tiny differences in how a server rejected bad messages leaked the plaintext. AEAD modes build in the right order, which is the main reason to use them instead of assembling your own.

The final comparison matters too. Comparing the received tag with the expected one byte by byte and stopping at the first mismatch leaks, through timing, how many leading bytes were right. Libraries provide a constant-time comparison (for example hmac.compare_digest in Python or crypto.timingSafeEqual in Node.js); use it.

Agreeing in public

Key exchange: mixing paint

Symmetric encryption has a chicken-and-egg problem. Alice and Bob need a shared secret key before they can talk privately, and they can't send it over the wire Eve is watching. In 1976 Whitfield Diffie and Martin Hellman published the answer: two parties can agree on a secret in full public view.

The usual picture is paint. Everyone agrees, publicly, on a starting color: yellow. Alice picks a secret color and mixes it into a pot of yellow; Bob does the same with his own secret color. They swap pots in public. Alice adds her secret color to Bob's pot; Bob adds his to Alice's. Both pots now contain yellow plus Alice's secret plus Bob's secret, so they are the same color. Eve saw the yellow and both mixed pots, but to get the final color she would have to un-mix paint, separating a secret color back out of a mixture. That is the one-way step.

In numbers, mixing is exponentiation in modular arithmetic. Everyone knows a prime p and a base g. Alice picks a secret a and sends A = ga mod p; Bob picks b and sends B = gb mod p. Alice computes Ba and Bob computes Ab, and both get gab mod p. Recovering a from A is the discrete logarithm problem: easy to state, and with large enough numbers, no known classical method solves it in any useful time.

Ba = (gb)a = gab = (ga)b = Ab  (mod p)
Both sides reach the same number by different routes. Eve has g, p, A and B, but every route to gab needs a or b.
Instrument 4

Diffie–Hellman by hand

Pick the two secrets with the sliders and a prime size with the chips. The page does the real modular arithmetic and shows both sides arriving at the same shared number, alongside the paint picture. It also plays Eve: it tries every possible secret until one matches Alice's public value, and reports how many tries that took. With these toy primes Eve wins instantly. Real key exchange uses numbers so large that the same search would outlast the universe.

StepWho knows itValue
Shared secret matches–
Eve's brute-force tries–
Possible secrets–
The real thing

X25519, forward secrecy and HKDF

Classic Diffie–Hellman needs primes of 3072 bits for 128-bit security. Elliptic-curve Diffie–Hellman does the same job with much smaller numbers, because the discrete logarithm problem on a well-chosen elliptic curve has no known shortcut of the kind that helps with plain modular arithmetic. The dominant choice today is X25519 (RFC 7748), Diffie–Hellman on Curve25519: public keys and the shared secret are 32 bytes, and the design avoids many pitfalls of older curves, such as having to validate that a received point really lies on the curve.

The single most important habit is to use ephemeral keys: a fresh key pair for every connection, thrown away when it ends. That gives forward secrecy. If a server's long-term signing key is stolen next year, the attacker still can't decrypt traffic recorded today, because the keys that protected it no longer exist anywhere. TLS 1.3 requires ephemeral key exchange for exactly this reason; WireGuard rotates its session keys every couple of minutes.

The raw Diffie–Hellman output is not used directly as a key. It is a random-looking group element, not uniformly random bytes, and one exchange usually needs several keys (one per direction, for example). A key derivation function turns it into as many independent keys as needed. The standard one is HKDF (RFC 5869), built on HMAC in two steps: extract concentrates the input's randomness into one strong pseudorandom key, and expand stretches that into labeled output keys, so that "client write key" and "server write key" can never collide.

Go deeper: key exchange does not know who is on the other end

Diffie–Hellman protects against a passive Eve, who only listens. Against an active attacker who can intercept and replace messages, plain key exchange fails quietly: she runs one exchange with Alice and another with Bob, and relays between them, reading everything. Each side has a perfectly good shared key, just with the wrong party. This is the man-in-the-middle problem, and the cure is authentication: one or both sides sign their key-exchange messages with a long-term key the other can check. That is the job of signatures, next, and of certificates in Chapter 3.

Proof of origin

Digital signatures: a stamp only one can make

Public-key cryptography splits a key in two. The private key is kept secret by its owner; the public key is published. For signatures, the private key makes the stamp and the public key checks it. Alice signs a document by computing a value from its hash and her private key. Anyone holding her public key can verify that the signature matches that exact document; change a single bit of the document and verification fails.

A valid signature proves two things: integrity (the data is exactly what was signed) and origin (it was signed with the private key matching this public key). Unlike a MAC, the verifier can't create signatures, so a signature can be checked by the whole world and still convince a third party. That is why software updates, firmware, package registries and certificates are signed rather than MACed.

What a signature does not prove is that the public key belongs to who you think. If Eve hands you her public key labeled "Alice," her signatures will verify perfectly. Binding keys to identities is the work of public-key infrastructure and certificates, in Chapter 3; signing the software you install is covered in Chapter 16.

AlgorithmBased onPublic keySignatureNote
Ed25519 (RFC 8032)Edwards curve32 bytes64 bytesDeterministic: no per-signature random number to get wrong
ECDSA P-256 (FIPS 186-5)Elliptic curve65 bytes (33 compressed)64 bytes raw (about 72 in DER encoding)Needs a fresh secret number per signature, or the RFC 6979 deterministic method
RSA-3072 (FIPS 186-5)Factoring384 bytes384 bytesOldest and most widely supported; use PSS padding for new designs
Go deeper: the per-signature number that must never repeat

ECDSA and its predecessor DSA use a secret random number, usually called k, for every signature. If the same k is ever used for two different messages under the same key, simple algebra on the two signatures reveals the private key, and if k is merely predictable or biased, statistical methods can recover the key from many signatures. It is the signature counterpart of nonce reuse in AEAD, and it has caused real key compromises in deployed products.

The defenses are mechanical. RFC 6979 derives k deterministically from the private key and the message hash, so no random number generator is involved. Ed25519 builds the same idea into the algorithm. If you use ECDSA, make sure your library uses RFC 6979 or a vetted random source; if you are choosing fresh, Ed25519 removes the hazard.

How big is big enough

Key sizes and why brute force is hopeless

A well-designed cipher has no shortcut: the best attack is to try keys until one works. Each extra key bit doubles the work. That doubling is the whole story. A 40-bit key, the legal export limit for some software in the 1990s, has about a trillion possibilities, which a single modern machine can sweep quickly. A 56-bit DES key fell to purpose-built hardware in the late 1990s. A 128-bit key has 272 times as many possibilities as the DES key; no improvement in hardware closes a gap like that.

Different algorithms need very different key lengths for the same strength, because their best attacks differ. NIST's key-management recommendation, SP 800-57 Part 1, expresses everything as a security strength in bits, meaning "as hard as brute-forcing a key of this many bits."

Security strengthSymmetricRSA / classic DH modulusElliptic curve key
112 bits(3-key Triple DES, now retired)2048 bits224–255 bits
128 bitsAES-1283072 bits256–383 bits
192 bitsAES-1927680 bits384–511 bits
256 bitsAES-25615360 bits512+ bits

Source: NIST SP 800-57 Part 1 Revision 5, Table 2 (comparable security strengths). X25519 and Ed25519 use a 255-bit curve and are treated as 128-bit strength.

Instrument 5

The brute-force clock

Slide the key length and pick an attacker. The page computes the expected time to find a key by trying them, which on average takes half the key space, and places it on a logarithmic timeline from a microsecond to far beyond the age of the universe. Every step right on the slider doubles the time.

Keys to try (average)–
Expected time–
Ages of the universe–

Attacker speeds are round, hypothetical numbers chosen to span the range. The age of the universe is taken as 13.8 billion years.

The quiet dependency

Randomness: every key starts as a coin toss

All of the strength above assumes the key was chosen uniformly at random. A 128-bit key generated from a source with only 32 bits of real unpredictability is a 32-bit key, however long it looks. Keys, nonces, salts and the per-signature numbers just discussed all need a cryptographically secure pseudorandom number generator (CSPRNG): one whose output can't be predicted even by someone who has seen lots of previous output.

Ordinary random functions are not that. JavaScript's Math.random, C's rand and most language "random" modules are built for simulations and games: fast and statistically even, but with internal state that can be reconstructed from a modest sample of output. MITRE catalogs this as CWE-338. The fix is easy, because every operating system already gathers unpredictability from hardware events and exposes a CSPRNG. In a browser that is crypto.getRandomValues; in Node.js, crypto.randomBytes; in Python, the secrets module.

// Wrong: Math.random is not a CSPRNG; its output can be predicted
const token = Math.random().toString(36).slice(2);
// Right: 32 bytes from the operating system's CSPRNG
const key = crypto.getRandomValues(new Uint8Array(32));

The other common failure is not using a weak generator but reusing a good value: a hard-coded key in source code, the same IV for every message, a test key shipped to production. Treat every key and nonce as something generated at the moment of need, stored in a key-management system or hardware module, and rotated on a schedule. The OWASP Cryptographic Storage Cheat Sheet is a good checklist.

The next transition

Quantum computers and post-quantum cryptography

A sufficiently large quantum computer would change the picture for public-key cryptography. In 1994 Peter Shor described a quantum algorithm that factors large numbers and computes discrete logarithms efficiently. That would break RSA, classic Diffie–Hellman and elliptic-curve cryptography alike: X25519, ECDSA and Ed25519 included. No quantum computer today comes close to the size needed, and nobody can say with confidence when one will.

Symmetric cryptography and hashes fare far better. Grover's algorithm can search an unstructured space of N possibilities in about √N quantum steps, which in the idealized worst case halves the effective bit-strength of a key: AES-128 would offer roughly 64-bit security against such a machine, AES-256 roughly 128. In practice Grover's search parallelizes badly and each step is expensive, so the real threat is far smaller; still, long-lived systems increasingly choose 256-bit symmetric keys as cheap insurance.

The urgent risk is harvest now, decrypt later. An adversary can record encrypted traffic today and store it until a quantum computer exists. Anything that must stay secret for ten or twenty years is therefore already exposed if its key exchange is quantum-vulnerable. That is why key exchange is moving first.

In August 2024 NIST published the first three post-quantum standards, built on mathematical problems with no known quantum shortcut:

StandardAlgorithmReplacesBuilt on
FIPS 203ML-KEM (from CRYSTALS-Kyber)Key exchange (ECDH, RSA key transport)Module lattices
FIPS 204ML-DSA (from CRYSTALS-Dilithium)Signatures (ECDSA, Ed25519, RSA)Module lattices
FIPS 205SLH-DSA (from SPHINCS+)Signatures, as a conservative backupHash functions only

The transition is happening in hybrid form: a connection runs a classical exchange such as X25519 and ML-KEM together and derives its keys from both, so it stays secure unless both are broken. Major browsers and TLS libraries already offer an X25519 plus ML-KEM-768 hybrid. A NIST draft, IR 8547, proposes deprecating the weaker quantum-vulnerable public-key algorithms after 2030 and disallowing all of them after 2035. The practical lesson for defenders is crypto agility: know where your systems use which algorithms, and make sure you can swap them by configuration rather than by rewrite.

Field guide

The mistakes that actually break systems

Strong algorithms are rarely the weak point. These are, and each has a clean fix. The CWE numbers are MITRE's catalog entries, useful when reading audit reports or scanner output.

MistakeWhat goes wrongFixCatalog
Homegrown or obsolete algorithmUnreviewed or broken (DES, RC4, MD5 or SHA-1 for signatures)AES-GCM or ChaCha20-Poly1305; SHA-256 or SHA-3; Ed25519CWE-327
ECB modeIdentical blocks encrypt identically; patterns leakUse an AEAD mode, never raw block encryptionCWE-327
Nonce or IV reuseKeystream cancels; GCM tags become forgeableCounter nonces, key rotation, or a misuse-resistant modeCWE-323
Encryption without authenticationCiphertext can be edited predictablyAEAD, or encrypt-then-MACCWE-327
Weak randomnessKeys and tokens can be predictedOS CSPRNG: getRandomValues, randomBytes, secretsCWE-338
Fast hash for passwordsBillions of guesses per secondArgon2id, scrypt or bcrypt (Chapter 4)CWE-916
Static, ephemeral-free key exchangeOne stolen key decrypts all recorded trafficEphemeral X25519 (forward secrecy)–
Hard-coded keysAnyone with the code or binary has the keySecrets manager or hardware module; rotateCWE-321
Where this comes from

Sources