# Cryptographic Failures ## Rule Use strong, modern algorithms. Never implement your own crypto. Manage keys securely. **Source:** [OWASP Top 10 2025 - A04 Cryptographic Failures](https://owasp.org/Top10/2025/A04_2025-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 ```python 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 ```python 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 ```python 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 ```python 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