Latest Intelligence

OWASP Top 10 — #2: Cryptographic Failures

6/28/2026
Research Report

Technical Presentation

OWASP Top 10 — #2: Cryptographic Failures

What Was #1 About? Quick Recap

In our first blog, we covered Broken Access Control — the problem of users being able to do things they shouldn't be able to do. You can read it [here]. Today, we tackle a completely different but equally dangerous vulnerability: Cryptographic Failures.

Chapter 1: What Does "Cryptography" Even Mean?

Before we talk about failures, let's make sure you understand what cryptography actually is.

The simplest definition: Cryptography is the art of scrambling information so that only the intended person can read it.

A Letter Analogy You'll Never Forget

Imagine you want to send a secret letter to your best friend across town. But the mailman might read it. Your mom might accidentally open it. A stranger on the bus might peek at it. So you and your friend agree on a system:

This is cryptography in its purest form. You take readable information (plaintext), scramble it (encrypt), send it through an unsafe channel (the mail), and the recipient unscrambles it (decrypt) using a shared secret (the key).

Now replace "letter" with "data," "mailman" with "hacker on the internet," and "secret code" with "encryption key" — you now understand the fundamental concept.

The Vocabulary of Cryptography

Let's learn the essential terms before going further. Think of this as learning the names of tools before starting a construction project:

Let me explain each one:

Term

What It Is

Real-Life Analogy

Plaintext

The original, readable data

A letter before you seal it

Ciphertext

The scrambled, unreadable version

The letter after encoding with a secret code

Encryption

The process of scrambling data

Locking something in a safe

Decryption

The process of unscrambling data

Unlocking the safe with the key

Key

The secret value that makes encryption work

The combination to the safe.

Hashing

A one-way scramble (cannot be reversed)

Burning a document — you can't un-burn it

Salt

Random noise added before hashing

Adding random spices so no two dishes taste identical

⚠️ Critical distinction: Encryption is reversible (if you have the key). Hashing is NOT reversible (even if you have the "key"). This is why we hash passwords but encrypt credit card numbers.

Chapter 2: What Exactly Are Cryptographic Failures?

In plain English: Cryptographic Failures are security vulnerabilities that happen when sensitive data is not properly encrypted — either it's not encrypted at all, encrypted with weak methods, or the encryption is implemented incorrectly.

The Two Big Questions

Every cryptographic failure comes down to two fundamental failures:

This diagram is critical. Cryptographic failures happen when you either:

  1. Don't recognize that certain data is sensitive and needs protection, OR
  2. Recognize it but protect it incorrectly (wrong algorithm, weak key, bad implementation)

Where Does Data Exist?

To understand where encryption can fail, you first need to understand that data exists in two states:

  • Data at Rest = Data sitting in a database, on a hard drive, in a backup file. It's not moving — it's just stored. Think of a letter sitting in a filing cabinet.
  • Data in Transit = Data moving across the internet, WiFi, or any network. Think of a letter being carried by the mailman.
  • Data in Processing = Data currently being used in memory. This is often overlooked but is an attack vector too..

Cryptographic failures can happen at ANY of these stages. The OWASP list covers all of them.

Chapter 3: Every Type of Cryptographic Failure, Explained in Depth

3.1 — Data Transmitted in Clear Text (No Encryption)

What is it? Sensitive information is sent over the network without any encryption — anyone on the same network can read it.

Real-life analogy: You're shouting your bank account number across a crowded room. Everyone hears it. You think you're having a private conversation, but you're actually broadcasting to the entire room.

This diagram tells a complete story: Alice types her username and password on a website that uses HTTP (not HTTPS). Her browser sends this information across the network. The data is readable — not scrambled, not encrypted, just plain text. A hacker sitting on the same WiFi network (coffee shop, airport, hotel) runs a simple tool called Wireshark, which captures network packets. The hacker reads Alice's credentials as easily as reading a newspaper. Now they can log in as Alice and do everything she can do.

How common is this? According to Google's Transparency Report, as of 2024, about 95% of web traffic is now HTTPS. But that still leaves 5% — and many internal APIs, older applications, and mobile backends still use HTTP. Additionally, just because the front page uses HTTPS doesn't mean all API calls do.

What does it look like in code?

python
# ❌ VULNERABLE — Sending sensitive data over HTTP
import requests

# This sends the password in PLAINTEXT over the network
response = requests.post('http://api.example.com/login', json={
    'username': 'alice',
    'password': 'SuperSecret123!'
})

# A hacker on the same network can read this!
python
# ✅ SECURE — Using HTTPS (TLS encryption)
import requests

# HTTPS encrypts ALL the data between browser and server
# A hacker on the same network sees only encrypted gibberish
response = requests.post('https://api.example.com/login', json={
    'username': 'alice',
    'password': 'SuperSecret123!'
})

The difference is literally one letter: http vs https. But that single letter activates TLS (Transport Layer Security) encryption, which scrambles everything between your browser and the server.

3.2 — Weak or Deprecated Cryptographic Algorithms

What is it? Using old, broken, or theoretically weak algorithms that modern computers can crack.

Real-life analogy: Imagine you wrote a secret code in 1920. At the time, it was unbreakable — it would take years to decode. But today, with modern computers, it takes 5 minutes. You're still using that 1920 code to protect your bank account. That's what using a weak algorithm is like.

Let's understand each broken algorithm:

MD5 (Message Digest 5): Released in 1991, it was once the most popular hash function. But in 2004, researchers demonstrated they could create collisions — two different inputs producing the same hash — in seconds on a normal computer. In 2012, Flame malware exploited MD5 collisions to forge Microsoft certificates. Today, you can crack MD5 hashes online for free in seconds.

SHA-1: Was considered secure until 2017 when Google and CWI Amsterdam demonstrated the SHAttered attack — they created two different PDF files with the same SHA-1 hash. This proved SHA-1 was broken. Google, Microsoft, and all major browsers have since deprecated SHA-1 certificates.

DES: Used a 56-bit key which was considered strong in 1977. But with modern hardware, a 56-bit key can be brute-forced (tried every combination) in less than 24 hours. Even worse, it was designed during an era when the NSA deliberately weakened the algorithm.

RC4: Was widely used in WiFi security (WEP), SSL/TLS, and Skype. In 2013, researchers showed they could recover TLS cookies in 75 hours. In 2015, RFC 7465 officially banned RC4 from all TLS implementations.

3.3 — Hardcoded, Default, or Weak Encryption Keys

What is it? The secret key used for encryption is either hardcoded in the source code, uses a default value, or is so weak it can be guessed.

Real-life analogy: You have an incredibly strong safe with a 32-digit combination lock. Nobody can crack it... except the combination is written on a sticky note on the front of the safe. Or the combination is 123456.

Let's look at a real-world example of a hardcoded key:

python
# ❌ VULNERABLE — Key is hardcoded in the source code
import hashlib

# This key is embedded right in the code
# Anyone who reads the source code can see it
SECRET_KEY = "my-super-secret-key-12345"

def encrypt_data(data):
    # Using a hardcoded key for encryption
    cipher = AES.new(SECRET_KEY.encode(), AES.MODE_CBC)
    return cipher.encrypt(pad(data))

# PROBLEM: This key is now:
# 1. Visible to anyone with access to the source code
# 2. Stored in version control (Git history forever)
# 3. Same for every deployment of this application
# 4. If the code is open-source, the key is public!

Real-world nightmare: In 2021, researchers scanned GitHub and found over 100,000 repositories containing hardcoded API keys, encryption keys, and secrets. These included AWS access keys, database passwords, and JWT signing keys — all publicly visible.

python
# ✅ SECURE — Key is stored in environment variables / secrets manager
import os

def encrypt_data(data):
    # Key is loaded from environment variable
    # Not in source code, not in Git, not visible in logs
    secret_key = os.environ['ENCRYPTION_KEY']
    
    # Better yet: use a dedicated secrets manager
    # secret_key = vault.get_secret('encryption-key')
    
    cipher = AES.new(secret_key.encode(), AES.MODE_CBC)
    return cipher.encrypt(pad(data))

# The ENCRYPTION_KEY is set via:
# - Environment variable (never in code)
# - AWS Secrets Manager
# - HashiCorp Vault
# - Azure Key Vault
# - Kubernetes Secrets

3.4 — Insufficient Key Length

What is it? Using encryption keys that are too short. A key is like a password for encryption — the shorter it is, the easier it is to guess.

Real-life analogy: A 1-digit bicycle lock can be cracked by trying all 10 numbers (0-9) in seconds. A 4-digit padlock has 10,000 combinations — still quick. But a 20-digit combination lock has 10,000,000,000,000,000,000 combinations — it would take billions of years to try them all. Key length is the same principle.

What this diagram shows: The security of encryption scales exponentially with key length. Going from 56-bit to 128-bit doesn't just double security — it makes it literally trillions of times stronger. Each additional bit doubles the number of possible keys an attacker must try.

The math:

  • 56-bit key = 72,057,594,037,927,936 combinations → Crackable in hours with modern hardware
  • 128-bit key = 3.4 × 10³⁸ combinations → Would take billions of years with all computers on Earth
  • 256-bit key = 1.2 × 10⁷⁷ combinations → More than the estimated number of atoms in the observable universe

3.5 — Missing or Incomplete TLS Configuration

What is it? The application uses HTTPS, but the TLS configuration is weak — allowing old protocols, weak cipher suites, or having other misconfigurations.

Real-life analogy: You installed a modern deadbolt on your front door — great! But you also left the old rusty latch on the side door, and you configured the deadbolt to work with a weak 3-digit combination instead of the full 32-digit one. The front door LOOKS secure, but a skilled burglar can still get in.

Understanding this diagram: The left side shows a broken TLS setup. SSL 2.0 and 3.0 are ancient protocols with known critical vulnerabilities. TLS 1.0 and 1.1 are deprecated — PCI-DSS (the payment card industry standard) banned them in 2018. Weak cipher suites allow attackers to downgrade the encryption. Self-signed certificates can be impersonated.

The right side shows a proper setup. TLS 1.3 is the gold standard — it's faster (fewer round trips) and more secure (removed all legacy features that were attack vectors). TLS 1.2 is still secure if configured correctly. Strong cipher suites use modern algorithms. Valid certificates from trusted Certificate Authorities ensure you're talking to the real server.

3.6 — Poor Password Hashing

What is it? Storing passwords incorrectly — either as plaintext, or using weak/unsalted hashing.

This deserves special attention because password storage failures have caused some of the largest data breaches in history.

Let's understand WHY speed matters for passwords:

This is the key insight about password hashing: For encryption, you WANT fast algorithms — you need to encrypt/decrypt data quickly. But for password hashing, you WANT slow algorithms. Why? Because:

  • MD5 can compute 10 billion hashes per second on a GPU. That means an attacker can try 10 billion passwords per second.
  • bcrypt can compute only 30,000 hashes per second on the SAME GPU. That means an attacker can only try 30,000 passwords per second.

That's a 333,000x slowdown for the attacker. What would take 1 second with MD5 takes 3.7 days with bcrypt. What would take 8 days takes 7,000 years.

3.7 — Hardcoded, Default, or Poorly Managed Keys

What is it? Encryption keys, API secrets, and credentials are stored in the source code, committed to Git, or managed poorly.

Each failure explained:

  • Git Repository: You put the API key in config.py, committed it, later realized it was a mistake, deleted it from the file and pushed again. Problem: Git keeps the entire history. Anyone can run git log -p and see the key in a past commit. Even GitHub's git-filter-branch can't guarantee complete removal because copies may exist in forks and clones.
  • Source Code: The key is literally written in the application code. If the code is open-source, the key is public. If the code is shared with a client or partner, they see the key. If a developer leaves the company, they might still have a copy.
  • Default Credentials: The software ships with a default key (e.g., CHANGE_ME or DEFAULT_SECRET). Many users never change it. In 2020, researchers found that many IoT devices shared the same hardcoded root password — one password compromised millions of devices.
  • Shared Between Environments: The same API key is used in development and production. A developer accidentally logs it during debugging. Or an intern pushes dev code to production. Now the "secret" key is in the dev environment, which is typically less secure.
  • Exposed in Logs: The application's error handler accidentally prints the encryption key to the error log. Or debug mode is accidentally enabled in production, and the key is in the debug output. Or the key appears in a stack trace that's sent to an error monitoring service.
  • Unrotated Keys: The company generated an encryption key 5 years ago and never changed it. If the key was compromised on day 1, the attacker has had 5 years of undetected access to all encrypted data. Industry best practice is to rotate keys every 90 days for critical systems.

3.8 — Missing Encryption for Sensitive Data at Rest

What is it? Sensitive data is stored in databases, files, or backups without any encryption.

This scenario illustrates the two outcomes perfectly:

Without encryption (top path): User enters their credit card. Server stores it as-is in the database — plain readable text. When a hacker breaches the database (through SQL injection, stolen credentials, or any other method), they get every credit card number in plain text. This is what happened in the Target breach (2013, 40 million cards), the Home Depot breach (2014, 56 million cards), and many others.

With encryption (bottom path): Same user, same credit card, but the server encrypts it before storing. The database only contains ciphertext — meaningless strings of characters. Even if a hacker breaches the database, they get garbage. Without the encryption key (which is stored separately), the data is useless.

3.9 — Missing or Weak Sensitive Data Encryption

What is it? Even when encryption is attempted, it may be using the wrong algorithm, weak keys, or incorrect implementation.

ECB Mode — The Famous Penguin:

This is one of the most famous cryptographic failures in history. When you encrypt an image using AES in ECB mode, each pixel block is encrypted independently. This means identical pixel blocks produce identical ciphertext blocks. The result? The encrypted image still shows the shape of the original image.

IV (Initialization Vector) Reuse:

An IV is a random value used to ensure that the same plaintext encrypted twice produces different ciphertext. If you encrypt "Hello" today and "Hello" tomorrow with the same key and same IV, you get the same ciphertext both times — revealing that the message is the same.

Chapter 4: Real-World Breaches

Case Study: Adobe (2014) — The Mass Password Disaster

This breach perfectly illustrates multiple cryptographic failures combined:

The complete failure chain:

  1. Encryption instead of hashing: Adobe encrypted passwords instead of hashing them. This means passwords could be reversed (decrypted) if the key was compromised. Hashing is one-way — you can never get the original password back.
  2. ECB mode: Because ECB encrypts each block independently, identical passwords produced identical ciphertext. Attackers could immediately identify which passwords were the same, and focus on cracking the most common ones first.
  3. Plaintext password hints: Adobe stored password hints alongside the encrypted passwords. Hints like "my dog's name" or "password plus year" essentially told attackers what the password was. Some hints were literally the password itself.
  4. Single encryption key: One key was used for all 153 million passwords. If that one key was discovered, every single password was compromised.

Chapter 5: Prevention Strategies

The Complete Defense Framework

Prevention 1: Classify Your Data First

You can't protect data if you don't know what's sensitive. Map every piece of data:

Prevention 2: Password Hashing Done Right

python
# ❌ TERRIBLE: Plaintext storage
db.save("users", {"email": "alice@mail.com", "password": "SuperSecret123!"})

# ❌ BAD: MD5 (fast, no salt — trivially crackable)
import hashlib
hashed = hashlib.md5("SuperSecret123!".encode()).hexdigest()

# ❌ BAD: SHA-1 (fast, no salt — still too fast)
hashed = hashlib.sha1("SuperSecret123!".encode()).hexdigest()

# ⚠️ OKAY: SHA-256 with salt (fast but at least salted)
import hashlib, os
salt = os.urandom(32)
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)

# ✅ GOOD: bcrypt (slow, auto-salted, industry standard)
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))

# ✅ BEST: Argon2 (slow, memory-hard, GPU-resistant)
from argon2 import PasswordHasher
ph = PasswordHasher(
    time_cost=3,        # 3 iterations
    memory_cost=65536,  # 64MB of memory
    parallelism=4       # 4 threads
)
hashed = ph.hash(password)

# Verifying a password
try:
    ph.verify(hashed, password)  # Returns True if match
except:
    pass  # Password is wrong

Prevention 3: TLS Configuration Done Right

yaml
# ✅ Nginx: Secure TLS Configuration

server {
    listen 443 ssl http2;
    
    # Only allow TLS 1.2 and 1.3
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Only strong cipher suites
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    
    # Use secure key exchange
    ssl_prefer_server_ciphers on;
    
    # OCSP Stapling for faster, more private certificate validation
    ssl_stapling on;
    ssl_stapling_verify on;
    
    # HSTS — Force browsers to always use HTTPS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    
    # Prevent clickjacking and other attacks
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
}

# Redirect ALL HTTP to HTTPS
server {
    listen 80;
    return 301 https://$host$request_uri;
}

Prevention 4: Proper Key Management

Prevention 5: Scan for Leaked Secrets

Use automated tools to find hardcoded secrets before they're deployed:

sh
"text-slate-500 font-normal italic"># 🔍 git-secrets — Blocks commits containing secrets
"text-slate-500 font-normal italic"># Install: brew install git-secrets
git secrets --install
git secrets --register-aws
"text-slate-500 font-normal italic"># Now any commit containing AWS keys will be BLOCKED

"text-slate-500 font-normal italic"># 🔍 truffleHog — Scans entire Git history for secrets
pip install trufflehog
trufflehog git https://github.com/your-org/your-repo

"text-slate-500 font-normal italic"># 🔍 gitleaks — Another excellent Git secret scanner
"text-slate-500 font-normal italic"># Install: brew install gitleaks
gitleaks detect --source="." --verbose

"text-slate-500 font-normal italic"># 🔍 detect-secrets (by Yelp) — Baseline approach
pip install detect-secrets
detect-secrets scan --all-files
detect-secrets audit .secrets.baseline

Chapter 6: Complete Encryption Architecture

Here's how a properly encrypted system should look end-to-end:

Walking through each layer:

  1. User's Browser ↔ CDN: All traffic between the user and your infrastructure is encrypted using TLS 1.3. The CDN terminates the TLS connection, inspects for security, and forwards to your origin server.
  2. CDN ↔ API Gateway: Internal traffic is also encrypted. Never assume your internal network is safe — attackers who breach one service can potentially sniff traffic to other services.
  3. Application ↔ Key Management: The application never hardcodes encryption keys. Instead, it requests keys from a dedicated KMS (Key Management Service) like AWS KMS, Azure Key Vault, or HashiCorp Vault. Keys are stored in HSMs (Hardware Security Modules) — tamper-resistant physical devices.
  4. Application ↔ Database: Before writing any sensitive data to the database, the application encrypts it using the key from KMS. The database itself may also have Transparent Data Encryption (TDE) as an additional layer. Backups are encrypted too.
  5. Passwords: Passwords are NEVER encrypted — they're hashed using a slow, salted algorithm (Argon2 or bcrypt). Even database administrators cannot see user passwords because hashing is one-way.

Chapter 7: The Mental Model

Every time you handle sensitive data, ask these questions:

Final Summary

Protect sensitive data everywhere — in transit (TLS 1.2+), at rest (AES-256), and in storage (hash passwords with Argon2/bcrypt). Never hardcode keys. Never use broken algorithms (MD5, SHA-1, DES). Always ask: "Is this data encrypted? With what? Where's the key? Is it strong enough?"