Luke a Pro

Luke Sun

Developer & Marketer

๐Ÿ‡บ๐Ÿ‡ฆ

Encryption โ‰  Security: System-Level Failures

| , 12 minutes reading.

1. Why Should You Care?

Your application uses AES-256-GCM. Your TLS configuration gets an A+ from SSL Labs. Your passwords are hashed with Argon2id.

Then you get breached anyway.

Encryption is a lock on a door. It doesnโ€™t help if the window is open, the key is under the mat, or the wall is made of cardboard.

2. The Security Mindset Gap

What Developers Think

Developer mental model:
"I encrypted the data, so it's secure."

Reality:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     Attack Surface                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚   Data in plaintext โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                     โ”‚
โ”‚         โ†“                             โ”‚                     โ”‚
โ”‚   [ Encryption ] โ† Key management โ”€โ”€โ”€โ”€โ”ผโ”€โ”€ Key exposure      โ”‚
โ”‚         โ†“                             โ”‚                     โ”‚
โ”‚   Encrypted data โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€ Storage breach    โ”‚
โ”‚         โ†“                             โ”‚                     โ”‚
โ”‚   [ Decryption ] โ† Access control โ”€โ”€โ”€โ”€โ”ผโ”€โ”€ Auth bypass       โ”‚
โ”‚         โ†“                             โ”‚                     โ”‚
โ”‚   Data in plaintext โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                     โ”‚
โ”‚                                                             โ”‚
โ”‚   Encryption only protects the middle part!                 โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Real Threat Model

Most breaches don't break encryption. They:
- Steal keys
- Access data before/after encryption
- Bypass authentication
- Exploit misconfigurations
- Use social engineering
- Find logical flaws

Strong encryption with weak operations = Weak system

3. Plaintext Exposure

Case 1: Logging Sensitive Data

# DISASTER: Logging plaintext before encryption
import logging

def process_payment(card_number, amount):
    logging.info(f"Processing payment: card={card_number}, amount={amount}")
    encrypted_card = encrypt(card_number)
    # Card number now in log files forever
    # Log aggregation services
    # Developer laptops
    # Backup systems

# The encrypted database is secure.
# The logs are in 47 different places, unencrypted.

Case 2: Error Messages

# DISASTER: Exceptions containing sensitive data
def decrypt_user_data(encrypted_data, key):
    try:
        return decrypt(key, encrypted_data)
    except DecryptionError as e:
        # Error message includes the key!
        raise Exception(f"Failed to decrypt with key {key}: {e}")

# Error tracking services (Sentry, Bugsnag) now have your keys

Case 3: Memory Dumps and Core Dumps

When processes crash, the OS may dump memory to disk:
- /var/crash/
- Windows error reports
- Docker container logs

Memory contains:
- Decrypted data
- Encryption keys
- Passwords in transit

The "encrypted" data exists in plaintext in memory.
Crash = permanent plaintext record.

Case 4: Swap and Hibernation

Operating systems may write memory to disk:

Swap space:
- RAM overflow goes to disk
- Includes decrypted secrets
- May persist after reboot

Hibernation:
- Entire RAM written to disk
- All secrets saved unencrypted
- Recoverable with disk access

Cloud VMs:
- Hypervisor can read guest memory
- Live migration copies all RAM
- Snapshots include memory state

4. Key Management Failures

Case 1: Keys in Source Code

# GitHub search: "AES_KEY" or "encryption_key" or "secret_key"
# Millions of results

# Found in real repositories:
AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"
ENCRYPTION_KEY = "super_secret_key_12345"
DATABASE_PASSWORD = "admin123"

# Once committed, even if deleted:
# - Still in git history
# - Cached by GitHub search
# - Stored in developer machines
# - Backed up multiple times

Case 2: Keys in Environment Variables (Leaked)

# docker-compose.yml committed to repo
services:
  app:
    environment:
      - ENCRYPTION_KEY=aGVsbG8gd29ybGQK
      - DATABASE_URL=postgres://user:password@db/prod

# CI/CD logs often print environment
# Container inspection reveals env vars
# Process listings show environment

Case 3: Key Reuse Across Environments

Common anti-pattern:
- Same key for dev, staging, and production
- Developer laptops have production keys
- Test databases encrypted with production keys

Breach in dev = Breach in production

Case 4: No Key Rotation

Company uses same encryption key for 10 years:
- Multiple employees who left had access
- Key may have leaked without detection
- All historical data vulnerable if key compromised
- No way to limit blast radius

Key rotation provides:
- Limited exposure window
- Ability to revoke compromised keys
- Regulatory compliance
- Cryptographic hygiene

5. Operational Security Failures

Case 1: Backups

Production database: Encrypted at rest โœ“
Database backups: Unencrypted, stored on S3 โœ—

Real incident (2019):
- Company encrypted their MongoDB properly
- Backup script dumped to unencrypted S3 bucket
- Bucket was public
- 250 million records exposed

The encryption was perfect.
The backup process bypassed it entirely.

Case 2: Development and Debug Access

# Production code with debug endpoint
@app.route('/debug/user/<user_id>')
def debug_user(user_id):
    if request.args.get('debug_key') == 'supersecret':
        user = get_user(user_id)
        return {
            'encrypted_data': user.encrypted_data,
            'encryption_key': user.encryption_key,  # ๐Ÿ˜ฑ
            'decrypted_data': decrypt(user.encrypted_data, user.encryption_key)
        }

# "We'll remove this before production"
# Narrator: They did not.

Case 3: Support and Admin Access

Typical enterprise:
- 50+ people with production database access
- 200+ people with access to decryption keys
- 500+ people who could theoretically access data

Each person is:
- A potential insider threat
- A social engineering target
- A laptop theft risk
- An account compromise risk

Encryption doesn't help when authorized users are the threat.

Case 4: Third-Party Integration

Your security:
- End-to-end encrypted storage
- HSM-backed key management
- Zero-trust architecture

Your vendor integration:
"Just POST the customer data to our webhook as JSON"

// Actual code found in production
async function sendToVendor(customer) {
    await fetch('https://vendor.com/webhook', {
        method: 'POST',
        body: JSON.stringify({
            ssn: customer.ssn,                    // ๐Ÿ˜ฑ
            bank_account: customer.bank_account,  // ๐Ÿ˜ฑ
            password: customer.password           // ๐Ÿ˜ฑ๐Ÿ˜ฑ๐Ÿ˜ฑ
        })
    });
}

6. Side-Channel Leaks

Timing Attacks in Practice

# Authentication timing leak
def authenticate(username, password):
    user = database.get_user(username)

    if user is None:
        return False  # Fast: user not found

    if not verify_password(password, user.password_hash):
        return False  # Slow: bcrypt comparison

    return True

# Attacker can enumerate valid usernames:
# Invalid username: 1ms response
# Valid username: 500ms response (bcrypt)

Cache-Based Leaks

AES implementations may have timing variations:
- Table lookups depend on cache state
- Different key bytes = different cache patterns
- Measurable from same machine or VM

Research has demonstrated:
- Extracting AES keys from co-located VMs
- Cross-process key extraction
- JavaScript cache timing attacks

Your "encrypted" data's key leaked through CPU cache timing.

Network-Based Leaks

HTTPS protects content, not metadata:

Observable by network:
- Request timing (when you access data)
- Request size (roughly what you access)
- Request frequency (how often you check)
- Endpoints accessed (which features you use)

Traffic analysis can reveal:
- What websites you visit (by packet sizes)
- What you're typing (by keystroke timing)
- What you're watching (by bandwidth patterns)

7. Architectural Failures

Case 1: Client-Side โ€œSecurityโ€

// "Encrypted" password storage in browser
function savePassword(password) {
    const encrypted = btoa(password);  // This is base64, not encryption!
    localStorage.setItem('password', encrypted);
}

// Even with real encryption:
const key = 'hardcoded_key_in_js';  // Visible in source
const encrypted = CryptoJS.AES.encrypt(password, key);
// Anyone can read the source and decrypt

Case 2: Security Through Obscurity

Real example from IoT device:
- Communication "encrypted" with XOR
- Key was the string "security"
- Repeated for longer messages

"Nobody will reverse engineer our protocol"
Narrator: It took 15 minutes.

Case 3: Encrypt-Then-Authenticate vs Authenticate-Then-Encrypt

Wrong order (vulnerable to padding oracle):
1. Encrypt plaintext
2. Compute MAC of ciphertext
3. Attacker modifies ciphertext
4. Server decrypts first, checks MAC second
5. Decryption errors leak information!

Right order (or use AEAD):
1. Compute MAC
2. Encrypt (plaintext + MAC)
3. Attacker modifications detected before decryption

Or just use AES-GCM which handles this correctly.

Case 4: The Confused Deputy

# Server correctly encrypts data per-user
def get_document(user_id, doc_id):
    doc = database.get_document(doc_id)
    # Decrypt with user's key
    key = get_user_key(user_id)
    return decrypt(doc.encrypted_content, key)

# But authorization check is wrong!
def get_document(user_id, doc_id):
    doc = database.get_document(doc_id)
    # Forgot to check if user_id owns doc_id!
    key = get_user_key(user_id)  # Gets WRONG user's key
    return decrypt(doc.encrypted_content, key)  # Decryption fails... or worse

# What if attacker replaces encrypted content with their own?
# What if keys are accidentally shared?

8. Real-World Breach Examples

Capital One (2019)

What they had:
- AWS encryption at rest
- AWS encryption in transit
- Proper key management

What went wrong:
- SSRF vulnerability in WAF
- Attacker accessed instance metadata
- Got IAM credentials
- Used credentials to access S3
- Downloaded 100 million customer records

The encryption was irrelevant.
Access control failure = breach.

Equifax (2017)

What they had:
- Encrypted databases
- Security team
- Compliance certifications

What went wrong:
- Unpatched Apache Struts (CVE known for months)
- Attackers got shell access
- Accessed data through application (which has decryption access)
- 147 million records exposed

The encryption was irrelevant.
The application was the authorized accessor.

Adobe (2013)

What they had:
- Encrypted passwords (but with ECB mode)
- Same key for all passwords

What went wrong:
- Database breach (separate vulnerability)
- ECB mode: same password = same ciphertext
- Password hints stored in plaintext
- Cross-reference hints with ciphertext patterns

User 1: Ciphertext ABC, Hint: "my cat's name"
User 2: Ciphertext ABC, Hint: "rhymes with fiskers"
User 3: Ciphertext ABC, Hint: "whiskers"

Attackers decrypted millions without breaking encryption.

9. What Actually Works

Defense in Depth

Layer 1: Network security
- Firewalls, segmentation, WAF
- Stops network-based attacks

Layer 2: Authentication & Authorization
- Strong authentication (MFA)
- Principle of least privilege
- Stops unauthorized access

Layer 3: Application security
- Input validation
- Secure coding practices
- Stops application-level attacks

Layer 4: Data security
- Encryption at rest and in transit
- Key management
- Stops data theft if other layers fail

Layer 5: Monitoring & Response
- Logging, alerting, incident response
- Detects and contains breaches

Each layer catches what others miss.

Practical Checklist

Before deployment, verify:

[ ] Secrets not in source code or logs
[ ] Encryption keys rotated regularly
[ ] Access controls tested (try to bypass them!)
[ ] Backups encrypted with different keys
[ ] Debug endpoints removed
[ ] Third-party integrations secured
[ ] Monitoring and alerting configured
[ ] Incident response plan exists
[ ] All team members trained on security
[ ] Regular security audits scheduled

10. Summary

Three things to remember:

  1. Encryption protects data in one state. Data exists before encryption, after decryption, in memory, in logs, in backups, in error messages. Encryption only protects the encrypted form.

  2. Access control failures bypass encryption. If attackers can access data through your application (which must decrypt it to use it), your encryption is irrelevant. Most breaches are access control failures, not cryptographic breaks.

  3. Security is a system property, not a feature. You canโ€™t add encryption and be โ€œdoneโ€ with security. Security requires ongoing attention to operations, monitoring, access control, and human factors.

11. Whatโ€™s Next

You understand why encryption alone isnโ€™t enough. But how do you think like a security engineer? How do you build systems that resist attack?

In the next article: Building Security Judgmentโ€”thinking like an attacker, threat modeling, and knowing when โ€œgood enoughโ€ really is good enough.