๐Ÿ”ฅ Advanced

Cryptography Best Practices

Risk Severity
๐Ÿ”ด Critical
Fix Effort
๐Ÿ”ง Medium
Est. Time
โฑ๏ธ 4-8 hours
Reference
A02:2021 CWE-327, CWE-326

Using weak or deprecated cryptographic algorithms, poor key management, or predictable random number generators can compromise all your security controls. Always use modern, well-vetted cryptographic libraries.

Don't Roll Your Own Crypto

NEVER implement your own cryptographic algorithms. Even experts make mistakes. Use established libraries like libsodium, OpenSSL, or language-specific crypto APIs.

Cryptographic Decision Tree

flowchart LR A[Need Crypto?] --> B{At Rest?} --> E[AES-256-GCM] --> H[KMS Key] A --> C{In Transit?} --> F[TLS 1.3] --> I[ECDSA Certs] A --> D{Passwords?} --> G[Argon2id] --> J[Per-user Salt]

๐Ÿ“‘ Table of Contents

Deprecated Algorithms (Never Use)

โŒ Hashing

  • โ€ข MD5: Broken (collisions)
  • โ€ข SHA1: Deprecated (2017)
  • โ€ข MD4: Severely broken
  • โ€ข CRC32: Not cryptographic

โŒ Encryption

  • โ€ข DES/3DES: 56-bit keys too small
  • โ€ข RC4: Stream cipher broken
  • โ€ข ECB mode: Patterns leak
  • โ€ข RSA < 2048: Too weak

โŒ Other

  • โ€ข Custom encoding: Not encryption
  • โ€ข XOR cipher: Trivially broken
  • โ€ข Random() for keys: Predictable
  • โ€ข TLS 1.0/1.1: Deprecated

Modern Symmetric Encryption

Python: AES-GCM (Recommended)

python
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
import os

# โŒ VULNERABLE: Weak encryption
import base64
from Crypto.Cipher import DES  # DES is broken!

def weak_encrypt(data, key):
    cipher = DES.new(key, DES.MODE_ECB)  # ECB leaks patterns!
    return base64.b64encode(cipher.encrypt(data))

# โœ… SECURE: AES-GCM provides encryption + authentication
def secure_encrypt(plaintext: bytes, key: bytes = None) -> tuple:
    """
    Encrypt with AES-256-GCM (authenticated encryption)
    Returns: (ciphertext, nonce, tag)
    """
    if key is None:
        key = AESGCM.generate_key(bit_length=256)  # 256-bit key
    
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)  # 96-bit nonce (NEVER reuse!)
    
    # GCM mode provides both encryption and authentication
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)
    
    return ciphertext, nonce, key

def secure_decrypt(ciphertext: bytes, nonce: bytes, key: bytes) -> bytes:
    """Decrypt and verify authentication tag"""
    aesgcm = AESGCM(key)
    
    try:
        plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data=None)
        return plaintext
    except Exception:
        raise ValueError("Decryption failed - data tampered or wrong key")

# Example usage
plaintext = b"Sensitive data to encrypt"
ciphertext, nonce, key = secure_encrypt(plaintext)

# Store: ciphertext + nonce (key stored separately!)
decrypted = secure_decrypt(ciphertext, nonce, key)
assert decrypted == plaintext

# โœ… SECURE: Using associated data (AEAD)
def encrypt_with_metadata(plaintext: bytes, metadata: bytes, key: bytes) -> tuple:
    """
    Authenticated encryption with associated data (AEAD)
    metadata is authenticated but not encrypted
    """
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=metadata)
    return ciphertext, nonce
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
import os

# โŒ VULNERABLE: Weak encryption
import base64
from Crypto.Cipher import DES  # DES is broken!

def weak_encrypt(data, key):
    cipher = DES.new(key, DES.MODE_ECB)  # ECB leaks patterns!
    return base64.b64encode(cipher.encrypt(data))

# โœ… SECURE: AES-GCM provides encryption + authentication
def secure_encrypt(plaintext: bytes, key: bytes = None) -> tuple:
    """
    Encrypt with AES-256-GCM (authenticated encryption)
    Returns: (ciphertext, nonce, tag)
    """
    if key is None:
        key = AESGCM.generate_key(bit_length=256)  # 256-bit key
    
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)  # 96-bit nonce (NEVER reuse!)
    
    # GCM mode provides both encryption and authentication
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)
    
    return ciphertext, nonce, key

def secure_decrypt(ciphertext: bytes, nonce: bytes, key: bytes) -> bytes:
    """Decrypt and verify authentication tag"""
    aesgcm = AESGCM(key)
    
    try:
        plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data=None)
        return plaintext
    except Exception:
        raise ValueError("Decryption failed - data tampered or wrong key")

# Example usage
plaintext = b"Sensitive data to encrypt"
ciphertext, nonce, key = secure_encrypt(plaintext)

# Store: ciphertext + nonce (key stored separately!)
decrypted = secure_decrypt(ciphertext, nonce, key)
assert decrypted == plaintext

# โœ… SECURE: Using associated data (AEAD)
def encrypt_with_metadata(plaintext: bytes, metadata: bytes, key: bytes) -> tuple:
    """
    Authenticated encryption with associated data (AEAD)
    metadata is authenticated but not encrypted
    """
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=metadata)
    return ciphertext, nonce

Node.js: AES-GCM

javascript
const crypto = require('crypto');

// โŒ VULNERABLE: Weak encryption
function weakEncrypt(text, password) {
    const cipher = crypto.createCipher('des', password);  // DES is broken!
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
}

// โœ… SECURE: AES-256-GCM with proper key derivation
function secureEncrypt(plaintext, password) {
    // Derive key from password using PBKDF2
    const salt = crypto.randomBytes(16);
    const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
    
    // Generate random IV (nonce)
    const iv = crypto.randomBytes(12);
    
    // Create cipher
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    
    // Encrypt
    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    // Get authentication tag
    const authTag = cipher.getAuthTag();
    
    // Return all components needed for decryption
    return {
        ciphertext: encrypted,
        iv: iv.toString('hex'),
        authTag: authTag.toString('hex'),
        salt: salt.toString('hex')
    };
}

function secureDecrypt(encrypted, password) {
    // Derive same key from password
    const salt = Buffer.from(encrypted.salt, 'hex');
    const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
    
    // Create decipher
    const iv = Buffer.from(encrypted.iv, 'hex');
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    
    // Set authentication tag
    const authTag = Buffer.from(encrypted.authTag, 'hex');
    decipher.setAuthTag(authTag);
    
    // Decrypt
    let decrypted = decipher.update(encrypted.ciphertext, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
}

// Example usage
const data = secureEncrypt('Sensitive information', 'strong-password');
const decrypted = secureDecrypt(data, 'strong-password');

// โœ… SECURE: Using Web Crypto API (browser)
async function browserEncrypt(plaintext, password) {
    const enc = new TextEncoder();
    
    // Derive key from password
    const keyMaterial = await crypto.subtle.importKey(
        'raw',
        enc.encode(password),
        'PBKDF2',
        false,
        ['deriveKey']
    );
    
    const salt = crypto.getRandomValues(new Uint8Array(16));
    
    const key = await crypto.subtle.deriveKey(
        {
            name: 'PBKDF2',
            salt: salt,
            iterations: 100000,
            hash: 'SHA-256'
        },
        keyMaterial,
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt']
    );
    
    const iv = crypto.getRandomValues(new Uint8Array(12));
    
    const ciphertext = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv: iv },
        key,
        enc.encode(plaintext)
    );
    
    return { ciphertext, iv, salt };
}
const crypto = require('crypto');

// โŒ VULNERABLE: Weak encryption
function weakEncrypt(text, password) {
    const cipher = crypto.createCipher('des', password);  // DES is broken!
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
}

// โœ… SECURE: AES-256-GCM with proper key derivation
function secureEncrypt(plaintext, password) {
    // Derive key from password using PBKDF2
    const salt = crypto.randomBytes(16);
    const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
    
    // Generate random IV (nonce)
    const iv = crypto.randomBytes(12);
    
    // Create cipher
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    
    // Encrypt
    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    // Get authentication tag
    const authTag = cipher.getAuthTag();
    
    // Return all components needed for decryption
    return {
        ciphertext: encrypted,
        iv: iv.toString('hex'),
        authTag: authTag.toString('hex'),
        salt: salt.toString('hex')
    };
}

function secureDecrypt(encrypted, password) {
    // Derive same key from password
    const salt = Buffer.from(encrypted.salt, 'hex');
    const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
    
    // Create decipher
    const iv = Buffer.from(encrypted.iv, 'hex');
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    
    // Set authentication tag
    const authTag = Buffer.from(encrypted.authTag, 'hex');
    decipher.setAuthTag(authTag);
    
    // Decrypt
    let decrypted = decipher.update(encrypted.ciphertext, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
}

// Example usage
const data = secureEncrypt('Sensitive information', 'strong-password');
const decrypted = secureDecrypt(data, 'strong-password');

// โœ… SECURE: Using Web Crypto API (browser)
async function browserEncrypt(plaintext, password) {
    const enc = new TextEncoder();
    
    // Derive key from password
    const keyMaterial = await crypto.subtle.importKey(
        'raw',
        enc.encode(password),
        'PBKDF2',
        false,
        ['deriveKey']
    );
    
    const salt = crypto.getRandomValues(new Uint8Array(16));
    
    const key = await crypto.subtle.deriveKey(
        {
            name: 'PBKDF2',
            salt: salt,
            iterations: 100000,
            hash: 'SHA-256'
        },
        keyMaterial,
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt']
    );
    
    const iv = crypto.getRandomValues(new Uint8Array(12));
    
    const ciphertext = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv: iv },
        key,
        enc.encode(plaintext)
    );
    
    return { ciphertext, iv, salt };
}

Password Hashing

Never Store Plaintext Passwords

Always hash passwords with a purpose-built algorithm (Argon2, bcrypt, scrypt). Never use fast hashes like SHA-256 for passwords!

Python: Argon2 (Best Choice)

python
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import hashlib

# โŒ VULNERABLE: Using SHA-256 for passwords
def weak_password_hash(password):
    return hashlib.sha256(password.encode()).hexdigest()
    # Fast = easy to brute force (billions of hashes/second)

# โŒ VULNERABLE: No salt
passwords = {}
def store_password(username, password):
    passwords[username] = hashlib.sha256(password.encode()).hexdigest()
    # Rainbow tables can crack this!

# โœ… SECURE: Argon2id (winner of password hashing competition)
ph = PasswordHasher(
    time_cost=2,        # Number of iterations
    memory_cost=65536,  # 64 MB of RAM
    parallelism=4,      # Number of threads
    hash_len=32,        # Output length
    salt_len=16         # Salt length
)

def hash_password(password: str) -> str:
    """Hash password with Argon2id"""
    return ph.hash(password)

def verify_password(password: str, hash: str) -> bool:
    """Verify password against hash"""
    try:
        ph.verify(hash, password)
        
        # Check if rehashing is needed (algorithm updated)
        if ph.check_needs_rehash(hash):
            return "rehash_needed"
        
        return True
    except VerifyMismatchError:
        return False

# Example usage
password = "user_password_123"
hash = hash_password(password)
# Output: $argon2id$v=19$m=65536,t=2,p=4$randomsalt$randomhash

# Verify
is_valid = verify_password(password, hash)
assert is_valid == True

# โœ… SECURE: bcrypt (also good, older but proven)
import bcrypt

def bcrypt_hash(password: str) -> bytes:
    """Hash with bcrypt (work factor 12)"""
    salt = bcrypt.gensalt(rounds=12)  # 2^12 iterations
    return bcrypt.hashpw(password.encode(), salt)

def bcrypt_verify(password: str, hash: bytes) -> bool:
    """Verify bcrypt hash"""
    return bcrypt.checkpw(password.encode(), hash)
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import hashlib

# โŒ VULNERABLE: Using SHA-256 for passwords
def weak_password_hash(password):
    return hashlib.sha256(password.encode()).hexdigest()
    # Fast = easy to brute force (billions of hashes/second)

# โŒ VULNERABLE: No salt
passwords = {}
def store_password(username, password):
    passwords[username] = hashlib.sha256(password.encode()).hexdigest()
    # Rainbow tables can crack this!

# โœ… SECURE: Argon2id (winner of password hashing competition)
ph = PasswordHasher(
    time_cost=2,        # Number of iterations
    memory_cost=65536,  # 64 MB of RAM
    parallelism=4,      # Number of threads
    hash_len=32,        # Output length
    salt_len=16         # Salt length
)

def hash_password(password: str) -> str:
    """Hash password with Argon2id"""
    return ph.hash(password)

def verify_password(password: str, hash: str) -> bool:
    """Verify password against hash"""
    try:
        ph.verify(hash, password)
        
        # Check if rehashing is needed (algorithm updated)
        if ph.check_needs_rehash(hash):
            return "rehash_needed"
        
        return True
    except VerifyMismatchError:
        return False

# Example usage
password = "user_password_123"
hash = hash_password(password)
# Output: $argon2id$v=19$m=65536,t=2,p=4$randomsalt$randomhash

# Verify
is_valid = verify_password(password, hash)
assert is_valid == True

# โœ… SECURE: bcrypt (also good, older but proven)
import bcrypt

def bcrypt_hash(password: str) -> bytes:
    """Hash with bcrypt (work factor 12)"""
    salt = bcrypt.gensalt(rounds=12)  # 2^12 iterations
    return bcrypt.hashpw(password.encode(), salt)

def bcrypt_verify(password: str, hash: bytes) -> bool:
    """Verify bcrypt hash"""
    return bcrypt.checkpw(password.encode(), hash)

Java: BCrypt

java
import org.mindrot.jbcrypt.BCrypt;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;

public class PasswordHashing {
    
    // โŒ VULNERABLE: Using SHA-256
    public static String weakHash(String password) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
        // No salt, fast = vulnerable to rainbow tables
    }
    
    // โœ… SECURE: BCrypt with salt
    public static String hashPassword(String password) {
        // Automatically generates salt and uses 2^10 rounds
        return BCrypt.hashpw(password, BCrypt.gensalt(12));
        // Output: $2a$12$randomsalt$randomhash
    }
    
    public static boolean verifyPassword(String password, String hash) {
        try {
            return BCrypt.checkpw(password, hash);
        } catch (Exception e) {
            return false;
        }
    }
    
    // Example usage
    public static void main(String[] args) {
        String password = "user_password_123";
        
        // Hash password
        String hash = hashPassword(password);
        System.out.println("Hash: " + hash);
        
        // Verify password
        boolean isValid = verifyPassword(password, hash);
        System.out.println("Valid: " + isValid);
        
        // Wrong password
        boolean isInvalid = verifyPassword("wrong", hash);
        System.out.println("Invalid: " + isInvalid);
    }
}
import org.mindrot.jbcrypt.BCrypt;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;

public class PasswordHashing {
    
    // โŒ VULNERABLE: Using SHA-256
    public static String weakHash(String password) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
        // No salt, fast = vulnerable to rainbow tables
    }
    
    // โœ… SECURE: BCrypt with salt
    public static String hashPassword(String password) {
        // Automatically generates salt and uses 2^10 rounds
        return BCrypt.hashpw(password, BCrypt.gensalt(12));
        // Output: $2a$12$randomsalt$randomhash
    }
    
    public static boolean verifyPassword(String password, String hash) {
        try {
            return BCrypt.checkpw(password, hash);
        } catch (Exception e) {
            return false;
        }
    }
    
    // Example usage
    public static void main(String[] args) {
        String password = "user_password_123";
        
        // Hash password
        String hash = hashPassword(password);
        System.out.println("Hash: " + hash);
        
        // Verify password
        boolean isValid = verifyPassword(password, hash);
        System.out.println("Valid: " + isValid);
        
        // Wrong password
        boolean isInvalid = verifyPassword("wrong", hash);
        System.out.println("Invalid: " + isInvalid);
    }
}

Secure Key Management

Key Storage Rules

  • โœ… Use environment variables or secrets managers (AWS KMS, Azure Key Vault, HashiCorp Vault)
  • โœ… Never commit keys to git - add to .gitignore
  • โœ… Rotate keys regularly (every 90 days)
  • โœ… Use different keys per environment (dev, staging, prod)
  • โŒ NEVER hardcode keys in source
  • โŒ NEVER derive keys from passwords directly (use PBKDF2/Argon2)

Environment Variables (Basic)

bash
# .env file (NEVER commit to git!)
ENCRYPTION_KEY=hex:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
DATABASE_ENCRYPTION_KEY=base64:YW5vdGhlcnNlY3VyZWtleWhlcmU=

# Python: Load from environment
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def get_encryption_key():
    key_hex = os.environ.get('ENCRYPTION_KEY')
    if not key_hex:
        raise ValueError("ENCRYPTION_KEY not set")
    
    # Remove 'hex:' prefix and decode
    key_hex = key_hex.replace('hex:', '')
    return bytes.fromhex(key_hex)

# Node.js: Load from environment
require('dotenv').config();

function getEncryptionKey() {
    const keyHex = process.env.ENCRYPTION_KEY;
    if (!keyHex) {
        throw new Error('ENCRYPTION_KEY not set');
    }
    return Buffer.from(keyHex.replace('hex:', ''), 'hex');
}
# .env file (NEVER commit to git!)
ENCRYPTION_KEY=hex:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
DATABASE_ENCRYPTION_KEY=base64:YW5vdGhlcnNlY3VyZWtleWhlcmU=

# Python: Load from environment
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def get_encryption_key():
    key_hex = os.environ.get('ENCRYPTION_KEY')
    if not key_hex:
        raise ValueError("ENCRYPTION_KEY not set")
    
    # Remove 'hex:' prefix and decode
    key_hex = key_hex.replace('hex:', '')
    return bytes.fromhex(key_hex)

# Node.js: Load from environment
require('dotenv').config();

function getEncryptionKey() {
    const keyHex = process.env.ENCRYPTION_KEY;
    if (!keyHex) {
        throw new Error('ENCRYPTION_KEY not set');
    }
    return Buffer.from(keyHex.replace('hex:', ''), 'hex');
}

AWS KMS (Production)

python
import boto3
import base64

# โœ… SECURE: Use AWS KMS for key management
kms = boto3.client('kms', region_name='us-east-1')

def encrypt_with_kms(plaintext: str, key_id: str) -> dict:
    """Encrypt data using AWS KMS"""
    response = kms.encrypt(
        KeyId=key_id,
        Plaintext=plaintext.encode()
    )
    
    return {
        'ciphertext': base64.b64encode(response['CiphertextBlob']).decode(),
        'key_id': response['KeyId']
    }

def decrypt_with_kms(ciphertext: str) -> str:
    """Decrypt data using AWS KMS"""
    ciphertext_blob = base64.b64decode(ciphertext)
    
    response = kms.decrypt(
        CiphertextBlob=ciphertext_blob
    )
    
    return response['Plaintext'].decode()

# Example
encrypted = encrypt_with_kms(
    "Sensitive data",
    key_id="arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
)

decrypted = decrypt_with_kms(encrypted['ciphertext'])
import boto3
import base64

# โœ… SECURE: Use AWS KMS for key management
kms = boto3.client('kms', region_name='us-east-1')

def encrypt_with_kms(plaintext: str, key_id: str) -> dict:
    """Encrypt data using AWS KMS"""
    response = kms.encrypt(
        KeyId=key_id,
        Plaintext=plaintext.encode()
    )
    
    return {
        'ciphertext': base64.b64encode(response['CiphertextBlob']).decode(),
        'key_id': response['KeyId']
    }

def decrypt_with_kms(ciphertext: str) -> str:
    """Decrypt data using AWS KMS"""
    ciphertext_blob = base64.b64decode(ciphertext)
    
    response = kms.decrypt(
        CiphertextBlob=ciphertext_blob
    )
    
    return response['Plaintext'].decode()

# Example
encrypted = encrypt_with_kms(
    "Sensitive data",
    key_id="arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
)

decrypted = decrypt_with_kms(encrypted['ciphertext'])

Cryptographically Secure Random Numbers

Never Use Predictable RNGs

Standard random() functions are NOT cryptographically secure. Use secrets or os.urandom().
python
import random
import secrets
import os

# โŒ VULNERABLE: Predictable random
session_token = ''.join(random.choices('0123456789', k=16))
# Attacker can predict next values if they know seed!

# โŒ VULNERABLE: Timestamp-based
import time
token = int(time.time())  # Predictable!

# โœ… SECURE: Use secrets module (Python 3.6+)
session_token = secrets.token_hex(32)  # 32 bytes = 64 hex chars
session_token_url = secrets.token_urlsafe(32)  # URL-safe
session_token_bytes = secrets.token_bytes(32)  # Raw bytes

# โœ… SECURE: Use os.urandom (all Python versions)
token = os.urandom(32)  # 32 random bytes
token_hex = token.hex()

# โœ… SECURE: Generate secure passwords
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for i in range(20))

# โœ… SECURE: Generate cryptographic keys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)

# Node.js equivalents
// โŒ VULNERABLE
const token = Math.random().toString(36);

// โœ… SECURE
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const token_base64 = crypto.randomBytes(32).toString('base64');
import random
import secrets
import os

# โŒ VULNERABLE: Predictable random
session_token = ''.join(random.choices('0123456789', k=16))
# Attacker can predict next values if they know seed!

# โŒ VULNERABLE: Timestamp-based
import time
token = int(time.time())  # Predictable!

# โœ… SECURE: Use secrets module (Python 3.6+)
session_token = secrets.token_hex(32)  # 32 bytes = 64 hex chars
session_token_url = secrets.token_urlsafe(32)  # URL-safe
session_token_bytes = secrets.token_bytes(32)  # Raw bytes

# โœ… SECURE: Use os.urandom (all Python versions)
token = os.urandom(32)  # 32 random bytes
token_hex = token.hex()

# โœ… SECURE: Generate secure passwords
import string

alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for i in range(20))

# โœ… SECURE: Generate cryptographic keys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)

# Node.js equivalents
// โŒ VULNERABLE
const token = Math.random().toString(36);

// โœ… SECURE
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const token_base64 = crypto.randomBytes(32).toString('base64');

TLS/SSL Configuration

nginx
# โŒ VULNERABLE: TLS 1.0/1.1, weak ciphers
server {
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2;
}

# โœ… SECURE: TLS 1.2+ only, strong ciphers (nginx)
server {
    listen 443 ssl http2;
    
    # Use TLS 1.2 and 1.3 only
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Strong cipher suites (Mozilla Modern compatibility)
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers on;
    
    # Strong DH parameters
    ssl_dhparam /etc/nginx/dhparam.pem;
    
    # HSTS (force HTTPS for 1 year)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    
    # Session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
}

# Python: Enforce TLS 1.2+
import ssl
import urllib.request

# โœ… Create secure SSL context
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_3

# Use with requests
import requests
response = requests.get('https://example.com', verify=True)

# Node.js: Enforce TLS 1.2+
const https = require('https');
const tls = require('tls');

const options = {
    minVersion: 'TLSv1.2',
    maxVersion: 'TLSv1.3',
    ciphers: tls.DEFAULT_CIPHERS
};

https.get('https://example.com', options, (res) => {
    // Handle response
});
# โŒ VULNERABLE: TLS 1.0/1.1, weak ciphers
server {
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2;
}

# โœ… SECURE: TLS 1.2+ only, strong ciphers (nginx)
server {
    listen 443 ssl http2;
    
    # Use TLS 1.2 and 1.3 only
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Strong cipher suites (Mozilla Modern compatibility)
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers on;
    
    # Strong DH parameters
    ssl_dhparam /etc/nginx/dhparam.pem;
    
    # HSTS (force HTTPS for 1 year)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    
    # Session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
}

# Python: Enforce TLS 1.2+
import ssl
import urllib.request

# โœ… Create secure SSL context
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_3

# Use with requests
import requests
response = requests.get('https://example.com', verify=True)

# Node.js: Enforce TLS 1.2+
const https = require('https');
const tls = require('tls');

const options = {
    minVersion: 'TLSv1.2',
    maxVersion: 'TLSv1.3',
    ciphers: tls.DEFAULT_CIPHERS
};

https.get('https://example.com', options, (res) => {
    // Handle response
});

Testing Verification

Test TLS Configuration

bash
# Test SSL/TLS with sslyze
pip install sslyze
sslyze --regular example.com:443

# Test with testssl.sh (comprehensive)
git clone https://github.com/drwetter/testssl.sh
cd testssl.sh
./testssl.sh https://example.com

# Check certificate
openssl s_client -connect example.com:443 -servername example.com

# Online scanners
# https://www.ssllabs.com/ssltest/
# https://observatory.mozilla.org/
# Test SSL/TLS with sslyze
pip install sslyze
sslyze --regular example.com:443

# Test with testssl.sh (comprehensive)
git clone https://github.com/drwetter/testssl.sh
cd testssl.sh
./testssl.sh https://example.com

# Check certificate
openssl s_client -connect example.com:443 -servername example.com

# Online scanners
# https://www.ssllabs.com/ssltest/
# https://observatory.mozilla.org/

Detect Weak Crypto in Code

bash
# Semgrep rules for crypto issues
semgrep --config=p/security-audit --config=p/secrets .

# Specific crypto checks
semgrep --config "r/python.cryptography" .

# Bandit (Python security linter)
pip install bandit
bandit -r . -f json -o bandit-report.json

# Checks for:
# - Use of MD5/SHA1
# - Weak random number generators
# - Hardcoded passwords/keys
# - Insecure SSL/TLS configurations
# Semgrep rules for crypto issues
semgrep --config=p/security-audit --config=p/secrets .

# Specific crypto checks
semgrep --config "r/python.cryptography" .

# Bandit (Python security linter)
pip install bandit
bandit -r . -f json -o bandit-report.json

# Checks for:
# - Use of MD5/SHA1
# - Weak random number generators
# - Hardcoded passwords/keys
# - Insecure SSL/TLS configurations

Common Mistakes

โŒ Using ECB Mode

ECB (Electronic Codebook) mode encrypts each block independently, leaking patterns.

python
# โŒ WRONG - ECB leaks patterns (see "ECB Penguin")
cipher = AES.new(key, AES.MODE_ECB)

# โœ… CORRECT - Use GCM, CTR, or CBC with authentication
cipher = AESGCM(key)
# โŒ WRONG - ECB leaks patterns (see "ECB Penguin")
cipher = AES.new(key, AES.MODE_ECB)

# โœ… CORRECT - Use GCM, CTR, or CBC with authentication
cipher = AESGCM(key)

โŒ Reusing IVs/Nonces

NEVER reuse an IV/nonce with the same key - it breaks encryption!

python
# โŒ WRONG - hardcoded IV
iv = b'0' * 16
cipher = AES.new(key, AES.MODE_CBC, iv)

# โœ… CORRECT - random IV each time
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
# โŒ WRONG - hardcoded IV
iv = b'0' * 16
cipher = AES.new(key, AES.MODE_CBC, iv)

# โœ… CORRECT - random IV each time
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)

โŒ Not Authenticating Ciphertext

Always use authenticated encryption (GCM, ChaCha20-Poly1305) to prevent tampering.

python
# โŒ WRONG - no authentication (padding oracle attacks!)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext, 16))

# โœ… CORRECT - authenticated encryption
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# โŒ WRONG - no authentication (padding oracle attacks!)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext, 16))

# โœ… CORRECT - authenticated encryption
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
๐ŸŽฏ

Cryptography Labs

Practice identifying and exploiting cryptographic weaknesses.

๐Ÿ”ถ
Crypto Challenges PortSwigger hard
Block cipher attacksStream cipher reusePadding oracle
Open Lab
๐Ÿ”ถ
JWT Vulnerabilities PortSwigger medium
Algorithm confusionKey injectionNone algorithm
Open Lab