Cryptographic Patterns
Secure cryptographic patterns for encryption, hashing, key management, and transport security.
Purpose
- Guide correct use of cryptographic primitives
- Prevent common crypto implementation mistakes
- Establish secure defaults for key management and storage
Password Hashing
Recommended Algorithms
| Algorithm |
Use Case |
Work Factor |
| Argon2id |
New implementations (preferred) |
Memory: 64MB, Iterations: 3 |
| bcrypt |
Widely supported, proven |
Cost factor: 12+ |
| scrypt |
Memory-hard alternative |
N=2^14, r=8, p=1 |
Anti-Patterns
| Pattern |
Risk |
Fix |
| Plain text storage |
Instant compromise |
Use adaptive hashing |
| MD5/SHA1 for passwords |
Rainbow tables |
Use bcrypt/argon2 |
| Low work factor |
Brute force |
Increase cost/memory |
| Same salt for all |
Rainbow tables |
Unique salt per password |
Correct Implementation
// GOOD: Argon2id (preferred)
import argon2 from 'argon2';
async function hashPassword(password: string): Promise<string> {
return argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4
});
}
async function verifyPassword(hash: string, password: string): Promise<boolean> {
return argon2.verify(hash, password);
}
// GOOD: bcrypt (widely supported)
import bcrypt from 'bcrypt';
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12); // Cost factor 12
}
async function verifyPassword(hash: string, password: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// BAD: Never use these for passwords
const hash = crypto.createHash('md5').update(password).digest('hex');
const hash = crypto.createHash('sha256').update(password).digest('hex');
const hash = crypto.createHash('sha1').update(password).digest('hex');
Symmetric Encryption
Recommended Algorithms
| Algorithm |
Mode |
Use Case |
| AES-256-GCM |
Authenticated |
General encryption (preferred) |
| ChaCha20-Poly1305 |
Authenticated |
Mobile/embedded |
| AES-256-CBC |
Non-authenticated |
Legacy (add HMAC) |
Anti-Patterns
| Pattern |
Risk |
Fix |
| ECB mode |
Pattern exposure |
Use GCM or CBC |
| Hardcoded keys |
Key compromise |
Use key management |
| Reused IV/nonce |
Plaintext recovery |
Generate random IV |
| No authentication |
Ciphertext tampering |
Use GCM or add HMAC |
Correct Implementation
import crypto from 'crypto';
// GOOD: AES-256-GCM (authenticated encryption)
function encrypt(plaintext: string, key: Buffer): string {
const iv = crypto.randomBytes(12); // 96-bit IV for GCM
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Return: iv + authTag + ciphertext
return iv.toString('hex') + authTag.toString('hex') + encrypted;
}
function decrypt(ciphertext: string, key: Buffer): string {
const iv = Buffer.from(ciphertext.slice(0, 24), 'hex');
const authTag = Buffer.from(ciphertext.slice(24, 56), 'hex');
const encrypted = ciphertext.slice(56);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// BAD: ECB mode (patterns visible)
const cipher = crypto.createCipheriv('aes-256-ecb', key, null);
// BAD: Reused IV
const iv = Buffer.alloc(16, 0); // Static IV
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
// BAD: No authentication (CBC without HMAC)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
// Missing: HMAC verification of ciphertext
Key Management
Key Generation
// GOOD: Cryptographically secure random key
const key = crypto.randomBytes(32); // 256 bits
// GOOD: Key derivation from password
import crypto from 'crypto';
function deriveKey(password: string, salt: Buffer): Buffer {
return crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
}
// Or use scrypt
function deriveKeyScrypt(password: string, salt: Buffer): Buffer {
return crypto.scryptSync(password, salt, 32, {
N: 16384, // CPU/memory cost
r: 8, // Block size
p: 1 // Parallelization
});
}
Key Storage
| Environment |
Storage Method |
Access |
| Development |
Environment variables |
Process only |
| Production |
Secrets manager |
IAM-controlled |
| CI/CD |
Encrypted secrets |
Pipeline only |
// GOOD: Load from environment
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'base64');
// GOOD: AWS Secrets Manager
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({});
const secret = await client.getSecretValue({ SecretId: 'app/encryption-key' });
const key = Buffer.from(secret.SecretString!, 'base64');
// BAD: Hardcoded key
const key = Buffer.from('my-secret-key-12345678901234567890');
// BAD: Key in source control
// .env committed to git with ENCRYPTION_KEY=...
Key Rotation
// Support multiple key versions
interface EncryptedData {
keyVersion: number;
ciphertext: string;
}
async function decrypt(data: EncryptedData): Promise<string> {
const key = await getKey(data.keyVersion);
return decryptWithKey(data.ciphertext, key);
}
// Re-encrypt with new key during rotation
async function rotateEncryption(data: EncryptedData, newVersion: number): Promise<EncryptedData> {
const plaintext = await decrypt(data);
const newKey = await getKey(newVersion);
return {
keyVersion: newVersion,
ciphertext: encryptWithKey(plaintext, newKey)
};
}
JWT Security
Signing Algorithms
| Algorithm |
Type |
Use Case |
| RS256 |
Asymmetric |
Distributed systems (preferred) |
| ES256 |
Asymmetric |
Smaller tokens |
| HS256 |
Symmetric |
Single service only |
Anti-Patterns
| Pattern |
Risk |
Fix |
alg: none |
No verification |
Reject unsigned tokens |
| HS256 with public key |
Key confusion |
Verify algorithm match |
| No expiration |
Token reuse |
Set short exp |
| Sensitive data in payload |
Information leak |
Encrypt or omit |
Correct Implementation
import jwt from 'jsonwebtoken';
// GOOD: RS256 with proper validation
const publicKey = fs.readFileSync('public.pem');
const privateKey = fs.readFileSync('private.pem');
function createToken(payload: object): string {
return jwt.sign(payload, privateKey, {
algorithm: 'RS256',
expiresIn: '15m',
issuer: 'my-app',
audience: 'my-app-users'
});
}
function verifyToken(token: string): object {
return jwt.verify(token, publicKey, {
algorithms: ['RS256'], // Explicitly allow only RS256
issuer: 'my-app',
audience: 'my-app-users'
});
}
// BAD: Algorithm confusion vulnerability
jwt.verify(token, key); // No algorithm restriction
// BAD: No expiration
jwt.sign(payload, key); // Token valid forever
// BAD: Sensitive data in payload
jwt.sign({ ssn: '123-45-6789', creditCard: '...' }, key);
Random Number Generation
// GOOD: Cryptographically secure random
import crypto from 'crypto';
// Random bytes
const bytes = crypto.randomBytes(32);
// Random integer in range [0, max)
function secureRandomInt(max: number): number {
const range = max;
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
let value: number;
do {
value = crypto.randomBytes(bytesNeeded).readUIntBE(0, bytesNeeded);
} while (value >= Math.floor(256 ** bytesNeeded / range) * range);
return value % range;
}
// Random UUID
const uuid = crypto.randomUUID();
// BAD: Insecure random (predictable)
const insecure = Math.random(); // NOT cryptographically secure
const token = Math.random().toString(36).substring(7); // Predictable
TLS Configuration
Server Configuration
import https from 'https';
import fs from 'fs';
const server = https.createServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
// Modern TLS configuration
minVersion: 'TLSv1.2',
// Prefer server cipher order
honorCipherOrder: true,
// Strong cipher suites only
ciphers: [
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384'
].join(':')
});
Client Configuration
import https from 'https';
// GOOD: Verify certificates
const response = await fetch('https://api.example.com', {
agent: new https.Agent({
rejectUnauthorized: true // Default, verify certs
})
});
// BAD: Disable certificate verification
const agent = new https.Agent({
rejectUnauthorized: false // NEVER in production
});
Hashing (Non-Password)
Use Cases
| Use Case |
Algorithm |
Example |
| File integrity |
SHA-256 |
Checksums |
| Data deduplication |
SHA-256 |
Content addressing |
| HMAC authentication |
HMAC-SHA-256 |
API signatures |
| Short identifiers |
SHA-256 truncated |
Short URLs |
import crypto from 'crypto';
// File/data integrity
function hashData(data: Buffer): string {
return crypto.createHash('sha256').update(data).digest('hex');
}
// HMAC for API authentication
function createHmac(message: string, secret: Buffer): string {
return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
function verifyHmac(message: string, signature: string, secret: Buffer): boolean {
const expected = createHmac(message, secret);
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}
Validation Checklist
Password Storage
Encryption
JWT/Tokens
TLS
Random Generation
Related Resources