Juicy Shop

← All lessons

Cryptographic Failures

CryptographyA02:2021 – Cryptographic Failuresintermediate12 min

Sensitive data leaks when it is stored or transmitted without proper cryptography. Learn why you hash passwords (never encrypt them), how to pick algorithms, and where data-in-transit protection belongs.

Hashing vs encryption

Encryption is reversible — given the key you get the plaintext back. Hashing is one-way — you cannot recover the input from the digest. Passwords must be HASHED, never encrypted: the server never needs the original password back, it only needs to check whether a login attempt produces the same hash. Encrypting passwords is a finding because anyone with the key recovers every password at once.

Why fast hashes are wrong for passwords

MD5, SHA-1 and SHA-256 are designed to be fast, which is exactly what an attacker wants when brute-forcing a stolen hash table. Use a slow, salted, memory-hard password hash: Argon2id (preferred), scrypt, or bcrypt. A per-user random salt defeats rainbow tables, and a high work factor makes each guess expensive. Re-tune the work factor as hardware improves.

Data in transit and at rest

Protect data in transit with TLS everywhere — no mixed content, HSTS enabled, modern cipher suites only. Protect sensitive data at rest with authenticated encryption (AES-GCM or ChaCha20-Poly1305) and keys held in a KMS or secrets manager, never in source code. Classify data first: you cannot protect what you have not identified as sensitive.

Don't roll your own crypto

Cryptographic primitives are easy to misuse: a reused nonce, an ECB-mode cipher, or a non-constant-time comparison can silently destroy the guarantees. Use vetted, high-level libraries (libsodium, the platform WebCrypto API) that expose safe defaults, and let them manage IVs, padding, and authentication tags for you.

Insecure vs secure

✗ insecure
import { createHash } from "node:crypto";

// Fast, unsalted hash — trivial to crack offline
const hash = createHash("sha256")
  .update(password)
  .digest("hex");
await db.insert({ email, password: hash });

SHA-256 is fast and unsalted, so a leaked table can be cracked with rainbow tables or GPU brute force in minutes.

✓ secure
import { hash, verify } from "@node-rs/argon2";

// Slow, salted, memory-hard — Argon2id
const digest = await hash(password);
await db.insert({ email, password: digest });

// On login:
const ok = await verify(digest, attempt);

Argon2id salts automatically and is memory-hard, so each guess is expensive and rainbow tables are useless.

Key takeaways

  • Hash passwords (one-way); never encrypt them.
  • Use Argon2id/scrypt/bcrypt with a per-user salt and a tuned work factor.
  • Fast hashes (MD5/SHA-256) are wrong for passwords.
  • Encrypt sensitive data in transit (TLS) and at rest (AES-GCM) with managed keys.
  • Use vetted libraries instead of hand-rolled crypto.

Knowledge check

Practice

Apply this lesson hands-on in the matching find-it / fix-it challenge.

Open challenge →

Further reading