Common Misuses
Crypto misuse is the gap between "we use AES" and "we use AES correctly". The library is almost never the bug; the usage is. This lesson walks through the misuse patterns that have shipped in production at companies you have heard of.
Eight cryptographic misuse patterns and their fixes
EXAMPLE
# 1) ECB mode reveals patterns in the plaintext (the 'penguin image')
# BUG: encrypted bitmap shows the outline of the original
# FIX: never use ECB. Use AES-GCM or ChaCha20-Poly1305 for everything.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(256)
nonce = __import__('secrets').token_bytes(12) # 12 bytes for GCM
ct = AESGCM(key).encrypt(nonce, b'sensitive', associated_data=b'context-v1')
# 2) Nonce reuse with AES-GCM catastrophically destroys authentication
# BUG: storing a counter that gets reset on deploy, reusing nonces across processes
# FIX: 96-bit random nonce per message AND track them on the server,
# OR derive a unique nonce from a key-id+counter you persist atomically.
# 3) Hashing passwords with SHA-256 (or any general-purpose hash)
# BUG: GPUs hash billions of SHA-256 candidates per second
# FIX: argon2id (preferred), or bcrypt with cost >= 12
import argon2
hasher = argon2.PasswordHasher(time_cost=3, memory_cost=64*1024, parallelism=4)
hash = hasher.hash('hunter2')
hasher.verify(hash, 'hunter2')
# 4) Comparing secrets with == (timing leak)
# BUG: webhook signature compared with == — attacker measures latency to learn bytes
# FIX: hmac.compare_digest (Python), crypto.timingSafeEqual (Node), subtle (Go)
import hmac
ok = hmac.compare_digest(expected_mac, supplied_mac)
# 5) Skipping certificate validation in TLS calls
# BUG: verify=False / insecureSkipVerify: true to 'make it work'
# FIX: ship the right CA bundle; pin if the target is critical (mobile clients).
import requests
requests.get('https://api.example.com', verify=True) # DEFAULT — keep it
# 6) Encrypting then NOT authenticating
# BUG: AES-CBC with no HMAC — attacker flips bits to corrupt or oracle-decrypt
# FIX: always use an AEAD (AES-GCM, ChaCha20-Poly1305). Encrypt-then-MAC ONLY
# if a constraint forces it and you write the construction carefully.
# 7) JWT 'alg: none' or HS256-vs-RS256 confusion
# BUG: server accepts whatever 'alg' the token claims; attacker swaps in alg:none
# FIX: HARDCODE the expected algorithm in the verifier; never trust the header.
import jwt
claims = jwt.decode(token, key=public_key, algorithms=['RS256'], audience='shop-api')
# 8) Reusing the same key for too many things ('one key to rule them all')
# BUG: same master key encrypts AND signs AND derives session keys
# FIX: derive purpose-specific keys with HKDF: HKDF(master, info='session-encryption-v1')
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
enc_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None,
info=b'session-encryption-v1').derive(master)
mac_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None,
info=b'session-mac-v1').derive(master)
# 9) Rolling your own crypto
# BUG: 'we wrote our own AES because the library was slow'
# FIX: do not. Reach for libsodium/Bouncy Castle/BoringSSL/cryptography. Their
# primitives are constant-time, peer-reviewed, and faster than yours.
Why it matters
A library you trust + an API you do not understand is not safe. The cheapest fix is to pick the highest-level primitive available — AEAD over raw block cipher, password hasher over hash function, HKDF over manual key derivation — because the high-level API hides the misuse-prone knobs.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Common bugs: // - IV / nonce reuse with the same key. // - Using a hash for passwords. // - JWT with alg=none accepted. // - HKDF info parameter omitted, mixing key purposes. // - Comparing MACs with ==.Try it Yourself »
Discussion
Loading…