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)
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user