A04 Cryptographic Failures
Encryption is rarely broken; it is skipped, misconfigured or misused. Plain-text transport, fast password hashes and hard-coded keys are how data actually leaks.
What it is
A cryptographic failure is anything that exposes sensitive data because cryptography was missing, weak, or used incorrectly: data sent without TLS, passwords stored with a fast hash, secrets in source, predictable "random" tokens, home-made algorithms, or keys that never rotate.
It is A04 in :2025. The first question is always: which data is sensitive, where does it travel and rest, and who can read it there?
How the attack works
- Network sniffing or a downgrade attack reads traffic that is not protected by modern TLS.
- A database leak yields passwords hashed with MD5 or SHA-1 (or unsalted); a GPU cracks most of them in hours.
- Tokens built from a timestamp or a non-cryptographic random generator are predicted and forged.
- A key committed to a repository, or one shared by every customer, decrypts everything at once.
- ECB mode, reused nonces or encryption without authentication let an attacker spot patterns or modify ciphertext.
Vulnerable code
A fast, unsalted hash for passwords and a predictable generator for a reset token.
import hashlib, random
def store_password(pw):
return hashlib.md5(pw.encode()).hexdigest() # fast and unsalted
def reset_token():
return str(random.randint(100000, 999999)) # predictableconst crypto = require("crypto");
const storePassword = (pw) => crypto.createHash("sha1").update(pw).digest("hex");
const resetToken = () => Math.random().toString(36).slice(2);String hash = DigestUtils.md5Hex(password);
String token = String.valueOf(new Random().nextInt(999999));The fix
Use a slow, salted, purpose-built password hash (, scrypt or bcrypt) through a maintained library, and take tokens from the operating system's cryptographic random source. Do not invent algorithms; do not manage raw keys by hand if a library or a can do it.
import secrets
from argon2 import PasswordHasher
ph = PasswordHasher()
def store_password(pw):
return ph.hash(pw) # salt and parameters are included
def verify_password(stored, pw):
try:
return ph.verify(stored, pw)
except Exception:
return False
def reset_token():
return secrets.token_urlsafe(32) # 256 bits from the OS CSPRNGconst crypto = require("crypto");
const argon2 = require("argon2");
const storePassword = (pw) => argon2.hash(pw, { type: argon2.argon2id });
const verifyPassword = (stored, pw) => argon2.verify(stored, pw);
const resetToken = () => crypto.randomBytes(32).toString("base64url");PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(password);
boolean ok = encoder.matches(password, hash);
byte[] raw = new byte[32];
new SecureRandom().nextBytes(raw);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(raw);func ResetToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil { // crypto/rand
return "", err
}
return base64.RawURLEncoding.EncodeToString(b), nil
}Prove the fix
Test properties, not algorithms: two hashes of the same password differ (salted), the stored value is not a bare fast hash, tokens are long and unique, and the transport enforces modern TLS.
def test_hashes_are_salted_and_slow_format():
a, b = store_password("correct horse"), store_password("correct horse")
assert a != b # random salt per hash
assert a.startswith("$argon2id$") # not md5/sha1 hex
assert verify_password(a, "correct horse")
assert not verify_password(a, "wrong")
def test_reset_tokens_are_long_and_unique():
tokens = {reset_token() for _ in range(1000)}
assert len(tokens) == 1000
assert all(len(t) >= 43 for t in tokens) # 32 bytes, base64url# fail if the server still accepts legacy protocols
testssl.sh --protocols --quiet https://staging.example.com | tee tls.txt
! grep -E 'TLS 1(\.0|\.1)? .*offered' tls.txtChecklist
- Classify data: know what is sensitive before choosing controls.
- TLS everywhere, modern versions only, HSTS on; no sensitive data in URLs.
- Passwords: Argon2id, scrypt or bcrypt with a maintained library; never a fast hash.
- Randomness for security comes from the OS , never from Math.random or random.
- Use (AES-GCM, ChaCha20-Poly1305); never reuse a nonce with the same key.
- Keys live in a KMS or secret manager, are rotated, and are never in the repository.
- Do not store what you do not need; delete it when you are done.
connections on the map
- A04 Cryptographic Failuresprotects credentials ofA07 Authentication Failures
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- KMS & crypto - keys and envelopescloudsecmanaged keys and encryption, with no homegrown code
Study before
Unlocks
test what you learned
Loading questions…