Topic 13

Common Crypto Mistakes

Pitfalls

Good primitives fail in bad hands, and most real-world crypto breaks are not broken algorithms but broken usage. This topic is a defender's field guide to the recurring mistakes — rolling your own crypto, reusing nonces, hard-coding keys, weak randomness, mixing up hashing and encryption — so you can spot them in Meridian's code and in the wild.

It is the "what actually goes wrong" capstone of the chapter, and every item here has appeared in a real, named breach. The through-line is simple: use vetted libraries and standard constructions, source your randomness and keys correctly, and match the primitive to the job.

Rolling Your Own

Custom crypto and clever homebrew schemes almost always contain a fatal flaw an expert would spot in minutes. The rule — use vetted libraries and standard constructions — is not gatekeeping; it is the accumulated experience of a field where subtle mistakes are invisible until they are catastrophic. Treat "I invented an encryption scheme" as a red flag, in your own code and in a dependency's.

Nonce and IV Reuse

The single most damaging usage bug, and the one from the AES topic worth repeating: reusing a nonce or IV breaks GCM and stream ciphers completely. It hides in counter resets, in forked processes that share initial state, and in "random" nonces drawn from a weak generator that eventually collide. Whenever you see per-message encryption, the first question is where the nonce comes from and whether it can ever repeat.

Key Management Failures

The algorithm is irrelevant once the key is in a public repo. Keys committed to source control, baked into container images, dropped in config files, shared across environments, or never rotated are the most common real-world crypto failure — not because the math broke, but because the secret leaked. Keys belong in a KMS or secret store, scoped per environment and purpose, and rotated (Chapter 3).

Weak or Misused Randomness

Using a non-cryptographic generator — rand(), Math.random() — for keys, tokens, or nonces produces predictable secrets, and predictable secrets are no secrets. Anything security-relevant must come from a cryptographically secure source: /dev/urandom, Python's secrets, or crypto.randomBytes. Bad randomness has broken real systems whose crypto was otherwise flawless.

Wrong Tool for the Job

Encrypting when you should hash (passwords), hashing when you should MAC (integrity with origin), or encrypting without authenticating — each mismatch was covered earlier and reappears here as a checklist. Hash-and-salt-and-slow for passwords, HMAC for keyed integrity, AEAD for confidentiality, signatures for non-repudiation. Using one where another belongs is a design error no amount of key length fixes.

"Encrypted" Does Not Mean "Secure"

The same word covers wildly different guarantees. Encrypted-but-unauthenticated data can be tampered with; encrypted-with-a-leaked-key is public; encrypted-with-ECB leaks structure; encrypted-with-a-reused-nonce is broken.

The guarantee comes from the whole construction and its key management, not from the word "encrypted." When someone says data is encrypted, the real questions are which mode, which key, and where that key lives.

Common Mistakes
  • Inventing a cipher, a token format, or a "lightweight" scheme instead of using an established library and construction.
  • Reusing nonces or IVs — the highest-impact usage bug — usually introduced by a reset counter or a weak RNG.
  • Committing keys or secrets to git, baking them into images, or reusing one key everywhere, so a single leak is total.
  • Generating keys, tokens, or nonces from a non-cryptographic RNG, making "random" secrets guessable.
  • Matching the wrong primitive to the task — encrypting passwords, hashing where a MAC was needed, encrypting without authenticating.
Best Practices
  • Use high-level, misuse-resistant libraries (libsodium/NaCl, Tink, the platform's AEAD API) that make the safe choice the default.
  • Source all keys, tokens, and nonces from a cryptographically secure RNG, and let the library manage nonces where it can.
  • Keep keys out of code entirely — fetch from a KMS or secret store, scope per environment and purpose, and rotate them (Chapter 3).
  • Match the primitive to the job: hash-plus-salt-plus-slow for passwords, HMAC for keyed integrity, AEAD for confidentiality, signatures for non-repudiation.
  • Run a secret scanner over your code and history to catch committed keys before an attacker does (Chapter 12).
Comparable toolsSafe libraries libsodium · Tink · ageSecret scanning gitleaks · truffleHogRandomness /dev/urandom · secrets · crypto.randomBytes

Knowledge Check

Why is "don't roll your own crypto" such a firm rule?

  • Subtle implementation flaws stay invisible until they are catastrophic
  • Custom algorithms are always mathematically weaker than AES
  • Writing crypto code is against most software licenses
  • Standard libraries run faster than any custom code could

A team's encryption is textbook-correct but their keys are generated with Math.random(). What is the flaw?

  • A weak RNG makes the keys predictable
  • Math.random() makes the ciphertext too short to be secure
  • The cipher will refuse to run with a weak key source
  • It only matters if they also reuse nonces

Someone says "the data is encrypted, so it's secure." Why is that not enough to conclude?

  • It depends on the mode, key management, and nonce handling
  • Encryption always makes data fully secure with no caveats
  • Encrypted data is secure only if it is also compressed
  • The statement is fine as long as AES was used

You got correct