openssl s_client -connect host:443 -tls1_3 to verify TLS; crypto.createCipheriv('aes-256-gcm', key, iv) (Node.js) or AESGCM(key) (Python) for encryption at rest.| # | Algorithm | Use Case | Key Size | Mode/Type | Performance | Notes |
|---|---|---|---|---|---|---|
| 1 | AES-256-GCM | Encryption at rest (primary) | 256-bit | AEAD | Fast with AES-NI | NIST approved; 12-byte nonce recommended |
| 2 | AES-128-GCM | Encryption at rest (resource-constrained) | 128-bit | AEAD | Faster than AES-256 | Sufficient security for most applications |
| 3 | ChaCha20-Poly1305 | Encryption at rest (mobile/no AES-NI) | 256-bit | AEAD | 3x faster without HW accel | Preferred on ARM CPUs without AES extensions |
| 4 | XChaCha20-Poly1305 | Encryption at rest (large nonce) | 256-bit | AEAD | Similar to ChaCha20 | 24-byte nonce; safer for random nonce generation |
| 5 | RSA-OAEP | Asymmetric encryption (key wrapping) | 2048+ bit | Asymmetric | Slow; key transport only | Never use PKCS#1 v1.5 padding; prefer 4096-bit |
| 6 | X25519 | Key exchange (ECDH) | 256-bit | Asymmetric (DH) | Very fast | Default curve for TLS 1.3; constant-time |
| 7 | Ed25519 | Digital signatures | 256-bit | Asymmetric (signing) | Very fast | Not for encryption; for authentication/signing |
| 8 | TLS 1.3 | Encryption in transit (preferred) | N/A | Protocol | Faster handshake (1-RTT) | Only AEAD ciphers; no legacy negotiation |
| 9 | TLS 1.2 | Encryption in transit (compat) | N/A | Protocol | 2-RTT handshake | Only use with AEAD cipher suites |
| 10 | Argon2id | Password hashing (NOT encryption) | N/A | KDF | Tunable | Winner of PHC; use for passwords |
| Priority | Cipher Suite | Protocol | Key Exchange | Notes |
|---|---|---|---|---|
| 1 | TLS_AES_128_GCM_SHA256 | TLS 1.3 | ECDHE | Default TLS 1.3 suite |
| 2 | TLS_AES_256_GCM_SHA384 | TLS 1.3 | ECDHE | Stronger key, slightly slower |
| 3 | TLS_CHACHA20_POLY1305_SHA256 | TLS 1.3 | ECDHE | Better on mobile/ARM |
| 4 | ECDHE-ECDSA-AES128-GCM-SHA256 | TLS 1.2 | ECDHE | Forward secrecy + AEAD |
| 5 | ECDHE-RSA-AES128-GCM-SHA256 | TLS 1.2 | ECDHE | RSA cert + ECDHE exchange |
| 6 | ECDHE-ECDSA-AES256-GCM-SHA384 | TLS 1.2 | ECDHE | 256-bit AES variant |
| 7 | ECDHE-RSA-AES256-GCM-SHA384 | TLS 1.2 | ECDHE | RSA cert + 256-bit AES |
| 8 | ECDHE-ECDSA-CHACHA20-POLY1305 | TLS 1.2 | ECDHE | Mobile-friendly alternative |
| 9 | ECDHE-RSA-CHACHA20-POLY1305 | TLS 1.2 | ECDHE | RSA cert + ChaCha20 |
START: What type of data are you encrypting?
├── Data in transit (network communication)?
│ ├── YES → Use TLS 1.3 (preferred) or TLS 1.2 with AEAD ciphers
│ │ ├── Web server (Nginx/Apache)? → See Nginx TLS config example
│ │ ├── Application-level (API calls)? → Use HTTPS with cert validation
│ │ └── Database connections? → Enable SSL/TLS on DB connection string
│ └── NO ↓
├── Data at rest (storage/database)?
│ ├── YES ↓
│ │ ├── Server has AES-NI? → Use AES-256-GCM
│ │ ├── Mobile/ARM without AES HW? → Use ChaCha20-Poly1305
│ │ ├── Database field-level? → Use AES-256-GCM with per-row IV
│ │ ├── Full-disk? → Platform native (LUKS, BitLocker, FileVault)
│ │ └── Cloud storage? → Provider KMS (AWS KMS, Azure Key Vault)
│ └── NO ↓
├── Key exchange / wrapping?
│ ├── YES → X25519 (ECDH) or RSA-OAEP (4096-bit) for envelope encryption
│ └── NO ↓
└── DEFAULT → AES-256-GCM for symmetric, X25519 for key exchange, TLS 1.3 for transport
Select AES-256-GCM for encryption at rest on servers with AES-NI hardware acceleration. Use ChaCha20-Poly1305 on mobile/ARM without AES hardware. Always use AEAD mode. [src1]
AES-256-GCM: Confidentiality + Integrity + Authentication (single pass)
AES-CBC + HMAC: Confidentiality + Integrity (two passes, error-prone -- avoid)
AES-ECB: NEVER USE -- leaks data patterns
Verify: Check CPU AES-NI support: grep -c aes /proc/cpuinfo (Linux) → non-zero means AES-NI available.
Use your platform's CSPRNG. Never derive keys from passwords without a proper KDF. [src6]
import os
key = os.urandom(32) # 256 bits from OS CSPRNG
Verify: Key length: len(key) should return 32 bytes for AES-256.
Every encryption operation MUST use a unique IV/nonce. For AES-GCM, use 12 bytes (96 bits) generated randomly. [src1]
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # unique per encryption
ciphertext = aesgcm.encrypt(nonce, plaintext_bytes, associated_data)
Verify: Decrypt with wrong key → InvalidTag exception.
Deploy TLS 1.3 as the primary protocol with TLS 1.2 AEAD fallback. Enable HSTS. [src3]
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
Verify: openssl s_client -connect example.com:443 -tls1_3 → Protocol : TLSv1.3. Test at SSL Labs for A+ rating.
Encrypt data with a DEK, encrypt the DEK with a KEK stored in KMS/HSM. [src5]
Plaintext → [DEK] → Ciphertext
DEK → [KEK] → Encrypted DEK
KEK → stored in KMS/HSM (never leaves)
Verify: Confirm plaintext DEK is never persisted to disk.
Force all HTTP to HTTPS and set HSTS headers. [src4]
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Verify: curl -sI http://example.com → 301 redirect to HTTPS.
# Input: plaintext bytes + optional associated data (AAD)
# Output: nonce + ciphertext (authenticated)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM # cryptography>=41.0.0
import os
import base64
def encrypt(plaintext: bytes, key: bytes, aad: bytes = None) -> bytes:
"""Encrypt with AES-256-GCM. Returns nonce || ciphertext."""
if len(key) != 32:
raise ValueError("Key must be 32 bytes (256 bits)")
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96-bit nonce, unique per operation
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
return nonce + ciphertext # prepend nonce for storage
def decrypt(data: bytes, key: bytes, aad: bytes = None) -> bytes:
"""Decrypt AES-256-GCM. Expects nonce || ciphertext."""
aesgcm = AESGCM(key)
nonce, ciphertext = data[:12], data[12:]
return aesgcm.decrypt(nonce, ciphertext, aad)
# Usage:
key = AESGCM.generate_key(bit_length=256)
encrypted = encrypt(b"sensitive data", key, aad=b"context")
decrypted = decrypt(encrypted, key, aad=b"context")
// Input: plaintext string + key (32 bytes)
// Output: IV + authTag + ciphertext (base64)
const crypto = require('crypto'); // Node.js built-in
function encrypt(plaintext, key) {
const iv = crypto.randomBytes(12); // 96-bit IV
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag(); // 128-bit auth tag
return Buffer.concat([iv, authTag, encrypted]).toString('base64');
}
function decrypt(data, key) {
const buf = Buffer.from(data, 'base64');
const iv = buf.subarray(0, 12);
const authTag = buf.subarray(12, 28);
const ciphertext = buf.subarray(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
}
const key = crypto.randomBytes(32);
const encrypted = encrypt('sensitive data', key);
const decrypted = decrypt(encrypted, key);
# Mozilla Intermediate TLS configuration for A+ SSL Labs rating
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri;
}
-- Enable pgcrypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Encrypt on insert
INSERT INTO users (email, ssn_encrypted)
VALUES (
'[email protected]',
pgp_sym_encrypt('123-45-6789', current_setting('app.encryption_key'))
);
-- Decrypt on select
SELECT email,
pgp_sym_decrypt(ssn_encrypted, current_setting('app.encryption_key')) AS ssn
FROM users WHERE email = '[email protected]';
-- Set key per session (load from vault at connection time)
SET app.encryption_key = 'load-from-vault-at-connection-time';
# BAD -- ECB encrypts each block independently, leaking patterns
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
cipher = Cipher(algorithms.AES(key), modes.ECB())
# The "ECB penguin": identical plaintext blocks produce identical ciphertext blocks
# GOOD -- GCM provides confidentiality + integrity + authentication
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
aesgcm = AESGCM(key)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
// BAD -- same IV reused for multiple encryptions with the same key
const STATIC_IV = Buffer.from('000000000000', 'hex');
const cipher1 = crypto.createCipheriv('aes-256-gcm', key, STATIC_IV);
const cipher2 = crypto.createCipheriv('aes-256-gcm', key, STATIC_IV);
// Nonce reuse with GCM reveals XOR of plaintexts AND breaks authentication
// GOOD -- unique random IV for every encryption operation
const iv = crypto.randomBytes(12); // fresh 96-bit IV each time
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
// Store IV alongside ciphertext (IV is not secret)
# BAD -- key in source code, visible in version control
ENCRYPTION_KEY = b'mysecretkey12345mysecretkey12345'
cipher = AESGCM(ENCRYPTION_KEY)
# GOOD -- load key from environment or secrets manager
import os, base64
key = base64.b64decode(os.environ['ENCRYPTION_KEY'])
# Or from AWS KMS / HashiCorp Vault / Azure Key Vault
# BAD -- MD5/SHA-1 are fast hashes, trivially brute-forced
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest() # crackable in seconds
# GOOD -- Argon2id is memory-hard, GPU-resistant
from argon2 import PasswordHasher # argon2-cffi>=21.0.0
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
hashed = ph.hash(password)
# BAD -- no identity verification, clients must disable cert validation
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout server.key -out server.crt -subj "/CN=example.com"
# GOOD -- trusted CA, automatic renewal, free
sudo certbot certonly --nginx -d example.com -d www.example.com
# Auto-renewal: sudo certbot renew --dry-run
IV (12 bytes) || ciphertext || auth tag (16 bytes). [src1]Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. [src4]Content-Security-Policy: upgrade-insecure-requests. [src4]verify=False (Python) or rejectUnauthorized: false (Node.js) enables MITM attacks. Fix: Always validate certificates; add CA bundles for private CAs. [src3]# Check TLS version and cipher of a remote host
openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep -E "Protocol|Cipher"
# Check if server supports TLS 1.3
openssl s_client -connect example.com:443 -tls1_3 < /dev/null 2>&1 | grep "Protocol"
# Check if server still accepts TLS 1.0 (should fail)
openssl s_client -connect example.com:443 -tls1 < /dev/null 2>&1 | grep "Protocol"
# Show full certificate chain
openssl s_client -connect example.com:443 -showcerts < /dev/null 2>&1
# Verify certificate expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# Check HSTS header
curl -sI https://example.com | grep -i strict-transport-security
# Check CPU AES-NI support (Linux)
grep -c aes /proc/cpuinfo
# Verify Nginx TLS configuration syntax
nginx -t
# List supported ciphers for a server
nmap --script ssl-enum-ciphers -p 443 example.com
| Standard | Version | Status | Key Feature |
|---|---|---|---|
| TLS 1.3 | RFC 8446 | Current (2018) | 1-RTT handshake, AEAD-only, no RSA key exchange |
| TLS 1.2 | RFC 5246 | Supported | Forward secrecy with ECDHE; use AEAD ciphers only |
| TLS 1.1 | RFC 4346 | Deprecated (RFC 8996) | Do not use |
| TLS 1.0 | RFC 2246 | Deprecated (RFC 8996) | Do not use -- vulnerable to BEAST, POODLE |
| AES-GCM | NIST SP 800-38D | Current | AEAD mode; NIST approved since 2007 |
| ChaCha20-Poly1305 | RFC 8439 | Current (2018) | IETF standard; alternative to AES-GCM |
| X25519 | RFC 7748 | Current (2016) | ECDH curve; default in TLS 1.3 |
| NIST PQC | FIPS 203/204/205 | Finalized (2024) | Post-quantum: ML-KEM, ML-DSA, SLH-DSA |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Storing sensitive data in databases (SSN, PII, financial) | Storage layer already encrypts (e.g., AWS S3 SSE) | Verify provider encryption meets compliance |
| Transmitting data over any network | Communication within an HSM | HSM internal operations handle encryption |
| Compliance requires encryption (PCI DSS, HIPAA, GDPR) | You need to hash passwords (one-way) | Argon2id, bcrypt, or scrypt |
| Building an API handling sensitive user data | You need integrity only (no confidentiality) | HMAC-SHA256 for message authentication |
| Encrypting backups, log files, data exports | You need computation on encrypted data | FHE libraries (Microsoft SEAL, TFHE) |
ssl_prefer_server_ciphers off is recommended by Mozilla when all configured ciphers are secure -- lets mobile clients choose ChaCha20-Poly1305 when they lack AES hardware