Cryptography

Cryptographic Failures Explained: Encryption Mistakes Developers Still Make

Understand common cryptographic failures, from weak algorithms and nonce reuse to poor key management, password storage and TLS mistakes, with practical developer guidance.

CryptographyApplication SecurityOWASP Top 10EncryptionKey Management
Cryptographic Failures Explained: Encryption Mistakes Developers Still Make article cover

Encryption can be mathematically strong and still fail in an application.

That sounds contradictory, but it is one of the most important lessons in practical cryptography. A developer can choose AES, HTTPS or a modern password hashing function and still create an insecure system if keys are exposed, nonces are reused, certificates are not validated, passwords are encrypted instead of hashed, or sensitive data is protected in one place but leaked somewhere else.

OWASP Top 10:2025 places Cryptographic Failures at A04. The category covers more than broken ciphers. OWASP includes missing cryptography, weak or risky algorithms, leaked keys, weak random number generation and mistakes in how cryptographic mechanisms are used. The practical problem is therefore not simply, “Did we encrypt the data?” A better question is, “Did we protect the right data with the right primitive, keys, parameters and lifecycle controls?”

This article explains the mistakes developers should look for and a safer way to reason about cryptography in application design.

Cryptographic protection lifecycle from sensitive data through algorithm, keys, nonce, storage and verification

What is a cryptographic failure?

A cryptographic failure happens when an application does not provide the confidentiality, integrity, authenticity or other cryptographic property it is expected to provide.

Sometimes the failure is obvious. A database stores sensitive data in plaintext. A web application accepts an obsolete transport protocol. An API key is committed to source control.

Other failures are more subtle. The application uses a respected algorithm, but the key is hard-coded in the same repository. AES-GCM is selected, but a nonce is reused with the same key. Passwords are stored with a fast general-purpose hash. TLS is enabled, but the client does not properly validate the server certificate.

These are implementation and system-design problems, not evidence that modern cryptography itself is useless.

The same distinction matters elsewhere in application security. A secure library cannot compensate for a dangerous deployment model, just as a correctly patched application can still be exposed by security misconfiguration.

Start with the security property you actually need

Developers often reach for “encryption” as if it were one universal control. Cryptography provides several different properties, and choosing the wrong one can create false confidence.

Need Appropriate concept Common mistake
Keep data secret Encryption Using encryption without integrity protection
Detect unauthorized modification Authentication or integrity protection Assuming ciphertext cannot be altered
Store user passwords for verification Password hashing Encrypting passwords so they can be decrypted later
Prove who signed data Digital signature Replacing signatures with an ordinary hash
Generate reset tokens or session secrets Cryptographically secure randomness Using predictable application PRNG output
Protect network traffic Properly configured TLS Building a custom encrypted socket protocol

This table is intentionally simplified. The lesson is that the primitive should follow the security requirement, not the other way around.

Mistake 1: using a weak, obsolete or inappropriate algorithm

Cryptographic algorithms age. Some are broken by advances in cryptanalysis, some no longer provide an adequate security margin, and some are still mathematically useful but inappropriate for a particular job.

A dangerous pattern is keeping an algorithm because “it still works” in the application. Functional compatibility is not the same as cryptographic suitability.

OWASP Top 10:2025 explicitly warns against deprecated cryptographic functions and risky algorithms. For modern application development, teams should prefer well-reviewed standards and current platform libraries rather than inventing their own algorithms or reviving old examples copied from years-old tutorials.

NIST maintains standards and recommendations for approved cryptographic mechanisms, including AES and key-management guidance. The IETF’s current TLS best-practice document, RFC 9325, recommends support for TLS 1.3 and preference for it over earlier versions when it is available.

A useful engineering rule is: do not choose algorithms from memory. Choose them from current standards, the current security guidance for your ecosystem, and the supported high-level APIs of a maintained cryptographic library.

Mistake 2: encryption without authentication

Confidentiality alone is often not enough.

Suppose an application encrypts a financial instruction. If an attacker cannot read the plaintext but can modify the ciphertext in a way that causes a meaningful change after decryption, confidentiality has not protected the integrity of the instruction.

This is why modern application designs often use authenticated encryption with associated data (AEAD). AEAD modes protect confidentiality while also allowing the receiver to verify that the protected data has not been modified without authorization. AES-GCM is one standardized example.

OWASP’s current Cryptographic Failures guidance recommends authenticated encryption instead of encryption alone. When a platform provides a high-level AEAD API, developers should normally use that API rather than manually combining encryption and a message authentication code.

The deeper lesson is not “always use one specific mode forever.” It is to select a current, reviewed construction that provides all required properties and to follow its parameter rules exactly.

Mistake 3: reusing a nonce or IV when the mode requires uniqueness

This is one of the clearest examples of how a strong algorithm can be defeated by incorrect use.

Some encryption modes require an initialization value or nonce that must not repeat under the same key. For GCM, NIST has specifically emphasized that security depends strongly on avoiding repeated IVs for distinct messages. A developer who treats a nonce as an unimportant random-looking field can therefore undermine the protection even though the key itself remains secret.

The right nonce strategy depends on the construction and library. Do not assume every mode has the same requirement. Some APIs generate the nonce for you, while others expect the application to supply it.

What matters operationally is that the application can guarantee the required nonce property across retries, restarts, multiple servers and key rotation. A design that is unique only inside one process may fail when ten application instances share the same encryption key.

That is a system problem, not just a few lines of crypto code.

Mistake 4: hard-coding or poorly storing keys

Encryption keys are not configuration trivia. They are security-critical secrets.

If an application encrypts a database field with a key that is committed in the same source repository, anyone who obtains both the database and repository may have everything required to decrypt the data. The ciphertext can look secure while the system’s real trust boundary is broken.

NIST SP 800-57 Part 1 Rev. 5 treats key management as a lifecycle discipline that includes generation, protection, storage, use, replacement, recovery where appropriate, and destruction. OWASP likewise recommends proper key management and stronger protection for highly sensitive keys, including hardware or cloud-based HSMs where justified.

For ordinary application teams, this usually means:

  • keep production keys out of source code and container images;
  • restrict which workloads and people can use a key;
  • separate development, staging and production keys;
  • record key versions so encrypted data can survive controlled rotation;
  • audit key use where the platform supports it;
  • design a rotation and revocation process before an incident happens.

The same principle applies to API keys and credentials. For related secret-handling practices, see How to Protect Sensitive Data, API Keys and Credentials When Using AI Tools.

Mistake 5: confusing password hashing with encryption

A login system normally does not need to recover a user’s original password. It needs to verify whether a future password attempt matches the value originally registered.

That is why passwords should generally be stored using a password hashing scheme, not reversible encryption.

NIST SP 800-63B requires verifiers to store passwords in a form resistant to offline attacks and specifies salted password hashing with an appropriate cost factor. The cost factor matters because a deliberately expensive password hashing operation makes large-scale offline guessing more costly after a password database is stolen.

A fast hash designed for checksums or general integrity work is not automatically a suitable password-storage function. Likewise, encrypting passwords creates a decryption-key problem that a normal password verifier does not need.

A good implementation also stores the algorithm and work-factor information needed for future migration. That allows the application to increase the cost or move to a newer scheme when users authenticate again.

Mistake 6: using predictable randomness for security tokens

Randomness appears in session identifiers, password-reset links, CSRF tokens, API credentials, cryptographic keys, nonces and other security-sensitive values.

The mistake is using a normal application pseudo-random number generator simply because its output looks unpredictable to a human.

Security tokens need randomness designed for adversarial conditions. NIST SP 800-90A specifies deterministic random bit generator mechanisms for cryptographic use, while modern operating systems and language runtimes normally expose a cryptographically secure random source through higher-level APIs.

For example, Python’s secrets module is intended for security-sensitive tokens:

import secrets

reset_token = secrets.token_urlsafe(32)

This is a deliberately small example. A real password-reset flow still needs an expiry, one-time use, account binding, secure transport, rate limiting and safe storage of the reset-token verifier. Secure randomness solves only the unpredictability requirement.

The important developer habit is to distinguish between randomness for simulation or UI behavior and randomness that an attacker must not be able to predict.

Mistake 7: treating TLS as a checkbox

“We use HTTPS” is a useful start, but it is not a complete transport-security review.

A secure TLS deployment depends on protocol versions, certificate validation, hostname verification, supported cipher suites, private-key protection, redirect behavior and the surrounding application configuration.

RFC 9325 encourages migration toward TLS 1.3 and requires careful configuration when TLS 1.2 remains supported. It also warns against falling back to older protocol versions. OWASP’s 2025 Cryptographic Failures guidance recommends modern TLS for data in transit and HSTS for HTTPS applications.

One especially dangerous development shortcut is disabling certificate validation to make a test environment work. If that behavior reaches production, the client may establish an encrypted connection to the wrong endpoint. Encryption without correct peer authentication does not provide the trust property the developer expected.

TLS should therefore be configured through a maintained server, framework, reverse proxy or platform with secure defaults wherever possible. Custom cryptographic transport protocols are rarely justified in ordinary web application development.

Mistake 8: encrypting data but leaking it elsewhere

Cryptography protects a specific data path. It does not automatically protect every copy of the information.

A database column may be encrypted while the same sensitive value appears in application logs, analytics events, exception traces, browser storage, backups or support exports. A well-protected production database does little good if a debug log contains plaintext secrets.

Start by mapping where sensitive data is collected, transmitted, processed, cached, logged, backed up and exported. Then decide what protection each location needs.

This connects cryptographic design to data minimisation. OWASP recommends not retaining sensitive data unnecessarily. Removing data that the application does not need can be a stronger control than adding another encryption layer around it.

Mistake 9: inventing custom cryptography

A developer may understand the basic mathematics of encryption and still miss subtle protocol requirements such as nonce construction, padding, authentication order, key separation, downgrade resistance or side-channel behavior.

That is why “we wrote our own crypto” should trigger serious review.

Use established protocols and high-level library interfaces whenever possible. Let a mature TLS stack handle transport encryption. Let a maintained password hashing library handle salts and work factors. Let a cloud KMS or HSM-backed service manage high-value keys when the threat model requires it.

Reducing the number of cryptographic decisions in application code reduces the number of ways those decisions can be wrong.

This is also a software supply-chain question. A cryptographic library still has to be maintained, patched and obtained through a trustworthy dependency process. The broader dependency and build problem is covered in Software Supply Chain Attacks.

A practical cryptographic review workflow

Instead of reviewing crypto by searching the repository for the word AES, review it as a data and trust problem.

1. Identify sensitive assets

List passwords, personal data, authentication tokens, API credentials, payment-related data, private keys, confidential business records and any regulated information. Decide which properties are required for each one: confidentiality, integrity, authenticity or non-repudiation where applicable.

2. Map every location and transition

For each sensitive asset, identify where it enters the system, where it is stored, how it moves between services, where it is logged, how it is backed up and where it leaves the system.

This exposes gaps such as encrypted database storage combined with plaintext message queues or backups.

3. Inventory cryptographic mechanisms

Record the protocols, algorithms, modes, key lengths, libraries, certificate settings, password hashing schemes and random-number APIs in use. Include versions and configuration, not just algorithm names.

4. Trace key ownership

For every encryption or signing key, ask:

  • Who or what can use it?
  • Where is it stored?
  • Is it shared across environments?
  • How is access audited?
  • What happens if it is exposed?
  • Can it be rotated without losing access to required data?

If these questions have no clear owner, key management is already a risk.

5. Verify parameters and failure behavior

Confirm nonce or IV requirements, authentication-tag handling, certificate validation and error handling. A secure primitive can become unsafe when the application ignores authentication failure or silently falls back to weaker behavior.

6. Test the whole lifecycle

Test rotation, expiry, certificate renewal, restore from backup and compromise response. A cryptographic system that works only while the original key and certificate remain unchanged has not been operationally designed.

What should developers do differently?

The strongest improvement is often to make fewer low-level cryptographic choices.

Prefer secure framework defaults and high-level APIs. Keep libraries current. Use a real secret-management or key-management mechanism. Separate keys by environment and purpose. Store passwords with a modern password hashing scheme. Use cryptographically secure randomness for security tokens. Prefer authenticated encryption for application-level encryption when it fits the requirement. Configure TLS according to current guidance and never disable certificate validation as a production shortcut.

Then review the system around the primitive. Cryptography cannot protect data that is unnecessarily copied, logged in plaintext or exposed through broken authorization. This is why OWASP Top 10:2025 treats cryptographic failures as one category within a wider application-security model rather than a complete security strategy.

A better way to think about cryptography

Cryptographic security is a chain of assumptions.

The algorithm must still be appropriate. The implementation must be trustworthy. Parameters must follow the construction’s rules. Randomness must be suitable. Keys must remain protected. The application must verify integrity and peer identity. Data must not leak through another path. Rotation and recovery must work when the system changes.

If one critical assumption fails, strong mathematics may still produce a weak application.

That is why the practical goal is not to “add encryption.” It is to build a system where the required security property survives from design through deployment and operation.

Sources and further reading