OWASP Top 10Long read

Cryptographic Failures in Data-at-Rest Implementations

Encryption that looks complete on paper often leaks data through unencrypted edges.

· 10 min read
Cover illustration for “Cryptographic Failures in Data-at-Rest Implementations”
OWASP Top 10 · October 14, 2026 · 10 min read · 2,343 words

Most organizations can say "we encrypt data at rest and in transit" and have every surface-level review confirm it. That sentence is also, in a surprising number of real environments, mostly meaningless. OWASP's A04:2025 Cryptographic Failures category defines the problem precisely: these weaknesses arise because cryptography is implemented or used incorrectly, not because it is missing. A weak algorithm gets chosen. Keys get managed poorly. Protocols get misconfigured. Encryption gets assumed instead of enforced. Attackers rarely sit down and try to crack AES-256. They look for the places where encryption was never actually applied, where it was applied inconsistently, or where it was asked to compensate for a design flaw it was never built to fix. The property that makes this category so dangerous is that none of it breaks the application. Requests still process. Responses still come back valid. An attacker can be reading data a developer genuinely believes is protected, and nothing about the system's behavior will say otherwise. That silent-failure quality is what makes the category easy to miss at audit time: a reviewer sees "AES-256 at rest" on an architecture diagram and stops looking. The rest of this piece is about the three places that confidence breaks down: plaintext that survives in edge cases, encryption that covers one layer but not the next, and key management that looks fine on paper.

Where plaintext survives inside an "encrypted" system

Plaintext rarely disappears from a system in one obvious place. It survives in the corners nobody designed for: backup files, debug logs, caches, and error outputs. Data can be fully encrypted in transport and sitting encrypted in the primary database, and still land in a backup file as plaintext, or get written out to a log during a debugging session, in patterns documented repeatedly under OWASP A04:2025. These paths get missed for a structural reason, not a careless one. Logging and backup code is usually written after the main encryption logic, often by a different engineer, often under time pressure, and often with a mandate to "just get the error captured" rather than to preserve the same protections the primary data path enforces. That is why these paths escape review: nobody designs the encryption model with the exception handler in mind.

A more specific version of the same pattern appears in CWE-261: passwords or secrets that are encoded rather than hashed. Encoding is reversible. It looks like protection on a config screen or in a code comment, but a compromised database, log file, or backup hands an attacker the credentials directly, no cracking required. The LinkedIn password breach is the clearest illustration of what happens when this kind of gap meets scale. Passwords were hashed with SHA-1 and stored without proper salting, and once the database was stolen, those hashes were cracked en masse. The encryption was present. The protection was not. That distinction, present versus protective, is the whole argument of this piece in miniature.

Encryption layers and coverage gaps

Data rarely stays in one place. It moves from a database to an application, through APIs, into queues, caches, and third-party services, and encryption applied correctly at one stop in that journey says nothing about the stops after it. Database-level encryption protects data at rest in the database. It does nothing for the same data once it is decrypted, pulled into the application layer, and processed, especially if application secrets are sitting in plaintext environment variables. An attacker with code execution or environment access reads those secrets directly, bypassing the encrypted storage layer.

APIs deserve particular attention because they form a distinct layer with its own cryptographic requirements, one that teams frequently assume database encryption already covers. APIs routinely handle authentication tokens, session identifiers, personal data, and other secrets in transit and in request payloads. Weak cryptographic controls at that layer dramatically increase the blast radius of a single failure, per Indusface's OWASP A04:2025 analysis: one exposed token or one misconfigured endpoint can expose everything that API touches, regardless of how well the underlying database is encrypted.

Client-side encryption compounds the same mistake in the opposite direction. It does not protect server-side assets, and it is straightforward for an attacker to bypass, so relying on it for anything sensitive is a layering error that reads correctly in a design document and fails completely in production. Encryption applied too late in a data flow has the same effect: if data passes through an unencrypted intermediate stage, a queue, a cache, a third-party enrichment call, encrypting it before final storage doesn't retroactively close that exposure window. And when legacy protocols stay running in production, they make the pattern worse at the infrastructure level. Applications that still accept outdated TLS versions or weak cipher suites usually keep them because those configurations don't cause visible failures, so they pass every operational review and no one notices they're still there. The structural outcome across all of this is the same: an attacker doesn't need to break encryption where it's applied. They find the layer where it isn't.

Key management antipatterns that survive every surface-level audit

Even an organization that gets every algorithm right and covers every layer consistently can still fail here, because keys are the part of the system that make all of it work, and keys are managed, in practice, by people under deadline pressure. Three patterns recur constantly: hard-coded keys, keys shared across environments, and keys that never rotate.

Embedded directly in code, cryptographic keys covered by CWE-321 are effectively impossible to rotate and get exposed the moment anyone gets access to source code or even a compiled binary. Hardcoded API keys, OAuth tokens, JWT signing secrets, encryption keys, and cloud credentials sitting in repositories are a direct failure of cryptographic protection: encryption can't protect data once an attacker already has the key. And once a credential like that is exposed, it's frequently exploited within minutes, letting attackers abuse APIs, reach cloud resources, or impersonate trusted services.

What makes this especially hard to catch is that even strong infrastructure, hardware security modules, dedicated key management services, doesn't prevent compromise when it sits on top of a misconfigured or overly complex cloud environment. In multi-cloud setups, governance gaps appear and keys end up shared across services or environments that were supposed to stay isolated. A reviewer can confirm that a key management service exists and is configured correctly. But confirming that every application component actually calls that service, instead of falling back to a hard-coded key left in a config file from an earlier build, takes a different kind of scrutiny. The gap here is a scoping limit: a document review or architecture walkthrough can't enumerate every call site in a codebase. Only reading the code can.

Why tests without code access miss these failures

Plaintext edge cases, layer inconsistency, and key management antipatterns share one trait: none of them produces a signal at the network or application surface. A black-box tester watching HTTPS enforced on every endpoint, and encrypted storage confirmed at the architecture level, has no way to see that a backup job writes plaintext, that a secondary logging path skips encryption entirely, or that the key sitting properly in a key management service is also duplicated as a hard-coded fallback in a config file somewhere. Automated scanning catches what shows up at the surface: deprecated TLS versions, weak cipher suites, insecure cookies. It cannot read application code, so it cannot find the paths where encryption gets bypassed or the places where keys live outside the system meant to manage them.

This is the methodological turn the whole argument rests on. These failure patterns aren't just technically interesting edge cases. They are structurally invisible to the testing approach most organizations default to. A system can return valid responses, process requests normally, and show no latency anomaly or error at all while an attacker reads protected data in real time. If the only tool pointed at that system is a scanner or a tester working without access to source code, the failure stays hidden indefinitely. Finding it requires whitebox access: reading the code, not just observing what the code produces.

Compliance audits and the same blind spot

Compliance frameworks are built to verify that controls exist. That is genuinely useful work, and it is not the same thing as verifying those controls are applied consistently across every code path, which is exactly the gap that lets an organization pass a SOC 2, HIPAA, or ISO 27001 audit while still carrying every failure pattern described above.

SOC 2 Type II does not explicitly require penetration testing. A recent pentest is commonly submitted as supporting evidence, but what the audit actually requires is documentation that controls exist, not proof that those controls cover every data path in the system. ISO 27001:2022 doesn't name penetration testing directly either, though several of its controls imply it: auditors expect evidence of vulnerability testing, and that expectation can be satisfied with code reviews and automated scans alone, which carry the same surface-level blind spots described in the previous section. HIPAA is further behind still. The proposed 2025 Security Rule update would require annual penetration testing if it's finalized, but the current rule remains risk-based and does not mandate it. A large number of covered entities have never had their encryption implementation tested at the code level.

The audit failure mode here is identical to the key management problem: a reviewer can confirm a key management service is deployed while every application's actual calls to it, versus its hard-coded fallbacks, go unverified. A company can hold a clean SOC 2 report, a valid ISO 27001 certificate, and a HIPAA attestation while its backup pipeline writes patient data in plaintext and its CI/CD configuration contains a hard-coded encryption key, because none of those audits required anyone to read the code. Regulators have moved past being satisfied by "AES-256 at rest" on a diagram. The distance between a clean compliance attestation and actual protection is now a regulatory and business liability, not just a technical one.

What finding these failures requires in practice

Surfacing these failures means tracing sensitive data through every path it travels, primary storage, backups, logs, caches, queues, and API responses, and verifying encryption and key hygiene at each stop along the way. That's only possible with direct access to code and configuration, and it breaks down into a few concrete steps.

Tracing data paths starts with identifying what counts as sensitive, then following every code path that data moves through, including exception handlers, administrative endpoints, export functions, and background jobs, often written by different teams at different times with no shared standard. Verifying encryption at rest means checking database encryption, application-layer field encryption, backup encryption, and log redaction as four separate questions, because none of them implies the others. Key management review means reading configuration files, environment variable definitions, CI/CD pipeline definitions, and infrastructure-as-code to check whether keys are hard-coded, whether rotation is actually enforced, whether keys cross environment boundaries they shouldn't, and whether expired keys are still active. Secret scanning across repositories and pipeline artifacts catches the hard-coded key and credential patterns under CWE-321 that survive manual review simply because reviewers aren't systematically looking for them.

Every finding worth reporting needs to be proven, not asserted. "This key appears to be hard-coded" is a hypothesis. "Using this key, data X was accessed" is a finding, and that distinction is what separates a report that creates real urgency from one that gets filed and ignored. None of this replaces human judgment, either. Deciding what data actually counts as sensitive, which code paths reach it, and whether a given key management pattern is a real risk in a specific environment takes expert judgment that automated scanning can't supply on its own.

Remediating and retesting cryptographic failures

Fixing cryptographic failures means you change code and configuration at every layer where the failure lives, then you retest at the same depth the original test used. A retest that only checks the headline finding and skips the adjacent paths restores a false sense of security while leaving the gap open.

For plaintext edge cases, fix the specific logging and backup paths that write sensitive data unencrypted, then audit every other exception and export path for the same pattern, because the root cause is usually an architectural assumption, not a single bad line of code. For layer inconsistency, establish field-level encryption requirements at the data model layer, so new code inherits the control automatically and you don't need each engineer to remember to apply it. For key management antipatterns, rotate and revoke every hard-coded or long-lived key the moment it's discovered, migrate to a managed key service with enforced rotation, and scan CI/CD pipeline history, not just the current codebase, since old commits often still hold exposed secrets. For password hashing failures specifically, migrate stored credentials to bcrypt, scrypt, or Argon2 with proper salting and sufficient iteration counts, and invalidate existing sessions to force re-authentication against the new hashes.

Retesting has to happen at the code level, and it needs the same whitebox access the original test used. A surface-level retest can confirm HTTPS is enforced and the database shows as encrypted, but that says nothing about whether the backup pipeline now encrypts its output. Continuous pull-request scanning is the natural next step beyond a point-in-time fix, because these failures get introduced through ordinary code changes, a new export function, a new environment variable, a new background job, so the control belongs in the development pipeline, not in an annual test cycle. Continuous secret scanning addresses the recurrence pattern directly: developers under deadline pressure reintroduce hard-coded credentials, and a CI/CD gate that catches them before merge works more reliably than a periodic audit ever will. These failures don't keep happening because developers are careless. They keep happening because the conditions that create them, time pressure, layered systems, and diffuse ownership of encryption logic, don't go away after the first fix. The answer has to be structural: controls built into the development process itself, not a cleaner report sitting in a compliance folder.

Sources

  1. HSM and TPM Failures in Cloud: A Real-World Taxonomy and Emerging Defenses
Filed underOWASP Top 10

More in OWASP Top 10