OWASP A02:2021 Cryptographic Failures: Detection and Prevention
TL;DR
A02:2021 Cryptographic Failures is the OWASP Top 10 category for broken or missing crypto: weak hashes, deprecated ciphers, hardcoded keys, missing TLS, predictable randomness, and unverified certificates. It was renamed from A03:2017 "Sensitive Data Exposure" because the old name described the symptom (data leaking) rather than the defect (crypto implemented incorrectly). Most cryptographic failures are visible in source code and detectable by SAST — hardcoded keys, MD5/SHA-1 password hashing, ECB mode, Math.random() for security tokens — while a smaller set (TLS misconfiguration on a deployed load balancer, certificate validation at runtime) require DAST or platform configuration scanning. Prevention is well understood: vetted libraries, modern algorithms (Argon2id for passwords, AES-GCM for symmetric, TLS 1.2+ for transport), and a secrets manager instead of hardcoded keys.
When OWASP published the 2021 edition of its Top 10, the category at the number-two spot was renamed. What had been A03:2017 Sensitive Data Exposure became A02:2021 Cryptographic Failures. The shift looks cosmetic, but it is not. "Sensitive data exposure" describes what the user sees after a breach: passwords on a paste site, credit card numbers on a dark-web forum, PII in a dump. "Cryptographic failures" describes what is actually wrong in the code: a hash function chosen in 2008 that should have been retired in 2012, an AES key checked into git five years ago, a TLS handshake that still negotiates RC4. The rename moves the category from an outcome to a cause, which is where defenders need to operate.
The change also reflects a decade of breach forensics. In incident after incident, the root finding was not that data was sensitive — almost all data is sensitive — but that the crypto protecting it had a defect that a static analyzer or library scanner could have flagged at commit time. This guide walks through what A02 covers, the patterns that drive most findings, the CWE mappings underneath, where SAST and DAST each contribute to detection, and how prevention should be structured. It closes with where GraphNode SAST fits in the layered defense.
What Cryptographic Failures Cover
A02:2021 is a wide category. OWASP's scoping pulls in any defect in the cryptographic controls that protect data either in transit or at rest, plus the supporting machinery — random number generation, key storage, certificate validation, protocol negotiation. The practical surface breaks into a handful of recurring failure modes.
Weak or broken algorithms. Use of cryptographic primitives that are no longer considered secure: MD5 and SHA-1 for any integrity-sensitive use, DES and 3DES for symmetric encryption, RC4 in TLS, RSA with key sizes below 2048 bits, ECB mode AES (which leaks block-level patterns and produces the famous "encrypted Tux" image when applied to bitmaps).
Missing encryption. Sensitive data transmitted over plain HTTP, stored in plaintext in a database column, or written to a log file unencrypted. Includes legacy protocols like FTP and Telnet still in production.
Hardcoded keys, passwords, and secrets. Encryption keys checked into source control, API keys embedded in compiled binaries, database credentials in configuration files committed to git history, hardcoded initialization vectors (IVs) and salts.
Weak password storage. Passwords hashed with a fast general-purpose hash (MD5, SHA-1, even SHA-256 by itself) instead of a memory-hard password hashing function (Argon2id, scrypt, bcrypt), or stored without a per-user salt.
Insufficient randomness. Use of Math.random(), java.util.Random, or rand() for security-relevant values: session tokens, password reset codes, CSRF tokens, encryption keys. These are pseudo-random number generators, not cryptographic ones, and an attacker with a few outputs can predict the rest.
Certificate and protocol validation failures. TLS clients that accept self-signed certificates in production, hostname verification disabled, expired certificates accepted, deprecated protocol versions (SSL 3.0, TLS 1.0/1.1) left enabled.
Common Patterns
A handful of code-level patterns account for the majority of A02 findings in real codebases. Recognizing them at review time — or having a static analyzer flag them automatically — closes most of the surface.
Hardcoded encryption keys. A literal AES key embedded in source: byte[] key = "ThisIsMySecretKey".getBytes(). Anyone with read access to the repository — including past employees, contractors, and anyone who clones a leaked git history — can decrypt every record the application has ever encrypted with that key. Rotation is impossible without re-encrypting the entire dataset.
MD5 or SHA-1 for password hashing. A backend that calls MessageDigest.getInstance("MD5") against a user password produces a hash that a modern GPU can crack at billions of guesses per second. A breach of the user table effectively becomes a breach of every reused password.
ECB mode AES. The default mode in many older crypto libraries is ECB (Electronic Code Book), which encrypts each block independently. Identical plaintext blocks produce identical ciphertext blocks, so structural patterns leak through. Use of ECB for anything beyond a single random block is almost always a defect.
DES and 3DES still in use. DES has been considered broken since the late 1990s; 3DES was deprecated by NIST in 2017 and disallowed for most uses by 2023. Code that still imports DESKeySpec or TripleDES is a prime A02 finding.
Missing TLS or weak TLS configuration. An HTTP client that calls http:// rather than https:// for a sensitive endpoint, or a server configured to allow TLS 1.0 with anonymous Diffie-Hellman ciphers because "older clients still need it." The clients are usually attackers.
Self-signed certificates accepted in production. Code that installs an all-trusting X509TrustManager or sets verify=False on a Python requests call. Common in development, frequently committed to production by accident.
JWT with the none algorithm. A JWT library configured to accept the none algorithm allows attackers to forge tokens without any signature at all. The vulnerability is straightforward: change the algorithm field in the header to none, drop the signature, and the server accepts the token.
Custom-rolled crypto. Any function named encrypt(), obfuscate(), or scramble() written in-house and not derived from a vetted standard. The first rule of crypto engineering remains: do not invent your own.
Real-World Incidents
Equifax, 2017. The Equifax breach exposed the personal records of approximately 147 million U.S. consumers. The primary entry point was an unpatched Apache Struts vulnerability (CVE-2017-5638), but contributing factors documented in the post-incident reporting included internal use of outdated TLS configurations and certificates that prevented network monitoring tools from inspecting attacker traffic for an extended period. The crypto failures were not the initial breach but they materially extended the dwell time.
Heartbleed, 2014 (CVE-2014-0160). A bounds-checking flaw in OpenSSL's TLS heartbeat extension allowed remote attackers to read up to 64 KB of server memory per request, frequently exposing private keys, session tokens, and user credentials. Heartbleed sits in A02 territory because the cryptographic library — the trusted base of TLS for a large fraction of the internet — leaked the very secrets it was supposed to protect.
Adobe, 2013. A breach of Adobe's customer database exposed credentials for roughly 38 million active users. The passwords had been encrypted (rather than properly hashed) using 3DES in ECB mode with a single shared key. Identical passwords produced identical ciphertexts across the dataset, and the included password hints made bulk recovery straightforward. The incident is the canonical example of why password storage is a hashing problem, not an encryption problem, and why ECB is unfit for purpose.
Yahoo, 2013. Disclosure in 2016 revealed that a 2013 breach had compromised credentials for all three billion Yahoo accounts. A portion of the affected user records used MD5 for password hashing, allowing offline cracking of the exposed hashes at a rate measured in billions per second on commodity hardware. MD5 had been considered cryptographically broken for years before the breach.
Other classes of well-documented A02-relevant attacks include the POODLE attack on SSL 3.0 (CVE-2014-3566), the BEAST attack on TLS 1.0, and the long line of MD5 and SHA-1 collision results that progressively retired both for any integrity-sensitive use. Each is a reminder that cryptographic primitives have lifecycles, and code that compiled cleanly five years ago can be a critical finding today.
Relevant CWE Mappings
A02:2021 aggregates a long list of CWE entries — OWASP maps roughly 30 individual weaknesses into the category. Six or seven of those account for the bulk of real-world findings, and they are the ones most worth knowing when triaging a SAST report.
| CWE | Name | Typical Finding |
|---|---|---|
| CWE-327 | Use of a Broken or Risky Cryptographic Algorithm | DES, 3DES, RC4, MD5, SHA-1 in security context |
| CWE-326 | Inadequate Encryption Strength | RSA below 2048 bits, AES-128 where AES-256 is required |
| CWE-321 | Use of Hard-coded Cryptographic Key | AES key embedded in source or config |
| CWE-319 | Cleartext Transmission of Sensitive Information | Credentials sent over HTTP rather than HTTPS |
| CWE-330 | Use of Insufficiently Random Values | Math.random() for session tokens |
| CWE-328 | Use of Weak Hash | MD5 or SHA-1 for password storage |
| CWE-916 | Use of Password Hash With Insufficient Computational Effort | SHA-256 for passwords without Argon2/bcrypt/scrypt |
Adjacent CWEs that frequently surface in A02 reports include CWE-259 (Hard-coded Password), CWE-296 (Improper Following of Certificate Chain), CWE-325 (Missing Cryptographic Step), CWE-347 (Improper Verification of Cryptographic Signature), and CWE-798 (Use of Hard-coded Credentials). Most are pattern-detectable in source.
Detection: Where SAST and DAST Each Help
Most A02 findings live in source code, which makes SAST the primary detection layer. Static analyzers can flag every relevant pattern at the moment a developer commits the change: a constructor call to MessageDigest.getInstance("MD5"), an import of javax.crypto.spec.DESKeySpec, a string literal that looks like a base64-encoded key, an instantiation of Cipher.getInstance("AES/ECB/PKCS5Padding"), a call to Math.random() in a function whose name contains "token" or "session." Pattern detection covers a large share of A02; data flow analysis covers the rest, by tracing a sensitive value (a password, a credit card number, a secret) from its source through method boundaries to a sink (an unencrypted file write, a log call, a plaintext HTTP request).
GraphNode SAST applies both: pattern rules for the broken-primitive cases and interprocedural data flow for the plaintext-leak cases. Hardcoded credentials are flagged via secret scanning heuristics that run alongside the SAST pass, catching API keys, tokens, and embedded passwords with regex and entropy signatures before they ship.
SAST has bounded coverage on the operational side of A02. Static analysis cannot see the TLS configuration of a load balancer running in production. It cannot tell you that the certificate on the API gateway is about to expire, or that the cipher suite list still includes RC4 because nobody updated the nginx config after the last upgrade. It cannot detect side-channel issues in a hardware security module. Those are DAST and platform-scanning territory: a TLS configuration scanner like Qualys SSL Labs or testssl.sh run against the deployed endpoint, certificate-monitoring tooling, and dedicated cryptographic policy scanners.
In practice the layering is: SAST in CI catches hardcoded keys, weak algorithms, and missing TLS in source; secret scanning catches credentials committed to git; TLS scanning against staging and production catches the runtime configuration; certificate-lifecycle tooling catches expirations and chain validation. No single layer covers all of A02, and most mature programs run all four.
Prevention
Use vetted cryptographic libraries. The default position is to never call low-level crypto primitives directly. Use libsodium, BoringSSL, the platform's standard cryptographic API (Java JCA, .NET System.Security.Cryptography, Python cryptography), or a high-level wrapper like Tink. The libraries enforce safe defaults — authenticated encryption modes, sufficient key sizes, proper IV generation — that hand-rolled code typically gets wrong.
Pick the right primitive for the job. For symmetric encryption, use AES-GCM or ChaCha20-Poly1305 — both are authenticated encryption modes that protect integrity as well as confidentiality. Avoid AES-CBC unless paired with a separate MAC, and avoid AES-ECB entirely. For password hashing, use Argon2id (the current OWASP recommendation), scrypt, or bcrypt; never use a general-purpose hash (SHA-256, BLAKE2) for passwords because they are designed to be fast and password hashing must be deliberately slow. For asymmetric encryption and signatures, use Ed25519 or RSA at 3072 bits or higher; for key exchange, use X25519 or ECDHE.
Use a cryptographically secure random source. In Java, SecureRandom rather than Random. In Python, the secrets module rather than random. In JavaScript, crypto.getRandomValues() or crypto.randomUUID() rather than Math.random(). The cost difference is negligible; the security difference is the entire bug class.
Enforce TLS 1.2 or 1.3 with modern cipher suites. Disable SSL 3.0, TLS 1.0, and TLS 1.1 at the load balancer and reverse proxy. Disable RC4, 3DES, and export ciphers. Use Mozilla's TLS configuration generator as a baseline. Enable HSTS to prevent downgrade attacks. Consider certificate pinning for high-value mobile clients, with a pin-rotation strategy that does not brick the app on rotation.
Manage keys outside source code. Use a secrets manager — HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager — or a hardware security module for high-value keys. Inject secrets at runtime via environment variables or a sidecar; never commit them to source control. Rotate keys on a defined schedule, and rotate immediately if exposure is suspected. For database-stored encryption keys, use envelope encryption: a per-record data key encrypted by a master key held in the secrets manager.
Validate certificates correctly. Never disable hostname verification or accept self-signed certificates outside development. If the application needs to trust a private CA, install that CA's root certificate explicitly; do not trust everything to work around the validation error. Monitor certificate expirations.
Where GraphNode SAST Fits
GraphNode SAST covers the source-code surface of A02 across 13+ languages including C#, Java, JavaScript, Python, PHP, Swift, Kotlin, Objective-C, C/C++, and VB.NET. The relevant rule families inside the 780+ rule set break into three groups.
Pattern detection catches the broken-primitive cases directly: calls to MD5, SHA-1, DES, 3DES, RC4 in security-relevant contexts; AES instantiations with ECB mode; Math.random() and equivalent non-CSPRNG calls feeding security-critical sinks; instantiations of all-trusting TrustManager implementations; JWT libraries configured to allow the none algorithm.
Taint and entropy detection catches hardcoded keys, passwords, and tokens (CWE-321 and CWE-798): high-entropy string literals in source, configuration files committed to the repository, base64-encoded blobs that decode to plausible key material. Findings link out to secret scanning for the broader credential-discovery workflow that runs alongside SAST.
Data flow tracking catches the plaintext-leak cases: a password, credit card number, or other sensitive value flowing from a source through method boundaries to a sink that does not encrypt it — a plaintext HTTP request, a log statement, an unencrypted file write. This is the same interprocedural taint propagation engine that powers the injection-class detection, applied to confidentiality sinks instead of injection sinks.
Frequently Asked Questions
What is the difference between A02:2021 Cryptographic Failures and A03:2017 Sensitive Data Exposure?
They cover the same underlying problems but framed differently. A03:2017 was named after the symptom: data leaking. A02:2021 is named after the cause: cryptographic controls that were missing, weak, or misused. OWASP renamed it in 2021 to push defenders toward fixing the broken crypto rather than only detecting the resulting exposure. The CWE mappings are largely the same, but the framing change reorients tooling and training: a code review now asks "is the crypto correct" rather than "is sensitive data showing up where it should not."
Is MD5 still safe for anything?
Not for any security-relevant use. MD5 has been considered cryptographically broken since the mid-2000s, with practical collision attacks demonstrated repeatedly. Do not use MD5 for password hashing, integrity verification of security-critical data, digital signatures, or anywhere collision resistance matters. The only acceptable remaining uses are non-security ones: as a fingerprint for caching, file deduplication, or non-adversarial checksumming where an attacker cannot influence the input. If a security control depends on MD5, treat it as a finding.
Should I use AES-CBC or AES-GCM?
AES-GCM. AES-GCM is an authenticated encryption mode that protects both confidentiality and integrity in a single primitive, and it is the default recommendation in modern crypto guidance. AES-CBC is unauthenticated; using it correctly requires pairing it with a separate MAC (encrypt-then-MAC), and most hand-rolled implementations get the pairing wrong, opening padding-oracle attacks. If you are starting fresh, use AES-GCM or ChaCha20-Poly1305. Use AES-CBC only in legacy contexts where you cannot change the protocol.
What is the right way to hash passwords?
Use a memory-hard password hashing function with a per-user salt. The current OWASP recommendation is Argon2id with parameters tuned to roughly 1 second of compute on the target hardware. Acceptable alternatives are scrypt and bcrypt. Do not use general-purpose hash functions like SHA-256 or BLAKE2 directly for passwords; they are designed to be fast, and password hashing needs to be deliberately slow to defeat offline cracking. The salt should be cryptographically random and stored alongside the hash.
Can SAST detect cryptographic failures?
Yes for the source-code surface, no for the deployment surface. SAST can flag use of weak algorithms, hardcoded keys, missing TLS in code, weak password hashing, ECB mode, insufficient randomness, JWT misuse, and trust-everything certificate handling. Most A02 findings in real codebases sit in this category and are SAST-detectable at commit time. SAST cannot detect TLS misconfiguration on a deployed load balancer, expired certificates in production, or cipher suite drift after an infrastructure change — those require DAST, TLS scanning tools, and certificate-monitoring infrastructure. A complete A02 program layers SAST in CI with TLS and certificate scanning in staging and production.