DEV Community

Olawale Afuye
Olawale Afuye

Posted on

Cryptographic Failures: The Silent Killer in Your Codebase (OWASP #2)

You ship a feature. Tests pass. Deployment goes smooth. Everyone's happy.

Meanwhile, somewhere in your codebase, you're storing passwords with MD5.

And someone, right now, is cracking them in under a second.

That's the thing about Cryptographic Failures — they don't throw errors. They don't break your CI pipeline. They sit quietly in production until the day they don't.


What Are Cryptographic Failures?

OWASP ranks them #2 on the Top 10 list of most critical web application vulnerabilities. Not #7. Not #5. Number two.

And the definition is deceptively simple: sensitive data is not protected by cryptography — or it's protected badly.

That second part is where most developers get caught. It's not that they skipped encryption entirely. It's that they used the wrong algorithm, mismanaged their keys, or trusted a default that hasn't been safe since 2004.

The result is the same either way: unauthorized access to data that was supposed to be locked.


Why Does This Keep Happening?

Three root causes show up again and again.

1. Weak or Deprecated Algorithms

Not all encryption is created equal. Some algorithms that were considered secure a decade ago are now trivially breakable with modern hardware.

The most common offender? MD5 for password hashing.

MD5 was never designed for passwords — it was designed for fast checksums. "Fast" is the exact opposite of what you want when hashing credentials. Fast means attackers can run billions of attempts per second against a leaked hash database.

Here's a concrete example of what not to do:

import hashlib

# ❌ Never do this
password = "mypassword123"
hashed = hashlib.md5(password.encode()).hexdigest()
Enter fullscreen mode Exit fullscreen mode

This is what led to the Freecycle data breach — a real-world incident where attackers accessed user credentials because the platform was using MD5. Once they had the hash database, cracking the passwords wasn't a challenge. It was a formality.

What you should use instead:

import bcrypt

# ✅ Use a proper password hashing algorithm
password = b"mypassword123"
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
Enter fullscreen mode Exit fullscreen mode

bcrypt, argon2, and scrypt are slow by design. That's the point.

The same principle applies to transport protocols. SSL 2.0, SSL 3.0, and TLS 1.0/1.1 are all deprecated. If your server still supports them, you're offering attackers a downgrade path. TLS 1.3 is the minimum you should be running.


2. Poor Key Management

You encrypted the data.

You stored the encryption key in the same repository.

Congratulations — you haven't protected anything.

This is embarrassingly common. Developers hardcode secrets directly into source code, commit them to GitHub (sometimes public repos), and ship them to production. The encryption is technically present. The protection is not.

// ❌ This is not secret management
const SECRET_KEY = "my_super_secret_key_123";
Enter fullscreen mode Exit fullscreen mode

The fix isn't complicated, but it requires intentionality:

// ✅ Pull from environment or a secrets manager
const SECRET_KEY = process.env.SECRET_KEY;
Enter fullscreen mode Exit fullscreen mode

Better yet, don't use environment variables for truly sensitive credentials. Use a dedicated secrets manager — AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager. These give you rotation, access control, audit logs, and a single source of truth for your credentials.


3. No Encryption at Rest or in Transit

Some applications simply don't encrypt sensitive data at all.

No HTTPS enforcement. Database fields stored in plaintext. PII sitting in logs. Backups with no encryption.

If your threat model includes "what happens when our database is dumped?" — and it should — plaintext storage is catastrophic. Encryption at rest means that even if an attacker exfiltrates your data, they get ciphertext, not customer information.


How to Prevent Cryptographic Failures

Prevention isn't one thing. It's a stack of practices.

Use Modern, Appropriate Algorithms

Use Case Recommended Avoid
Password hashing bcrypt, argon2, scrypt MD5, SHA-1, plain SHA-256
Data encryption AES-256-GCM DES, 3DES, RC4
Transport security TLS 1.3 SSL, TLS 1.0/1.1
Key exchange ECDH, RSA-2048+ Anything < 2048-bit RSA

Manage Secrets Properly

  • Never hardcode credentials in source code
  • Never commit .env files to version control (add to .gitignore immediately)
  • Use a secrets manager for production
  • Rotate keys regularly and have a rotation plan
  • Enforce least-privilege access to secrets

Bake Security Into Your Dev Workflow

The best time to catch cryptographic failures is before they reach production. These four tool categories should be in your pipeline:

SAST — Static Application Security Testing

Scans your source code without running it. Catches hardcoded secrets, insecure function calls, and deprecated API usage.

→ Try: Bandit (Python), Semgrep

DAST — Dynamic Application Security Testing

Tests your running application. Finds vulnerabilities that only appear at runtime — weak cipher suites, missing security headers, misconfigured TLS.

→ Try: OWASP ZAP

Secrets Detection

Scans your git history and codebase for leaked credentials, API keys, and tokens. Because git push is forever.

→ Try: GitLeaks, TruffleHog

SCA — Software Composition Analysis

Audits your dependencies for known vulnerabilities. That npm package you haven't updated in two years? It might be pulling in an insecure crypto library.

→ Try: Trivy, npm audit, Snyk


A Practical Checklist

Before you ship, run through these:

  • [ ] Are passwords hashed with bcrypt, argon2, or scrypt?
  • [ ] Is TLS 1.3 enforced? SSL and early TLS versions disabled?
  • [ ] Are encryption keys stored outside the codebase?
  • [ ] Is sensitive data encrypted at rest in the database?
  • [ ] Are .env files in .gitignore?
  • [ ] Is SAST running in your CI pipeline?
  • [ ] Have you scanned your git history for leaked secrets?
  • [ ] Are dependencies up to date and audited?

The Uncomfortable Truth

Most cryptographic failures aren't the result of developers not caring.

They're the result of developers moving fast, inheriting legacy code, or trusting defaults that were never safe to trust. MD5 was everywhere in tutorials for years. Hardcoded secrets are convenient. "We'll fix it later" is a sentence said in every company on earth.

The problem is that "later" sometimes arrives looking like a breach notification email.

Cryptography isn't optional infrastructure. It's the floor. Everything else you build sits on top of it — and if the floor is weak, it doesn't matter how good the rest of the building is.


Further Reading


If this helped clarify something that's been fuzzy, share it with a teammate who's still using MD5 somewhere. You'd be doing them a favour.

Top comments (0)