Files
claude-skills-reference/engineering-team/senior-security/references/cryptography-implementation.md
Alireza Rezvani 5e1f6955e8 fix(skill): rewrite senior-security with real security engineering content (#87) (#168)
PROBLEM: Issue #87 feedback - senior-security scored 40/100
- Placeholder reference files with template content only
- Generic scripts without actual security functionality
- Missing trigger phrases, no TOC, no validation workflows

SOLUTION: Complete rewrite with comprehensive security content

SKILL.md (210→436 lines):
- Added 12 triggers: security architecture, threat modeling, STRIDE analysis,
  penetration testing, vulnerability assessment, secure coding, OWASP, etc.
- Added TOC with 7 sections
- 5 numbered workflows with validation checkpoints:
  1. Threat Modeling (STRIDE methodology)
  2. Security Architecture (Zero Trust, Defense-in-Depth)
  3. Vulnerability Assessment (OWASP Top 10 mapping)
  4. Secure Code Review (checklist with risk categories)
  5. Incident Response (severity levels, response phases)
- Added STRIDE per element matrix, DREAD scoring, severity matrices
- Authentication pattern selection guide
- Security tools reference (SAST, DAST, dependency scanning)
- Cryptographic algorithm selection guide
- Compliance frameworks reference (OWASP ASVS, CIS, NIST, PCI-DSS)
- Security headers checklist

References (rebuilt from scratch):
- security-architecture-patterns.md (~615 lines): Zero Trust implementation,
  Defense-in-Depth layers, OAuth 2.0 + PKCE flows, JWT patterns, API security
- threat-modeling-guide.md (~518 lines): STRIDE framework with element matrix,
  attack trees with probability calculations, DREAD scoring, DFD creation
- cryptography-implementation.md (~775 lines): AES-256-GCM, ChaCha20-Poly1305,
  envelope encryption, RSA, Ed25519 signatures, X25519 key exchange, Argon2id
  password hashing, key management strategies

Scripts (rebuilt with real functionality):
- threat_modeler.py (~675 lines): Interactive STRIDE analysis for any system
  component, DREAD risk scoring, comprehensive threat database with 70+ threats,
  mitigation recommendations, JSON/text output
- secret_scanner.py (~516 lines): Detects AWS/GCP/Azure credentials, GitHub/Slack/
  Stripe tokens, private keys (RSA/EC/OpenSSH/PGP), generic API keys, database
  connection strings, 20+ secret patterns, CI/CD integration ready

Deleted placeholder files:
- references/cryptography_implementation.md (template)
- references/penetration_testing_guide.md (template)
- references/security_architecture_patterns.md (template)
- scripts/pentest_automator.py (placeholder)
- scripts/security_auditor.py (placeholder)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:31:54 +01:00

21 KiB

Cryptography Implementation Guide

Practical cryptographic patterns for securing data at rest, in transit, and in use.


Table of Contents


Cryptographic Primitives

Algorithm Selection Guide

Use Case Recommended Algorithm Avoid
Symmetric encryption AES-256-GCM, ChaCha20-Poly1305 DES, 3DES, AES-ECB, RC4
Asymmetric encryption RSA-OAEP (2048+), ECIES RSA-PKCS1v1.5
Digital signatures Ed25519, ECDSA P-256, RSA-PSS RSA-PKCS1v1.5
Key exchange X25519, ECDH P-256 RSA key transport
Password hashing Argon2id, bcrypt, scrypt MD5, SHA-1, plain SHA-256
Message authentication HMAC-SHA256, Poly1305 MD5, SHA-1
Random generation OS CSPRNG Math.random(), time-based

Security Strength Comparison

Key Size Security Level Equivalent Symmetric
RSA 2048 112 bits AES-128
RSA 3072 128 bits AES-128
RSA 4096 152 bits AES-192
ECDSA P-256 128 bits AES-128
ECDSA P-384 192 bits AES-192
Ed25519 128 bits AES-128

Symmetric Encryption

AES-256-GCM Implementation

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

class AESGCMEncryption:
    """
    AES-256-GCM authenticated encryption.

    Provides both confidentiality and integrity.
    GCM mode prevents tampering with authentication tag.
    """

    def __init__(self, key: bytes = None):
        if key is None:
            key = AESGCM.generate_key(bit_length=256)
        if len(key) != 32:
            raise ValueError("Key must be 32 bytes (256 bits)")
        self.key = key
        self.aesgcm = AESGCM(key)

    def encrypt(self, plaintext: bytes, associated_data: bytes = None) -> bytes:
        """
        Encrypt with random nonce.

        Returns: nonce (12 bytes) + ciphertext + tag (16 bytes)
        """
        nonce = os.urandom(12)  # 96-bit nonce for GCM
        ciphertext = self.aesgcm.encrypt(nonce, plaintext, associated_data)
        return nonce + ciphertext

    def decrypt(self, ciphertext: bytes, associated_data: bytes = None) -> bytes:
        """
        Decrypt and verify authentication tag.

        Raises InvalidTag if tampered.
        """
        nonce = ciphertext[:12]
        actual_ciphertext = ciphertext[12:]
        return self.aesgcm.decrypt(nonce, actual_ciphertext, associated_data)


# Usage
encryptor = AESGCMEncryption()
plaintext = b"Sensitive data to encrypt"
aad = b"user_id:12345"  # Authenticated but not encrypted

ciphertext = encryptor.encrypt(plaintext, associated_data=aad)
decrypted = encryptor.decrypt(ciphertext, associated_data=aad)

ChaCha20-Poly1305 Implementation

from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os

class ChaChaEncryption:
    """
    ChaCha20-Poly1305 authenticated encryption.

    Faster than AES on systems without hardware AES support.
    Resistant to timing attacks (constant-time implementation).
    """

    def __init__(self, key: bytes = None):
        if key is None:
            key = ChaCha20Poly1305.generate_key()
        self.key = key
        self.chacha = ChaCha20Poly1305(key)

    def encrypt(self, plaintext: bytes, associated_data: bytes = None) -> bytes:
        """Encrypt with random 96-bit nonce."""
        nonce = os.urandom(12)
        ciphertext = self.chacha.encrypt(nonce, plaintext, associated_data)
        return nonce + ciphertext

    def decrypt(self, ciphertext: bytes, associated_data: bytes = None) -> bytes:
        """Decrypt and verify Poly1305 authentication tag."""
        nonce = ciphertext[:12]
        actual_ciphertext = ciphertext[12:]
        return self.chacha.decrypt(nonce, actual_ciphertext, associated_data)

Envelope Encryption Pattern

"""
Envelope Encryption: Encrypt data with a Data Encryption Key (DEK),
then encrypt DEK with a Key Encryption Key (KEK).

Benefits:
- KEK can be rotated without re-encrypting data
- DEK can be stored alongside encrypted data
- Enables per-record encryption with different DEKs
"""

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
import os
import json
import base64

class EnvelopeEncryption:
    def __init__(self, kek_public_key, kek_private_key=None):
        self.kek_public = kek_public_key
        self.kek_private = kek_private_key

    def encrypt(self, plaintext: bytes) -> dict:
        """
        1. Generate random DEK
        2. Encrypt plaintext with DEK
        3. Encrypt DEK with KEK
        4. Return encrypted DEK + encrypted data
        """
        # Generate Data Encryption Key
        dek = AESGCM.generate_key(bit_length=256)
        aesgcm = AESGCM(dek)

        # Encrypt data with DEK
        nonce = os.urandom(12)
        encrypted_data = aesgcm.encrypt(nonce, plaintext, None)

        # Encrypt DEK with KEK (RSA-OAEP)
        encrypted_dek = self.kek_public.encrypt(
            dek,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )

        return {
            'encrypted_dek': base64.b64encode(encrypted_dek).decode(),
            'nonce': base64.b64encode(nonce).decode(),
            'ciphertext': base64.b64encode(encrypted_data).decode()
        }

    def decrypt(self, envelope: dict) -> bytes:
        """
        1. Decrypt DEK with KEK
        2. Decrypt data with DEK
        """
        if self.kek_private is None:
            raise ValueError("Private key required for decryption")

        # Decrypt DEK
        encrypted_dek = base64.b64decode(envelope['encrypted_dek'])
        dek = self.kek_private.decrypt(
            encrypted_dek,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )

        # Decrypt data
        aesgcm = AESGCM(dek)
        nonce = base64.b64decode(envelope['nonce'])
        ciphertext = base64.b64decode(envelope['ciphertext'])

        return aesgcm.decrypt(nonce, ciphertext, None)

Asymmetric Encryption

RSA Key Generation and Usage

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

def generate_rsa_keypair(key_size=4096):
    """Generate RSA key pair for encryption/signing."""
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=key_size
    )
    public_key = private_key.public_key()

    return private_key, public_key

def serialize_keys(private_key, public_key, password=None):
    """Serialize keys for storage."""
    # Private key (encrypted with password)
    if password:
        encryption = serialization.BestAvailableEncryption(password.encode())
    else:
        encryption = serialization.NoEncryption()

    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=encryption
    )

    # Public key
    public_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )

    return private_pem, public_pem

def rsa_encrypt(public_key, plaintext: bytes) -> bytes:
    """RSA-OAEP encryption (for small data like keys)."""
    return public_key.encrypt(
        plaintext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )

def rsa_decrypt(private_key, ciphertext: bytes) -> bytes:
    """RSA-OAEP decryption."""
    return private_key.decrypt(
        ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )

Digital Signatures (Ed25519)

from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PrivateKey, Ed25519PublicKey
)

class Ed25519Signer:
    """
    Ed25519 digital signatures.

    Fast, secure, and deterministic.
    256-bit keys provide 128-bit security.
    """

    def __init__(self, private_key=None):
        if private_key is None:
            private_key = Ed25519PrivateKey.generate()
        self.private_key = private_key
        self.public_key = private_key.public_key()

    def sign(self, message: bytes) -> bytes:
        """Create digital signature."""
        return self.private_key.sign(message)

    def verify(self, message: bytes, signature: bytes) -> bool:
        """Verify digital signature."""
        try:
            self.public_key.verify(signature, message)
            return True
        except Exception:
            return False

    def get_public_key_bytes(self) -> bytes:
        """Export public key for verification."""
        return self.public_key.public_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PublicFormat.Raw
        )


# Usage for message signing
signer = Ed25519Signer()
message = b"Important document content"
signature = signer.sign(message)

# Verification (can be done with public key only)
is_valid = signer.verify(message, signature)

ECDH Key Exchange

from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

class X25519KeyExchange:
    """
    X25519 Diffie-Hellman key exchange.

    Used to establish shared secrets over insecure channels.
    """

    def __init__(self):
        self.private_key = x25519.X25519PrivateKey.generate()
        self.public_key = self.private_key.public_key()

    def get_public_key_bytes(self) -> bytes:
        """Get public key to send to peer."""
        return self.public_key.public_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PublicFormat.Raw
        )

    def derive_shared_key(self, peer_public_key_bytes: bytes,
                          info: bytes = b"") -> bytes:
        """
        Derive shared encryption key from peer's public key.

        Uses HKDF to derive a proper encryption key.
        """
        peer_public_key = x25519.X25519PublicKey.from_public_bytes(
            peer_public_key_bytes
        )

        shared_secret = self.private_key.exchange(peer_public_key)

        # Derive encryption key using HKDF
        derived_key = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=info,
        ).derive(shared_secret)

        return derived_key


# Key exchange example
alice = X25519KeyExchange()
bob = X25519KeyExchange()

# Exchange public keys (can be done over insecure channel)
alice_public = alice.get_public_key_bytes()
bob_public = bob.get_public_key_bytes()

# Both derive the same shared key
alice_shared = alice.derive_shared_key(bob_public, info=b"session-key")
bob_shared = bob.derive_shared_key(alice_public, info=b"session-key")

assert alice_shared == bob_shared  # Same key!

Hashing and Password Storage

Password Hashing with Argon2

import argon2
from argon2 import PasswordHasher, Type

class SecurePasswordHasher:
    """
    Argon2id password hashing.

    Argon2id combines resistance to:
    - GPU attacks (memory-hard)
    - Side-channel attacks (data-independent)
    """

    def __init__(self):
        # OWASP recommended parameters
        self.hasher = PasswordHasher(
            time_cost=3,        # Iterations
            memory_cost=65536,  # 64 MB
            parallelism=4,      # Threads
            hash_len=32,        # Output length
            type=Type.ID        # Argon2id variant
        )

    def hash_password(self, password: str) -> str:
        """
        Hash password for storage.

        Returns encoded string with algorithm parameters and salt.
        """
        return self.hasher.hash(password)

    def verify_password(self, password: str, hash: str) -> bool:
        """
        Verify password against stored hash.

        Automatically handles timing-safe comparison.
        """
        try:
            self.hasher.verify(hash, password)
            return True
        except argon2.exceptions.VerifyMismatchError:
            return False

    def needs_rehash(self, hash: str) -> bool:
        """Check if hash needs upgrading to current parameters."""
        return self.hasher.check_needs_rehash(hash)


# Usage
hasher = SecurePasswordHasher()

# During registration
password = "user_password_123!"
password_hash = hasher.hash_password(password)
# Store password_hash in database

# During login
stored_hash = password_hash  # From database
if hasher.verify_password("user_password_123!", stored_hash):
    print("Login successful")

    # Check if hash needs upgrading
    if hasher.needs_rehash(stored_hash):
        new_hash = hasher.hash_password(password)
        # Update stored hash

Bcrypt Alternative

import bcrypt

class BcryptHasher:
    """
    Bcrypt password hashing.

    Well-established, widely supported.
    Use when Argon2 is not available.
    """

    def __init__(self, rounds=12):
        self.rounds = rounds

    def hash_password(self, password: str) -> str:
        salt = bcrypt.gensalt(rounds=self.rounds)
        return bcrypt.hashpw(password.encode(), salt).decode()

    def verify_password(self, password: str, hash: str) -> bool:
        return bcrypt.checkpw(password.encode(), hash.encode())

HMAC for Message Authentication

import hmac
import hashlib
import secrets

def create_hmac(key: bytes, message: bytes) -> bytes:
    """Create HMAC-SHA256 authentication tag."""
    return hmac.new(key, message, hashlib.sha256).digest()

def verify_hmac(key: bytes, message: bytes, tag: bytes) -> bool:
    """Verify HMAC in constant time."""
    expected = hmac.new(key, message, hashlib.sha256).digest()
    return hmac.compare_digest(expected, tag)

# API request signing example
def sign_api_request(secret_key: bytes, method: str, path: str,
                     body: bytes, timestamp: str) -> str:
    """Sign API request for authentication."""
    message = f"{method}\n{path}\n{timestamp}\n".encode() + body
    signature = create_hmac(secret_key, message)
    return signature.hex()

Key Management

Key Derivation Functions

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives import hashes
import os

def derive_key_pbkdf2(password: str, salt: bytes = None,
                       iterations: int = 600000) -> tuple:
    """
    Derive encryption key from password using PBKDF2.

    NIST recommends minimum 600,000 iterations for PBKDF2-SHA256.
    """
    if salt is None:
        salt = os.urandom(16)

    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=iterations
    )

    key = kdf.derive(password.encode())
    return key, salt

def derive_key_scrypt(password: str, salt: bytes = None) -> tuple:
    """
    Derive key using scrypt (memory-hard).

    More resistant to hardware attacks than PBKDF2.
    """
    if salt is None:
        salt = os.urandom(16)

    kdf = Scrypt(
        salt=salt,
        length=32,
        n=2**17,  # CPU/memory cost
        r=8,      # Block size
        p=1       # Parallelization
    )

    key = kdf.derive(password.encode())
    return key, salt

Key Rotation Strategy

from datetime import datetime, timedelta
from typing import Dict, Optional
import json

class KeyManager:
    """
    Manage encryption key lifecycle.

    Supports key rotation without data re-encryption.
    """

    def __init__(self, storage_backend):
        self.storage = storage_backend

    def generate_key(self, key_id: str, algorithm: str = 'AES-256-GCM') -> dict:
        """Generate and store new encryption key."""
        key_material = os.urandom(32)

        key_metadata = {
            'key_id': key_id,
            'algorithm': algorithm,
            'created_at': datetime.utcnow().isoformat(),
            'expires_at': (datetime.utcnow() + timedelta(days=365)).isoformat(),
            'status': 'active'
        }

        self.storage.store_key(key_id, key_material, key_metadata)
        return key_metadata

    def rotate_key(self, old_key_id: str) -> dict:
        """
        Rotate encryption key.

        1. Mark old key as 'decrypt-only'
        2. Generate new key as 'active'
        3. Old key can still decrypt, new key encrypts
        """
        # Mark old key as decrypt-only
        old_metadata = self.storage.get_key_metadata(old_key_id)
        old_metadata['status'] = 'decrypt-only'
        self.storage.update_key_metadata(old_key_id, old_metadata)

        # Generate new key
        new_key_id = f"{old_key_id.rsplit('_', 1)[0]}_{datetime.utcnow().strftime('%Y%m%d')}"
        return self.generate_key(new_key_id)

    def get_encryption_key(self) -> tuple:
        """Get current active key for encryption."""
        return self.storage.get_active_key()

    def get_decryption_key(self, key_id: str) -> bytes:
        """Get specific key for decryption."""
        return self.storage.get_key(key_id)

Hardware Security Module Integration

# AWS CloudHSM / KMS integration pattern
import boto3

class AWSKMSProvider:
    """
    AWS KMS integration for key management.

    Keys never leave AWS infrastructure.
    """

    def __init__(self, key_id: str, region: str = 'us-east-1'):
        self.kms = boto3.client('kms', region_name=region)
        self.key_id = key_id

    def encrypt(self, plaintext: bytes) -> bytes:
        """Encrypt using KMS master key."""
        response = self.kms.encrypt(
            KeyId=self.key_id,
            Plaintext=plaintext
        )
        return response['CiphertextBlob']

    def decrypt(self, ciphertext: bytes) -> bytes:
        """Decrypt using KMS master key."""
        response = self.kms.decrypt(
            KeyId=self.key_id,
            CiphertextBlob=ciphertext
        )
        return response['Plaintext']

    def generate_data_key(self) -> tuple:
        """Generate data encryption key."""
        response = self.kms.generate_data_key(
            KeyId=self.key_id,
            KeySpec='AES_256'
        )
        return response['Plaintext'], response['CiphertextBlob']

Common Cryptographic Mistakes

Mistake 1: Using ECB Mode

# BAD: ECB mode reveals patterns
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def bad_ecb_encrypt(key, plaintext):
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    encryptor = cipher.encryptor()
    return encryptor.update(plaintext) + encryptor.finalize()

# GOOD: Use authenticated encryption (GCM)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def good_gcm_encrypt(key, plaintext):
    aesgcm = AESGCM(key)
    nonce = os.urandom(12)
    return nonce + aesgcm.encrypt(nonce, plaintext, None)

Mistake 2: Reusing Nonces

# BAD: Static nonce
nonce = b"fixed_nonce!"  # NEVER DO THIS

# GOOD: Random nonce per encryption
nonce = os.urandom(12)

# ALSO GOOD: Counter-based nonce (if you can guarantee no repeats)
class NonceCounter:
    def __init__(self):
        self.counter = 0

    def get_nonce(self):
        self.counter += 1
        return self.counter.to_bytes(12, 'big')

Mistake 3: Rolling Your Own Crypto

# BAD: Custom "encryption"
def bad_encrypt(data, key):
    return bytes([b ^ k for b, k in zip(data, key * len(data))])

# GOOD: Use established libraries
from cryptography.fernet import Fernet

def good_encrypt(data, key):
    f = Fernet(key)
    return f.encrypt(data)

Mistake 4: Weak Random Generation

import random
import secrets

# BAD: Predictable random
def bad_generate_token():
    return ''.join(random.choices('abcdef0123456789', k=32))

# GOOD: Cryptographically secure
def good_generate_token():
    return secrets.token_hex(16)

Mistake 5: Timing Attacks in Comparison

# BAD: Early exit reveals length
def bad_compare(a, b):
    if len(a) != len(b):
        return False
    for x, y in zip(a, b):
        if x != y:
            return False
    return True

# GOOD: Constant-time comparison
import hmac

def good_compare(a, b):
    return hmac.compare_digest(a, b)

Quick Reference Card

Operation Algorithm Key Size Notes
Symmetric encryption AES-256-GCM 256 bits Use random 96-bit nonce
Alternative encryption ChaCha20-Poly1305 256 bits Faster on non-AES hardware
Asymmetric encryption RSA-OAEP 2048+ bits Only for small data/keys
Key exchange X25519 256 bits Derive key with HKDF
Digital signature Ed25519 256 bits Fast, deterministic
Password hashing Argon2id - 64MB memory, 3 iterations
Message authentication HMAC-SHA256 256 bits Use for API signing
Key derivation PBKDF2-SHA256 - 600,000+ iterations