Web Security (OWASP Top 10:2025) Module 5 – A04:2025 Cryptographic Failures
Spotting cryptographic failures in web code
What to look for when code uses cryptography: weak randomness, encryption that misses changes, loose MAC and signature checks, and data kept without need.
What you will learn
- Recognise weak algorithms, modes and key handling in a code review
- Use secure randomness for tokens and identifiers
- Use authenticated encryption through a high-level library
- Verify signatures and MACs with a pinned algorithm and a constant-time comparison
- Decide what data to encrypt, hash, tokenise or not store at all
Before you start
What you’ll learn · Before you start
What you will learn
- Recognise weak algorithms, modes and key handling in a code review
- Use secure randomness for tokens and identifiers
- Use authenticated encryption through a high-level library
- Verify signatures and MACs with a pinned algorithm and a constant-time comparison
- Decide what data to encrypt, hash, tokenise or not store at all
Before you start
On this page
A cryptographic failure rarely looks like a broken cipher. It looks like working code: Math.random() making a reset
token, MD5 in a password check, a fixed value for the Initialisation vector: a value that a cipher mode uses together with the key, so that encrypting the same data twice gives different results. It need not be secret, and it must be new for each message., an == between two message authentication code (MAC): A short tag computed from a message and a secret key. Only a holder of the key can make a tag that matches, so a match shows that the message is unchanged and came from a key holder. HMAC-SHA-256 is a common MAC.. OWASP’s A04:2025 collects 32
weaknesses of this kind, and a reviewer finds them by looking at five places: where random values come from, how data is
encrypted, how a value is checked, how long keys and tokens live, and whether the data should exist at all.
This lesson gives you those five questions, the safe building block that answers each, and a reading of the lines that look fine and are not. The first section names the five places and runs one failure that passes every test.
Five questions to ask of every line that uses cryptography
A token in a reset link, an encrypted column, a signed cookie and a stored password all come down to the same questions. Ask them in this order of every pull request that touches one of these, before you read a line of the algorithm:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Where does it come from?. A value that must not be guessed, such as a token, a key or a nonce, has to come from the operating system's source of unpredictable bytes. A generator built for simulations is a different tool.
- How is it hidden?. Encryption hides data. Authenticated encryption also notices when the data was changed, and a reviewer wants both properties from any code that encrypts.
- How is it checked?. A MAC or a signature shows that a value was made by someone with the right key. The check has to cover the whole value, use an algorithm that you chose, and give nothing away while it runs.
- How long does it live?. Tokens should expire, keys should be replaceable, and neither belongs in source code. A value with no end date is a value that stays dangerous after it leaks.
- Should it exist at all?. The safest value is one that was never stored. Before any algorithm is chosen, ask whether the data is needed, or whether a stand-in would do.
Each question has a building block that answers it:
- Randomness. A CSPRNG: A cryptographically secure pseudo-random number generator: a source of random bytes, fed by the operating system, from which nobody can predict the next output. Tokens, keys and nonces must come from one., a cryptographically secure pseudo-random number generator, is the operating system’s source of unpredictable bytes, and tokens, keys and cipher nonce: A number used once with an encryption key. A cipher mode takes a fresh one with each message, so that equal messages do not give equal results. It need not be secret, and it must not repeat under the same key. must come from it.
- Encryption. authenticated encryption: Encryption that also produces an authentication tag, so that decryption refuses data that was changed after it was encrypted. AES-GCM is the usual example., such as AES-GCM used through a high-level library, hides data and also fails loudly when any part of it was changed.
- Checking. A MAC is a keyed fingerprint that only holders of the key can make. A signature does the same with a private key, and a constant-time comparison: A comparison whose running time does not depend on how much of the two values matches, so a timer learns nothing about where they first differ. It is the right way to compare MACs, signatures and secret tokens. checks either one without giving anything away.
- Lifetime. Tokens expire and keys are rotated, so a value that leaks stops being useful.
- Data. tokenisation: Replacing a sensitive value, such as a card number, with a random token and keeping the real value in a separate vault with its own access control. The token reveals nothing on its own. More in “Protecting sensitive data: classify, minimise, mask” replaces a value such as a card number with a random stand-in that a provider keeps, and the safest data is data that was never stored. For a new column, this is the question to ask first.
One failure passes every test: a reset token made with Python’s random module, which is built for simulations, and
seeded with the clock. The program prints what such a token is worth next to one from secrets, which draws on the
operating system’s source:
# One failure that passes every test: a reset token made with Python's random module, which is built for simulations,
# and seeded with the clock. The token looks like 16 random characters. Anyone who knows roughly when it was made has
# at most one day of seconds to try. secrets draws from the operating system's source of unpredictable bytes instead.
import math
import random
import secrets
ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"
def weak_token(seed):
rng = random.Random(seed)
return "".join(rng.choice(ALPHABET) for _ in range(16))
looks = 16 * math.log2(len(ALPHABET))
print(f"a 16-character token over {len(ALPHABET)} symbols looks like {looks:.1f} bits")
print("the same seed gives the same token:", weak_token(1_760_000_000) == weak_token(1_760_000_000))
print("the next second gives a different one:", weak_token(1_760_000_000) != weak_token(1_760_000_001))
seconds_in_a_day = 24 * 60 * 60
print(f"seeded with the second it was made, one day holds {seconds_in_a_day:,} tokens: {math.log2(seconds_in_a_day):.1f} bits")
token = secrets.token_urlsafe(32)
print(f"secrets.token_urlsafe(32): {len(token)} characters from 32 random bytes, 256 bits")
print("two of them are equal:", secrets.token_urlsafe(32) == secrets.token_urlsafe(32)) Output
a 16-character token over 36 symbols looks like 82.7 bits the same seed gives the same token: True the next second gives a different one: True seeded with the second it was made, one day holds 86,400 tokens: 16.4 bits secrets.token_urlsafe(32): 43 characters from 32 random bytes, 256 bits two of them are equal: False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 seeded_token.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The weak token looks like 82.7 bits of secret. Seeded with the second it was made, it has 86,400 possible values a day,
16.4 bits, and the same seed gives the same token every time. The one from secrets has 256 bits, and two draws from it
differ. No test that asks whether a token looks random can tell the two apart, so the review has to happen in the code.
Checkpoint
Check: The five places a review looks
Flashcards · 4 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What five places does a reviewer check in every line that uses cryptography?
Where random values come from; how data is encrypted; how a value is checked; the lifetime of keys and tokens; and whether you need the data at all. A04:2025 collects 32 weaknesses of this kind, and they look like working code: Math.random() making a reset token, MD5 in a password check, a fixed IV, an == between two MACs.
-
Which safe building block answers each of the five questions?
Randomness: a CSPRNG, which draws unpredictable bytes from the operating system. Encryption: authenticated encryption, such as AES-GCM used through a high-level library, which hides data and also fails loudly when any part was changed. Checking: a MAC or signature compared in constant time. Lifetime: expiring tokens and rotated keys. Data: tokenisation, and above all not storing the data.
-
Why is a reset token made with Python's random module, seeded with the clock, weak?
random is built for simulations. Seeded with the second it was made, such a token has only 86,400 possible values a day, about 16.4 bits, although it looks like 82.7 bits, and the same seed gives the same token every time. A token from secrets draws on the operating system and has 256 bits. No test that asks whether a token looks random can tell them apart.
-
Which of the five questions do you ask first about a new column of sensitive data?
Whether the data should exist at all. Tokenisation replaces a value such as a card number with a random stand-in that a provider keeps, and the safest data is data that was never stored.
Twelve lines, ten smells: what a scanner flags and what only a reviewer finds Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Randomness: how many guesses a token costs, by how it was made
- Encryption that notices change, and encryption that does not
- Verifying: whole MACs, constant-time comparison and an algorithm you chose
- Choosing the primitive by the job, and keeping less to protect
- Practise: six pieces of security code to fix
- Fix six small pieces of security code
- Interview questions
- A teammate built passwordless sign-in. The emailed link carries a token made with Math.random().toString(36).slice(2). The token is stored as it is, never expires, and works any number of times. Review it: what is wrong, and what do you change?
- Compliance asks you to encrypt the phone-number column of a table of 20 million users. The app also looks users up by phone number, and one report needs the last four digits. How do you design it, and where does the key live?
- A partner's webhooks carry a signature header: eight hex characters of an MD5 over the body and a shared key. Our handler compares it with ==, after parsing the body, and the key is three years old. The partner cannot change quickly. What do you ask for, and what do you do meanwhile?
- Key takeaways
5 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 47 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Terms in this lesson
- authenticated encryption
- Encryption that also produces an authentication tag, so that decryption refuses data that was changed after it was encrypted. AES-GCM is the usual example.
- cipher nonce
- A number used once with an encryption key. A cipher mode takes a fresh one with each message, so that equal messages do not give equal results. It need not be secret, and it must not repeat under the same key.
- constant-time comparison
- A comparison whose running time does not depend on how much of the two values matches, so a timer learns nothing about where they first differ. It is the right way to compare MACs, signatures and secret tokens.
- CSPRNG
- A cryptographically secure pseudo-random number generator: a source of random bytes, fed by the operating system, from which nobody can predict the next output. Tokens, keys and nonces must come from one.
- message authentication code (MAC)
- A short tag computed from a message and a secret key. Only a holder of the key can make a tag that matches, so a match shows that the message is unchanged and came from a key holder. HMAC-SHA-256 is a common MAC.
References
- A04:2025 Cryptographic Failures (OWASP Foundation)
- Cryptographic Storage Cheat Sheet (OWASP Foundation)
- Key Management Cheat Sheet (OWASP Foundation)
- ASVS 5.0.0, V11 Cryptography (OWASP Foundation)
- NIST SP 800-38D: Galois/Counter Mode (GCM) and GMAC (NIST)
- RFC 2104: HMAC: Keyed-Hashing for Message Authentication (IETF (RFC Editor))
- RFC 8725: JSON Web Token Best Current Practices (IETF (RFC Editor))
- RFC 9562: Universally Unique IDentifiers (UUIDs) (IETF (RFC Editor))
- crypto: Node.js v24 documentation (OpenJS Foundation (Node.js))
- secrets: generate secure random numbers for managing secrets (Python Software Foundation)
- hmac: keyed-hashing for message authentication (Python Software Foundation)
- CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) (MITRE)
- CWE-327: Use of a Broken or Risky Cryptographic Algorithm (MITRE)
- Technical Guidelines on SBOM, QBOM and CBOM, AIBOM and HBOM (version 2.0) (CERT-In (Ministry of Electronics and Information Technology, Government of India))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress