The idea: protect sensitive data in transit and at rest

Cryptographic failures (OWASP Top 10:2025, A04) are what happens when sensitive data — passwords, session tokens, personal and payment data — is not protected with the right cryptography, or with any at all. The failure is rarely a broken algorithm; it is almost always a use mistake: data sent in the clear, a password stored with a fast unsalted hash, a cipher used in a mode that leaks structure, a key committed to a repository, or a secret token generated so predictably that it can be guessed.

This page runs five such mistakes against a fictional shop, shop.example, with users alice, carol (who both pick the password password1) and bob. The attacker is mallory. The core control is a vulnerable vs fixed switch: each scenario runs the same attack against the vulnerable app, then against the fixed app, so you can see exactly which control stops it. Every hash, ciphertext, key and token on the page is a short, deterministic fake value — the page teaches the ideas, it is not a working tool.

Two panels. Left, http://: alice's browser sends password=password1 to shop.example; mallory taps the wire and reads the password (CWE-319). Right, https:// with HSTS: the same request is a TLS record 9f3a c1d7; mallory taps the wire but sees only ciphertext.
Over plain HTTP an on-path attacker reads the password; over TLS with HSTS the same capture is just ciphertext.

Common CWEs behind A04: CWE-319 (cleartext transmission), CWE-327 (broken or risky crypto algorithm or mode), CWE-328 (use of a weak hash), CWE-916 (password hash without enough computational effort / no salt), CWE-321 (use of a hard-coded cryptographic key) and CWE-330 (use of insufficiently random values).

Data in transit: TLS and HSTS

On open Wi-Fi anyone nearby can read every packet. If the login goes over http://, the request body password=password1 crosses the air as readable text and mallory simply copies it (Demo 1, vulnerable). The fix is to serve the whole site over TLS (https://): the browser encrypts the request into a TLS record before it leaves the device, so the capture holds only ciphertext. HSTS (Strict-Transport-Security) tells the browser to never even try plain HTTP again, closing the downgrade window. That is CWE-319.

Password storage: salt and a slow hash

Databases leak. What matters is what the users table stored. Unsalted MD5 is fast and salt-free, so two problems appear at once (Demo 2, vulnerable): alice and carol show the same hash 7c6a180b, which already reveals that they share a password (CWE-916); and because the hash is fast and unsalted, a precomputed rainbow table maps it straight back to password1 in a single lookup (CWE-328).

The fix is a slow password hash with a per-user salt and a tunable cost: bcrypt or argon2id. A bcrypt hash carries its parameters and salt inline, $2b$12$<22-char salt><hash>; the 12 means 212 rounds per guess. Now alice and carol get different hashes despite the same password (the salt differs), rainbow tables are useless, and each brute-force guess is thousands of times slower.

Two users tables. Left, unsalted MD5: alice and carol both store 7c6a180b (same hash), bob stores 0d107d09; equal hashes let a rainbow lookup crack all. Right, bcrypt with per-user salt: alice and carol store different $2b$12$ hashes even though both chose password1, so the lookup fails.
A per-user salt makes equal passwords hash differently; a cost factor makes each guess slow.

Cipher mode: ECB vs GCM

AES is a strong cipher, but the mode that chains its blocks decides whether patterns survive. ECB encrypts each 16-byte block independently with the same key, so identical plaintext blocks become identical ciphertext blocks (Demo 3, vulnerable): here blocks B1, B2 and B4 are equal, and the ciphertext repeats AB12 in the same places. mallory never decrypts anything, yet the layout of the data leaks — the famous "ECB penguin" (CWE-327).

An authenticated mode such as AES-GCM mixes a fresh random nonce and the block position into the keystream, so identical plaintext blocks encrypt to different ciphertext and no structure shows. GCM also produces an authentication tag that detects tampering, which ECB and CBC do not.

Plaintext blocks LOGO, LOGO, name, LOGO where B1=B2=B4. AES-ECB encrypts them to AB12, AB12, 77CD, AB12 so identical blocks give identical ciphertext (CWE-327). AES-GCM with a nonce gives 3F0A, 9C21, D4E7, 6B18 which all differ, so no pattern leaks.
ECB repeats ciphertext wherever plaintext repeats; GCM with a random nonce does not, and it authenticates.

Keys and secrets

A cipher is only as safe as its key. A key hard-coded in crypto.js and committed to git lives in every clone and every past commit, so anyone who reads the source reads the key and can decrypt and forge everything (Demo 4, vulnerable; CWE-321). Rotating it means editing and redeploying code. The fix is to keep keys out of source entirely: load them at runtime from a secrets manager / KMS / environment, grant them only to the running service, and rotate them centrally. The repository then holds a reference such as kms.get("APP_KEY"), never a value.

Randomness: use a CSPRNG

Password-reset links, session ids and API keys are only safe if they cannot be guessed. A token built from the current timestamp — md5(now) — has just a handful of likely values, so mallory, who knows roughly when the request was made, can reproduce it in a few tries (Demo 5, vulnerable; CWE-330). Secrets must come from a cryptographically secure random generator (CSPRNG): 32 bytes (256 bits) of randomness, single-use, with a short expiry, is unguessable even to someone who knows the exact time.

How to prevent cryptographic failures

  • Classify data; apply controls to everything sensitive, and don't store what you don't need.
  • Encrypt in transit: TLS everywhere, HSTS, no mixed content, modern cipher suites.
  • Store passwords with bcrypt / scrypt / argon2id — per-user salt and a tuned cost. Never MD5, SHA-1 or plain SHA-256 for passwords.
  • Encrypt data at rest with an authenticated mode (AES-GCM, ChaCha20-Poly1305); never ECB; use a fresh nonce per message.
  • Keep keys in a KMS / secrets manager, out of source control; rotate them; scan repos and history for leaked secrets.
  • Generate all tokens and keys from a CSPRNG; never from timestamps, counters or rand().
  • Prefer vetted libraries and defaults over hand-rolled crypto.

OWASP Top 10:2025 series

This page is part of a ten-page series, one per category:

See also How an HTTPS Connection Is Established for the TLS handshake behind the fixed transport, How a Digital Signature Works for hashing and keys, and How Browser Cookies Work and How OAuth 2.0 Works for session tokens.

See Password Hashing: Fast vs Slow Hashes for guesses per second per algorithm.

What the page leaves out

Real hashes and ciphertexts are far longer and the values here are invented for clarity; the page does not compute any actual cryptography. It skips the TLS handshake and certificate checks (see the HTTPS page), key derivation and wrapping, pepper and HMAC, nonce-reuse failures in GCM, timing-safe comparison, certificate and key rotation details, and post-quantum concerns. The five scenarios are independent snapshots, not one running system.