Files
Rodin 8a94a08511 Add supply-chain, deserialization, cryptography, error-handling patterns
Now covers all OWASP Top 10:2025 categories:
- A03: supply-chain.md (SolarWinds, Bybit, npm worm examples)
- A04: cryptography.md (algorithm recommendations, key management)
- A08: deserialization.md (pickle, yaml, language-specific risks)
- A10: error-handling.md (fail closed, error messages)
2026-05-10 22:48:39 -07:00

3.8 KiB

Cryptographic Failures

Rule

Use strong, modern algorithms. Never implement your own crypto. Manage keys securely.

Source: OWASP Top 10 2025 - A04 Cryptographic Failures

Algorithms to Use

Purpose Recommended Avoid
Symmetric encryption AES-256-GCM DES, 3DES, RC4, ECB mode
Hashing (general) SHA-256, SHA-3 MD5, SHA-1
Password hashing bcrypt, Argon2, scrypt SHA-*, MD5, plain hash
Key exchange ECDH, X25519 RSA < 2048 bits
Signatures Ed25519, ECDSA RSA < 2048 bits
TLS 1.2+ SSL, TLS 1.0, 1.1

Correct Pattern

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import os
import base64

# Generate a secure key
def generate_key() -> bytes:
    return Fernet.generate_key()

# Encrypt data
def encrypt(data: bytes, key: bytes) -> bytes:
    f = Fernet(key)
    return f.encrypt(data)

# Decrypt data
def decrypt(ciphertext: bytes, key: bytes) -> bytes:
    f = Fernet(key)
    return f.decrypt(ciphertext)

# Derive key from password (for encryption, not storage)
def derive_key(password: str, salt: bytes) -> bytes:
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=600000,  # OWASP 2023 recommendation
    )
    return base64.urlsafe_b64encode(kdf.derive(password.encode()))

# Generate secure random values
def generate_token(length: int = 32) -> str:
    return base64.urlsafe_b64encode(os.urandom(length)).decode()

Incorrect Pattern

import hashlib
import random

# Wrong: MD5 for anything security-related
hash = hashlib.md5(data).hexdigest()

# Wrong: SHA-256 for passwords (no salt, too fast)
password_hash = hashlib.sha256(password.encode()).hexdigest()

# Wrong: predictable random
token = random.randint(0, 999999)  # Not cryptographically secure

# Wrong: hardcoded key
KEY = b"mysecretkey12345"

# Wrong: ECB mode (patterns visible in ciphertext)
from Crypto.Cipher import AES
cipher = AES.new(key, AES.MODE_ECB)

# Wrong: rolling your own crypto
def my_encrypt(data, key):
    return bytes(a ^ b for a, b in zip(data, cycle(key)))

Key Management

import os

# Load keys from environment or secret manager
def get_encryption_key() -> bytes:
    key = os.environ.get("ENCRYPTION_KEY")
    if not key:
        raise RuntimeError("ENCRYPTION_KEY not set")
    return base64.urlsafe_b64decode(key)

# Key rotation
class KeyManager:
    def __init__(self):
        self.current_key_id = os.environ["CURRENT_KEY_ID"]
        self.keys = self._load_keys()
    
    def encrypt(self, data: bytes) -> dict:
        key = self.keys[self.current_key_id]
        ciphertext = encrypt(data, key)
        return {"key_id": self.current_key_id, "data": ciphertext}
    
    def decrypt(self, envelope: dict) -> bytes:
        key = self.keys[envelope["key_id"]]
        return decrypt(envelope["data"], key)

TLS Configuration

import ssl

# Correct: modern TLS settings
def create_ssl_context() -> ssl.SSLContext:
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.minimum_version = ssl.TLSVersion.TLSv1_2
    context.verify_mode = ssl.CERT_REQUIRED
    context.check_hostname = True
    context.load_default_certs()
    return context

# Wrong: disabling verification
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE  # Never do this!

Edge Cases

  • IV/nonce reuse breaks encryption security
  • Timing attacks on comparison operations
  • Side-channel attacks on key operations
  • Key material in swap/core dumps
  • Encrypted data without integrity (use AEAD)
  • Insufficient entropy at startup