Cryptography is rarely broken by attacking the mathematics. It is broken by the way developers use it: the wrong mode, the wrong random number generator, a key left in the source. "Cryptographic failures" sits second in the OWASP Top 10 2021, and most of it is visible to a careful reviewer who knows what to look for.

Is the code using home-grown cryptography?

If you see custom encryption routines, hand-built token schemes or XOR "encryption", flag it. The rule is to use vetted, maintained libraries and high-level APIs that make the safe choice the default, rather than assembling primitives yourself.

Are passwords stored correctly?

Passwords should be hashed with a slow, salted, purpose-built algorithm such as Argon2, scrypt or bcrypt. Red flags include MD5, SHA-1 or plain SHA-256 used directly on passwords, unsalted hashes, and reversible encryption of passwords. Fast hashes are designed to be fast, which is exactly what an attacker with a stolen database wants.

Is randomness coming from the right place?

Session IDs, reset tokens, API keys and anything else an attacker must not guess need a cryptographically secure random number generator. Flag general-purpose generators such as Math.random(), rand() and Python's random module when they produce security values, and look for tokens derived from timestamps or user IDs.

Are modes, keys and IVs handled properly?

  • ECB mode leaks patterns in the data. Prefer an authenticated mode such as AES-GCM.
  • Reused or fixed IVs and nonces can break the security of many modes completely.
  • Hardcoded keys in the source code, which is the same problem as any hardcoded secret.
  • Encryption without integrity. If data is encrypted but not authenticated, an attacker may be able to modify it undetected.
  • Outdated algorithms such as DES, RC4 and MD5 for anything security-relevant.

Is transport security switched off anywhere?

Search for code that disables certificate or hostname verification, often added to get past a test-environment error and then forgotten. Examples include verify=False, trust-all certificate managers and rejectUnauthorized: false. A TLS connection that does not verify the other end is only protected against passive eavesdropping.

# Looks harmless, defeats TLS
requests.get(url, verify=False)

Smaller details that matter

  • Compare secrets and MACs with a constant-time comparison function.
  • For JWTs, pin the accepted algorithms rather than trusting the token's own header.
  • Plan for key rotation: can you replace a key without redeploying everything?

Cryptographic review is a specialty, and subtle mistakes are easy to miss. For code that protects payments, health data or authentication, a focused secure code review by someone who reads this kind of code daily is well worth it.

Related reading: Code Review for Authentication and Authorization Flaws and Finding Business Logic Flaws in Code Review.

Key takeaways

  • Use vetted libraries and high-level APIs. Do not build your own cryptography.
  • Hash passwords with Argon2, scrypt or bcrypt, never with a fast hash.
  • Security-sensitive values need a cryptographically secure random source.
  • Look for ECB, reused IVs, hardcoded keys and disabled certificate verification.