---
# === IDENTITY ===
id: software/security/encryption-at-rest-in-transit/2026
canonical_question: "How do I implement encryption at rest and in transit?"
aliases:
  - "encryption best practices for data at rest and in transit"
  - "AES-256-GCM encryption implementation"
  - "TLS 1.3 configuration guide"
  - "how to encrypt data in storage and transmission"
  - "envelope encryption with KMS"
  - "ChaCha20-Poly1305 vs AES-GCM"
  - "NIST cryptographic standards for encryption"
  - "OWASP cryptographic storage best practices"
entity_type: software_reference
domain: software > security > Encryption at Rest and in Transit
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "TLS 1.0/1.1 deprecated by IETF (RFC 8996, March 2021); AWS deprecated TLS 1.0/1.1 on API endpoints (Feb 2024); NIST post-quantum cryptography standards finalized (Aug 2024)"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER use ECB mode for any encryption -- it leaks data patterns and is cryptographically broken for most uses"
  - "NEVER use MD5 or SHA-1 for password hashing or key derivation -- use Argon2id, bcrypt, or scrypt"
  - "NEVER hardcode encryption keys in source code or store them alongside encrypted data"
  - "TLS 1.0 and 1.1 are deprecated (RFC 8996) -- use TLS 1.2 minimum, prefer TLS 1.3"
  - "AES-GCM nonces MUST be unique per encryption operation with the same key -- nonce reuse breaks authentication entirely"
  - "Self-signed certificates MUST NOT be used in production -- use certificates from a trusted CA (Let's Encrypt is free)"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to hash passwords, not encrypt data"
    use_instead: "software/security/password-hashing/2026"
  - condition: "Need TLS certificate management (issuance, renewal, pinning)"
    use_instead: "software/security/tls-certificate-management/2026"
  - condition: "Need end-to-end encryption for messaging (Signal Protocol)"
    use_instead: "software/security/e2e-encryption/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "encryption_context"
    question: "Are you encrypting data at rest, in transit, or both?"
    type: choice
    options: ["at rest", "in transit", "both"]
  - key: "platform"
    question: "What platform or language are you using?"
    type: choice
    options: ["Python", "Node.js", "Go", "Java", "Nginx/Apache", "AWS", "Azure", "GCP", "Other"]
  - key: "compliance"
    question: "Do you have specific compliance requirements?"
    type: choice
    options: ["None", "PCI DSS", "HIPAA", "SOC 2", "GDPR", "FedRAMP/NIST"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/encryption-at-rest-in-transit/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention"
    - id: "software/security/password-hashing/2026"
      label: "Password Hashing Best Practices"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/password-hashing/2026"
      label: "Password Hashing (hashing != encryption)"
    - id: "software/security/tls-certificate-management/2026"
      label: "TLS Certificate Management"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Cryptographic Storage Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src2
    title: "SP 800-175B Rev. 1: Guideline for Using Cryptographic Standards"
    author: NIST
    url: https://csrc.nist.gov/pubs/sp/800/175/b/r1/final
    type: official_docs
    published: 2020-03-31
    reliability: authoritative
  - id: src3
    title: "Server Side TLS Configuration"
    author: Mozilla
    url: https://wiki.mozilla.org/Security/Server_Side_TLS
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src4
    title: "TLS Configuration - Practical Implementation Guide"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src5
    title: "Encrypting Data-at-Rest and Data-in-Transit"
    author: AWS
    url: https://docs.aws.amazon.com/whitepapers/latest/logical-separation/encrypting-data-at-rest-and--in-transit.html
    type: official_docs
    published: 2024-09-01
    reliability: high
  - id: src6
    title: "Key Management Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src7
    title: "Do the ChaCha: Better Mobile Performance with Cryptography"
    author: Cloudflare
    url: https://blog.cloudflare.com/do-the-chacha-better-mobile-performance-with-cryptography/
    type: technical_blog
    published: 2024-02-23
    reliability: high
---

# Encryption at Rest and in Transit: Implementation Guide

## TL;DR

- **Bottom line**: Use AES-256-GCM for data at rest and TLS 1.3 (or TLS 1.2 with AEAD ciphers) for data in transit -- always use authenticated encryption modes and never roll your own crypto.
- **Key tool/command**: `openssl s_client -connect host:443 -tls1_3` to verify TLS configuration; `crypto.createCipheriv('aes-256-gcm', key, iv)` (Node.js) or `AESGCM(key)` (Python) for encryption at rest.
- **Watch out for**: ECB mode (leaks patterns), reusing nonces/IVs with AES-GCM (catastrophic auth failure), and hardcoding encryption keys in source code.
- **Works with**: All modern languages and platforms. TLS 1.3 supported in all current browsers, OpenSSL 1.1.1+, Node.js 12+, Python 3.7+, Go 1.12+, Java 11+.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER use ECB mode for any encryption -- it leaks data patterns and is cryptographically broken for most uses
- NEVER use MD5 or SHA-1 for password hashing or key derivation -- use Argon2id, bcrypt, or scrypt
- NEVER hardcode encryption keys in source code or store them alongside encrypted data
- TLS 1.0 and 1.1 are deprecated (RFC 8996) -- use TLS 1.2 minimum, prefer TLS 1.3
- AES-GCM nonces MUST be unique per encryption operation with the same key -- nonce reuse breaks authentication entirely
- Self-signed certificates MUST NOT be used in production -- use certificates from a trusted CA (Let's Encrypt is free)

## Quick Reference

### Algorithm Recommendation Table

| # | Algorithm | Use Case | Key Size | Mode/Type | Performance | Notes |
|---|---|---|---|---|---|---|
| 1 | AES-256-GCM | Encryption at rest (primary) | 256-bit | AEAD (authenticated) | Fast with AES-NI hardware | NIST approved; 12-byte nonce recommended [src2] |
| 2 | AES-128-GCM | Encryption at rest (resource-constrained) | 128-bit | AEAD (authenticated) | Faster than AES-256 | Sufficient security for most applications [src1] |
| 3 | ChaCha20-Poly1305 | Encryption at rest (mobile/no AES-NI) | 256-bit | AEAD (authenticated) | 3x faster than AES-GCM without hardware accel | Preferred on ARM CPUs without AES extensions [src7] |
| 4 | XChaCha20-Poly1305 | Encryption at rest (large nonce) | 256-bit | AEAD (authenticated) | 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 [src1] |
| 6 | X25519 | Key exchange (ECDH) | 256-bit | Asymmetric (DH) | Very fast | Default curve for TLS 1.3; constant-time [src3] |
| 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 [src3] |
| 9 | TLS 1.2 | Encryption in transit (compat) | N/A | Protocol | 2-RTT handshake | Only use with AEAD cipher suites (GCM, ChaCha20) [src3] |
| 10 | Argon2id | Password hashing (NOT encryption) | N/A | KDF | Tunable | Winner of Password Hashing Competition; use for passwords |

### TLS Cipher Suite Priority (Mozilla Intermediate)

| Priority | Cipher Suite | Protocol | Key Exchange | Notes |
|---|---|---|---|---|
| 1 | TLS_AES_128_GCM_SHA256 | TLS 1.3 | ECDHE | Default TLS 1.3 suite [src3] |
| 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 [src7] |
| 4 | ECDHE-ECDSA-AES128-GCM-SHA256 | TLS 1.2 | ECDHE | Forward secrecy + AEAD [src3] |
| 5 | ECDHE-RSA-AES128-GCM-SHA256 | TLS 1.2 | ECDHE | RSA cert + ECDHE key 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 |

## Decision Tree

```
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 below
│   │   ├── Application-level (API calls)? → Use HTTPS with certificate validation
│   │   └── Database connections? → Enable SSL/TLS on DB connection string
│   └── NO ↓
├── Data at rest (storage/database)?
│   ├── YES ↓
│   │   ├── File/blob encryption?
│   │   │   ├── Server has AES-NI? → Use AES-256-GCM
│   │   │   └── Mobile/ARM without AES hardware? → Use ChaCha20-Poly1305
│   │   ├── Database field-level encryption? → Use AES-256-GCM with per-row IV
│   │   ├── Full-disk encryption? → Use platform native (LUKS, BitLocker, FileVault)
│   │   └── Cloud storage? → Use provider KMS (AWS KMS, Azure Key Vault, GCP KMS)
│   └── NO ↓
├── Key exchange / wrapping?
│   ├── YES → Use 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
```

## Step-by-Step Guide

### 1. Choose your encryption algorithm and mode

Select AES-256-GCM for encryption at rest on servers with AES-NI hardware acceleration (most modern x86 CPUs). Use ChaCha20-Poly1305 on mobile devices or ARM platforms without AES hardware support. Always use an authenticated encryption mode (AEAD) -- never bare AES-CBC or AES-CTR without a separate MAC. [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 support for AES-NI: `grep -c aes /proc/cpuinfo` (Linux) or `sysctl -a | grep aes` (macOS) → non-zero means AES-NI is available.

### 2. Generate cryptographically secure keys

Use your platform's CSPRNG (cryptographically secure pseudo-random number generator) to generate keys. Never derive keys from passwords without a proper KDF. [src6]

```python
# Python: generate a 256-bit key
import os
key = os.urandom(32)  # 256 bits from OS CSPRNG

# Node.js: generate a 256-bit key
# const crypto = require('crypto');
# const key = crypto.randomBytes(32);
```

**Verify**: Key length check -- `len(key)` should return 32 (bytes) for AES-256.

### 3. Implement encryption at rest with unique IVs

Every encryption operation MUST use a unique IV/nonce. For AES-GCM, use 12 bytes (96 bits) generated randomly or via a counter. Never reuse an IV with the same key. [src1]

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

key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)  # 96-bit nonce — MUST be unique per encryption
ciphertext = aesgcm.encrypt(nonce, plaintext_bytes, associated_data)
# Store: nonce + ciphertext (nonce is not secret, but must be available for decryption)
```

**Verify**: Decrypt with the same key and nonce → original plaintext returned. Decrypt with wrong key → `InvalidTag` exception.

### 4. Configure TLS for data in transit

Deploy TLS 1.3 as the primary protocol. If backward compatibility is needed, allow TLS 1.2 with only AEAD cipher suites. Enable HSTS to prevent protocol downgrade attacks. [src3]

```nginx
# Nginx TLS configuration (Mozilla Intermediate)
server {
    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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # HSTS (2 years, include subdomains, preload)
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}
```

**Verify**: `openssl s_client -connect example.com:443 -tls1_3` → should show `Protocol : TLSv1.3`. Test at https://www.ssllabs.com/ssltest/ → target A+ rating.

### 5. Implement envelope encryption for key management

Use envelope encryption: encrypt data with a Data Encryption Key (DEK), then encrypt the DEK with a Key Encryption Key (KEK) stored in a KMS. This limits the blast radius of key compromise. [src5]

```
┌────────────────────────────────────────────────┐
│ Envelope Encryption Architecture               │
│                                                │
│ Plaintext → [DEK] → Ciphertext                │
│              ↓                                 │
│             DEK → [KEK] → Encrypted DEK        │
│                     ↓                          │
│                    KEK → stored in KMS/HSM     │
│                                                │
│ Stored: Ciphertext + Encrypted DEK             │
│ KMS only: KEK (never leaves KMS)               │
└────────────────────────────────────────────────┘
```

**Verify**: Confirm the plaintext DEK is never persisted to disk -- only the encrypted DEK is stored alongside the ciphertext.

### 6. Enable HSTS and HTTP-to-HTTPS redirect

Force all HTTP traffic to HTTPS and set HSTS headers to prevent downgrade attacks. [src4]

```nginx
# HTTP → HTTPS redirect
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
```

**Verify**: `curl -sI http://example.com` → should return `301` with `Location: https://example.com/`. Check HSTS header with `curl -sI https://example.com | grep -i strict`.

## Code Examples

### Python: AES-256-GCM Encryption at Rest

```python
# 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")
```

### Node.js: AES-256-GCM Encryption at Rest

```javascript
// 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, unique per operation
  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 authentication 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');
}

// Usage:
const key = crypto.randomBytes(32);  // 256-bit key from CSPRNG
const encrypted = encrypt('sensitive data', key);
const decrypted = decrypt(encrypted, key);
```

### Nginx: TLS 1.3 + 1.2 Configuration

> Full script: [nginx-tls-1-3-1-2-configuration.txt](scripts/nginx-tls-1-3-1-2-configuration.txt) (36 lines)

```nginx
# Input:  Nginx server block
# Output: A+ SSL Labs rating with TLS 1.3 + 1.2 AEAD ciphers
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
# ... (see full script)
```

### PostgreSQL: Transparent Data Encryption with pgcrypto

```sql
-- Input:  sensitive field value + encryption key
-- Output: encrypted bytea column

-- Enable pgcrypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Encrypt on insert (AES-256 with random IV via pgp_sym_encrypt)
INSERT INTO users (email, ssn_encrypted)
VALUES (
  'user@example.com',
  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 = 'user@example.com';

-- Set encryption key per session (never hardcode in queries)
SET app.encryption_key = 'load-from-vault-at-connection-time';
```

## Anti-Patterns

### Wrong: Using ECB mode

```python
# BAD -- ECB mode encrypts each block independently, leaking patterns
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
cipher = Cipher(algorithms.AES(key), modes.ECB())
# The famous "ECB penguin" — identical plaintext blocks produce identical ciphertext blocks
```

### Correct: Using GCM mode (authenticated encryption)

```python
# 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)
# Each block is unique even with identical plaintext; tampering is detected
```

### Wrong: Reusing nonces with AES-GCM

```javascript
// BAD -- same IV reused for multiple encryptions with the same key
const STATIC_IV = Buffer.from('000000000000', 'hex');  // NEVER DO THIS
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
```

### Correct: Random nonce per encryption

```javascript
// 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)
```

### Wrong: Hardcoding encryption keys

```python
# BAD -- key in source code, visible in version control
ENCRYPTION_KEY = b'mysecretkey12345mysecretkey12345'  # committed to git
cipher = AESGCM(ENCRYPTION_KEY)
```

### Correct: Loading keys from a secure vault

```python
# GOOD -- load key from environment or secrets manager
import os
key = base64.b64decode(os.environ['ENCRYPTION_KEY'])
# Or from AWS KMS / HashiCorp Vault / Azure Key Vault
# key = kms_client.decrypt(encrypted_key)['Plaintext']
```

### Wrong: Using MD5/SHA-1 for password hashing

```python
# BAD -- MD5/SHA-1 are fast hashes, trivially brute-forced for passwords
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()  # crackable in seconds
```

### Correct: Using Argon2id for password hashing

```python
# 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)
# Verify: ph.verify(hashed, password) → True or raises VerifyMismatchError
```

### Wrong: Self-signed certificates in production

```bash
# BAD -- self-signed certs provide encryption but NO identity verification
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout server.key -out server.crt -subj "/CN=example.com"
# Clients must disable certificate verification, defeating the purpose of TLS
```

### Correct: Using Let's Encrypt for free TLS certificates

```bash
# GOOD -- trusted CA certificate with automatic renewal
sudo certbot certonly --nginx -d example.com -d www.example.com
# Auto-renewal: sudo certbot renew --dry-run
# Certificate chain is trusted by all major browsers and OS trust stores
```

## Common Pitfalls

- **AES-GCM nonce exhaustion**: With random 96-bit nonces and the same key, collision probability exceeds 50% after 2^32 (~4 billion) encryptions. Fix: Rotate keys before reaching 2^32 operations, or use AES-GCM-SIV/XChaCha20 for nonce-misuse resistance. [src1]
- **Storing IV/nonce separately from ciphertext**: If the IV is lost, the data is unrecoverable. Fix: Prepend the IV to the ciphertext (IV is not secret) and document the format: `IV (12 bytes) || ciphertext || auth tag (16 bytes)`. [src1]
- **TLS without HSTS**: First visit over HTTP is vulnerable to MITM downgrade. Fix: Set `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` and submit to the HSTS preload list. [src4]
- **Mixed content on HTTPS pages**: Loading any resource over HTTP on an HTTPS page degrades security and triggers browser warnings. Fix: Use `Content-Security-Policy: upgrade-insecure-requests` and audit all resource URLs. [src4]
- **Not validating TLS certificates in code**: HTTP clients with `verify=False` (Python requests) or `rejectUnauthorized: false` (Node.js) disable certificate validation, enabling MITM attacks. Fix: Always validate certificates; add CA bundles if using private CAs. [src3]
- **Encrypting without authenticating**: AES-CBC without HMAC allows ciphertext manipulation (padding oracle attacks). Fix: Always use AEAD modes (GCM, ChaCha20-Poly1305) or apply Encrypt-then-MAC correctly. [src1]
- **PCI DSS key rotation**: PCI DSS requires annual key rotation. Fix: Implement envelope encryption so rotating the KEK does not require re-encrypting all data -- only the DEK wrappers need re-encryption. [src6]
- **Weak Diffie-Hellman parameters**: Using 1024-bit DH groups is vulnerable to Logjam attack. Fix: Use ECDHE with X25519 or generate 4096-bit DH parameters (`openssl dhparam -out dhparams.pem 4096`). [src3]

## Diagnostic Commands

```bash
# 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

# Test SSL Labs rating (use web interface)
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=example.com

# Check CPU AES-NI support (Linux)
grep -c aes /proc/cpuinfo

# Check CPU AES-NI support (macOS)
sysctl -a 2>/dev/null | grep -i aes

# Verify Nginx TLS configuration syntax
nginx -t

# List supported ciphers for a server
nmap --script ssl-enum-ciphers -p 443 example.com
```

## Version History & Compatibility

| 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 -- no modern cipher support |
| 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 |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Storing sensitive data in databases (SSN, PII, financial data) | Data is already encrypted by the storage layer (e.g., AWS S3 SSE) | Verify provider encryption meets your compliance requirements |
| Transmitting data over any network (public or private) | Communication is strictly within a hardware security module | HSM internal operations handle their own encryption |
| Compliance requires encryption (PCI DSS, HIPAA, GDPR, SOC 2) | You need to hash passwords (one-way, not reversible) | Use Argon2id, bcrypt, or scrypt for password hashing |
| Building an API that handles sensitive user data | You need to verify data integrity only (no confidentiality) | Use HMAC-SHA256 for message authentication without encryption |
| Encrypting backups, log files, or data exports | You need homomorphic computation on encrypted data | Specialized FHE libraries (Microsoft SEAL, TFHE) |

## Important Caveats

- Post-quantum cryptography standards (FIPS 203: ML-KEM, FIPS 204: ML-DSA) were finalized by NIST in August 2024 -- organizations should begin planning migration for long-lived secrets, but AES-256 and X25519 remain safe for current use
- AES-GCM has a maximum plaintext size of ~64 GB per single encryption operation (2^36 - 32 bytes) -- for larger data, split into chunks with unique nonces or use a streaming AEAD construction
- `ssl_prefer_server_ciphers off` is recommended by Mozilla when all configured ciphers are secure -- this lets mobile clients choose ChaCha20-Poly1305 when they lack AES hardware acceleration
- Cloud provider "encryption at rest" (AWS SSE-S3, Azure Storage encryption) uses provider-managed keys by default -- for compliance, use customer-managed keys (CMK) via KMS
- Transparent Database Encryption (TDE) protects data files on disk but does NOT encrypt data in memory or in transit to the application -- use TLS for the database connection and application-level encryption for sensitive fields
- Key management is the hardest part of encryption -- losing keys means permanently losing data; compromise means all data encrypted with that key is exposed

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention](/software/security/xss-prevention/2026)
- [Password Hashing Best Practices](/software/security/password-hashing/2026)
- [TLS Certificate Management](/software/security/tls-certificate-management/2026)
