Encryption at Rest Reference
Comprehensive technical reference for encryption at rest, key management, and compliance standards.
Table of Contents
- Fundamentals
- Encryption Algorithms
- Key Management
- Envelope Encryption
- Database Encryption
- File and Disk Encryption
- Cloud KMS Integration
- Key Rotation
- Compliance Standards
- Performance Considerations
- Common Patterns
- Anti-Patterns
- Security Best Practices
- Implementation Examples
Fundamentals
What is Encryption at Rest?
Encryption at rest protects data stored on disk from unauthorized access. Unlike encryption in transit (TLS/SSL), encryption at rest protects data when it's not actively being transmitted.
Key Differences:
| Aspect | Encryption at Rest | Encryption in Transit |
|---|---|---|
| Protects | Stored data on disk | Data moving over network |
| Threat Model | Physical access, disk theft, backups | Network eavesdropping, MITM |
| Typical Use | Databases, files, backups, volumes | HTTPS, TLS, SSH |
| Performance | One-time cost on read/write | Continuous overhead during transmission |
| Key Lifetime | Long-lived (months/years) | Short-lived (session keys) |
Threat Model
What encryption at rest protects against:
- Physical theft: Stolen laptops, hard drives, backup tapes
- Cloud provider access: Unauthorized access by cloud staff
- Forensic recovery: Data recovery from decommissioned drives
- Backup compromise: Stolen or leaked backup files
- Snapshot access: Unauthorized access to volume snapshots
- Insider threats: Malicious employees with physical access
What it does NOT protect against:
- Application-level attacks: SQL injection, XSS, CSRF
- Authorized user access: Users with valid credentials
- Memory dumps: Data in RAM is not encrypted
- Side-channel attacks: Timing attacks, cache attacks
- Compromised encryption keys: If attacker has keys, encryption is useless
Encryption Layers
┌─────────────────────────────────────────┐
│ Application-Level Encryption │ ← Column/field encryption (highest control)
├─────────────────────────────────────────┤
│ Database-Level Encryption │ ← TDE (Transparent Data Encryption)
├─────────────────────────────────────────┤
│ Filesystem-Level Encryption │ ← eCryptfs, EncFS
├─────────────────────────────────────────┤
│ Volume/Block-Level Encryption │ ← LUKS, dm-crypt, BitLocker
├─────────────────────────────────────────┤
│ Hardware-Level Encryption │ ← Self-encrypting drives (SEDs)
└─────────────────────────────────────────┘
Trade-offs:
| Layer | Granularity | Performance | Key Management | Flexibility |
|---|---|---|---|---|
| Application | Per-field | Slowest | Most complex | Highest |
| Database | Per-database | Fast | Moderate | Moderate |
| Filesystem | Per-file | Moderate | Moderate | Moderate |
| Volume | Per-volume | Fast | Simple | Low |
| Hardware | Per-drive | Fastest | Simple | Lowest |
When to Use Each Layer
Application-Level:
- Need per-field encryption (e.g., credit card numbers)
- Compliance requires end-to-end encryption
- Different users need different keys
- Zero-trust architecture
Database-Level:
- Encrypt entire database with minimal code changes
- Compliance requires encryption at rest
- Centralized key management
- Performance is critical
Filesystem-Level:
- Encrypt user directories or shared folders
- Protect files from OS-level access
- Need per-user encryption
Volume-Level:
- Encrypt entire disk/partition
- Protect against physical theft
- Simplest to implement
- Minimal performance overhead
Hardware-Level:
- Best performance
- No OS support required
- Limited key management control
Encryption Algorithms
Symmetric Encryption
Definition: Same key used for encryption and decryption.
Use Case: Encryption at rest (fast, efficient).
AES (Advanced Encryption Standard)
Overview:
- Standard: FIPS 197 (2001)
- Block Size: 128 bits
- Key Sizes: 128, 192, 256 bits
- Status: Industry standard, NIST approved
Modes of Operation:
| Mode | Properties | Use Case | Security |
|---|---|---|---|
| ECB (Electronic Codebook) | Deterministic, no IV | ❌ NEVER USE | Insecure |
| CBC (Cipher Block Chaining) | Requires IV, sequential | Legacy systems | Secure with IV |
| CTR (Counter) | Parallel, requires nonce | Performance-critical | Secure with unique nonce |
| GCM (Galois/Counter Mode) | AEAD, parallel, auth tag | Modern apps | Best choice |
| XTS (XEX-based tweaked-codebook) | Disk encryption | Full-disk encryption | Optimized for storage |
Recommended: AES-256-GCM (authenticated encryption with associated data).
Example (OpenSSL):
# Encrypt file with AES-256-GCM
openssl enc -aes-256-gcm -salt -in plaintext.txt -out encrypted.bin -k password
# Decrypt
openssl enc -aes-256-gcm -d -in encrypted.bin -out plaintext.txt -k password
Python Example (cryptography library):
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# Generate key (256 bits)
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
# Generate nonce (96 bits recommended for GCM)
nonce = os.urandom(12)
# Encrypt
plaintext = b"Secret data"
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# Decrypt
decrypted = aesgcm.decrypt(nonce, ciphertext, None)
assert decrypted == plaintext
Go Example:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
)
func encryptAESGCM(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Prepend nonce to ciphertext
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
Performance:
- Hardware acceleration: AES-NI (Intel), ARMv8 Crypto Extensions
- Throughput: 2-10 GB/s with hardware acceleration
- Latency: ~1-5 microseconds per operation
ChaCha20-Poly1305
Overview:
- Standard: RFC 8439 (2018)
- Algorithm: Stream cipher (ChaCha20) + MAC (Poly1305)
- Key Size: 256 bits
- Nonce Size: 96 bits (12 bytes)
- Use Case: Mobile devices without AES-NI, software-only systems
Advantages over AES:
- Faster on systems without AES-NI
- Constant-time implementation (resistant to timing attacks)
- Simpler to implement correctly
Python Example:
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os
# Generate key
key = ChaCha20Poly1305.generate_key()
chacha = ChaCha20Poly1305(key)
# Generate nonce
nonce = os.urandom(12)
# Encrypt
plaintext = b"Secret data"
ciphertext = chacha.encrypt(nonce, plaintext, None)
# Decrypt
decrypted = chacha.decrypt(nonce, ciphertext, None)
assert decrypted == plaintext
When to use ChaCha20-Poly1305:
- Mobile devices (ARM without crypto extensions)
- Software-only encryption
- Need constant-time implementation
- Alternative to AES-GCM
When to use AES-GCM:
- x86/x64 processors with AES-NI
- Hardware acceleration available
- Compliance requires FIPS-approved algorithms
Deprecated/Insecure Algorithms
❌ Never use:
- DES (Data Encryption Standard) - 56-bit key, broken
- 3DES (Triple DES) - 112-bit effective key, deprecated
- RC4 - Stream cipher, broken
- Blowfish - 64-bit block size, vulnerable to birthday attacks
Key Takeaways:
- Use AES-256-GCM or ChaCha20-Poly1305
- Always use authenticated encryption (AEAD)
- Never reuse nonces
- Use hardware acceleration when available
Key Management
Key Hierarchy
Principle: Separate encryption keys from data encryption keys (DEKs).
┌──────────────────────────────────────────┐
│ Root Key (Master Key) │ ← Stored in HSM or KMS
│ - Rarely changed │
│ - Used to encrypt KEKs │
└──────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Key Encryption Key (KEK) │ ← Encrypted by root key
│ - Changed periodically (e.g., yearly) │
│ - Used to encrypt DEKs │
└──────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Data Encryption Key (DEK) │ ← Encrypted by KEK
│ - Changed frequently (e.g., daily) │
│ - Used to encrypt actual data │
└──────────────┬───────────────────────────┘
│
▼
┌──────────────────┐
│ Encrypted Data │ ← Encrypted by DEK
└──────────────────┘
Benefits:
- Key rotation: Rotate KEKs without re-encrypting all data
- Performance: Encrypt DEKs (small) instead of data (large)
- Security: Compromise of DEK doesn't expose root key
Key Generation
Cryptographically Secure Random Number Generators (CSRNGs):
| Language | CSRNG Function | Entropy Source |
|---|---|---|
| Python | os.urandom(), secrets |
/dev/urandom (Linux), CryptGenRandom (Windows) |
| Go | crypto/rand.Reader |
/dev/urandom |
| Node.js | crypto.randomBytes() |
OpenSSL |
| OpenSSL | RAND_bytes() |
/dev/urandom |
❌ Never use:
random.random()(Python) - PredictableMath.random()(JavaScript) - Predictablerand()(C) - Predictable- Timestamp-based keys
- Hardcoded keys
✅ Good key generation:
import os
import secrets
# Generate 256-bit key (32 bytes)
key = os.urandom(32)
# Or use secrets module (Python 3.6+)
key = secrets.token_bytes(32)
# Generate key from password (DO NOT use directly, see KDF section)
Key Derivation Functions (KDFs)
Purpose: Derive encryption keys from passwords or passphrases.
Problem: Passwords are low-entropy, weak keys. Solution: Use KDF to derive strong keys from passwords.
PBKDF2 (Password-Based Key Derivation Function 2)
Standard: RFC 8018 (PKCS #5) Algorithm: HMAC + iteration count Parameters:
- Password/passphrase
- Salt (16+ bytes, random)
- Iteration count (100,000+ for HMAC-SHA256)
- Key length (32 bytes for AES-256)
Python Example:
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
import os
password = b"user-password"
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32, # 256 bits
salt=salt,
iterations=480000, # OWASP recommendation (2023)
)
key = kdf.derive(password)
Pros: FIPS-approved, widely supported Cons: Vulnerable to GPU/ASIC attacks
Argon2
Standard: RFC 9106 (2021) Algorithm: Memory-hard KDF Variants:
- Argon2i: Optimized for resistance to side-channel attacks
- Argon2d: Optimized for resistance to GPU attacks
- Argon2id: Hybrid (recommended)
Parameters:
- Password
- Salt (16+ bytes)
- Time cost (iterations)
- Memory cost (KB)
- Parallelism (threads)
Python Example:
from argon2 import PasswordHasher
import os
ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # 64 MB
parallelism=4, # 4 threads
hash_len=32, # 256-bit key
salt_len=16,
)
password = "user-password" # Example only - in production, get from secure input
hash_str = ph.hash(password)
# Verify
try:
ph.verify(hash_str, password)
print("Password correct")
except:
print("Password incorrect")
Pros: Memory-hard (resistant to GPU/ASIC attacks), winner of Password Hashing Competition (2015) Cons: Not FIPS-approved (yet)
scrypt
Standard: RFC 7914 (2016) Algorithm: Memory-hard KDF Parameters:
- Password
- Salt (16+ bytes)
- N (CPU/memory cost factor, power of 2, e.g., 2^14)
- r (block size, e.g., 8)
- p (parallelization factor, e.g., 1)
Python Example:
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
import os
password = b"user-password"
salt = os.urandom(16)
kdf = Scrypt(
salt=salt,
length=32,
n=2**14, # CPU/memory cost factor
r=8, # Block size
p=1, # Parallelization factor
)
key = kdf.derive(password)
Pros: Memory-hard, widely supported Cons: Complex parameter tuning
Recommendations
For password hashing (authentication):
- First choice: Argon2id
- FIPS compliance: PBKDF2-HMAC-SHA256 (480,000+ iterations)
- Legacy systems: bcrypt
For key derivation (encryption):
- First choice: Argon2id or scrypt
- FIPS compliance: PBKDF2-HMAC-SHA256
Key Storage
❌ Never store keys:
- Hardcoded in source code
- In configuration files (plaintext)
- In version control (Git)
- In environment variables (for production)
- In application logs
- In databases (unencrypted)
✅ Secure key storage:
Option 1: Hardware Security Module (HSM)
What: Dedicated hardware for key storage and cryptographic operations.
Features:
- Keys never leave HSM
- FIPS 140-2 Level 3/4 certified
- Tamper-resistant
- Expensive ($10K-$100K+)
Use Case: High-security environments (banks, government).
Examples:
- Thales Luna HSM
- AWS CloudHSM
- Google Cloud HSM
- Azure Dedicated HSM
Option 2: Cloud Key Management Service (KMS)
What: Managed service for key storage and encryption.
Features:
- API-based key management
- Automatic key rotation
- Access control (IAM)
- Audit logging
- Lower cost ($1-$10/month per key)
Use Case: Cloud-native applications.
Examples:
- AWS KMS
- Google Cloud KMS
- Azure Key Vault
- HashiCorp Vault
Option 3: Software-Based Key Management
What: Store keys encrypted with master key.
Pattern:
- Generate master key (stored securely, e.g., HSM or KMS)
- Encrypt data encryption keys (DEKs) with master key
- Store encrypted DEKs alongside encrypted data
Example (envelope encryption):
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import json
# Master key (stored in KMS or HSM)
master_key = os.urandom(32)
master_aesgcm = AESGCM(master_key)
# Generate DEK for this data
dek = AESGCM.generate_key(bit_length=256)
dek_nonce = os.urandom(12)
# Encrypt DEK with master key
encrypted_dek = master_aesgcm.encrypt(dek_nonce, dek, None)
# Encrypt data with DEK
data_aesgcm = AESGCM(dek)
data_nonce = os.urandom(12)
plaintext = b"Sensitive data"
ciphertext = data_aesgcm.encrypt(data_nonce, plaintext, None)
# Store encrypted DEK + ciphertext
envelope = {
"encrypted_dek": encrypted_dek.hex(),
"dek_nonce": dek_nonce.hex(),
"ciphertext": ciphertext.hex(),
"data_nonce": data_nonce.hex(),
}
print(json.dumps(envelope, indent=2))
Decryption:
# Load envelope
envelope = json.loads(envelope_json)
# Decrypt DEK with master key
dek = master_aesgcm.decrypt(
bytes.fromhex(envelope["dek_nonce"]),
bytes.fromhex(envelope["encrypted_dek"]),
None
)
# Decrypt data with DEK
data_aesgcm = AESGCM(dek)
plaintext = data_aesgcm.decrypt(
bytes.fromhex(envelope["data_nonce"]),
bytes.fromhex(envelope["ciphertext"]),
None
)
Option 4: Operating System Keystore
What: OS-provided secure storage (e.g., macOS Keychain, Windows Credential Manager).
Use Case: Desktop applications, local development.
Python Example (macOS Keychain):
import keyring
# Store key
keyring.set_password("myapp", "encryption_key", "base64-encoded-key")
# Retrieve key
key = keyring.get_password("myapp", "encryption_key")
Key Access Control
Principle of Least Privilege: Only authorized users/services should access keys.
AWS KMS Example (IAM Policy):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
"Condition": {
"StringEquals": {
"kms:EncryptionContext:AppName": "myapp"
}
}
}
]
}
Best Practices:
- Use separate keys for different environments (dev, staging, prod)
- Use separate keys for different applications
- Rotate keys regularly
- Audit key access (CloudTrail, Stackdriver, Azure Monitor)
- Use encryption context for additional security
Envelope Encryption
Concept
Problem: Encrypting large amounts of data with KMS is slow and expensive (API calls).
Solution:
- Generate Data Encryption Key (DEK) locally
- Encrypt data with DEK (fast, local)
- Encrypt DEK with KMS (small, one API call)
- Store encrypted DEK + encrypted data together
Flow:
┌─────────────┐
│ Plaintext │
└──────┬──────┘
│
▼
┌─────────────────────────────┐
│ Generate DEK (local) │
│ key = os.urandom(32) │
└─────────┬───────────────────┘
│
▼
┌─────────────────────────────┐
│ Encrypt data with DEK │
│ ciphertext = encrypt( │
│ plaintext, dek │
│ ) │
└─────────┬───────────────────┘
│
▼
┌─────────────────────────────┐
│ Encrypt DEK with KMS │ ← One API call
│ encrypted_dek = kms.encrypt(│
│ dek │
│ ) │
└─────────┬───────────────────┘
│
▼
┌─────────────────────────────┐
│ Store encrypted_dek + │
│ ciphertext │
└─────────────────────────────┘
Decryption:
┌─────────────────────────────┐
│ Load encrypted_dek + │
│ ciphertext │
└─────────┬───────────────────┘
│
▼
┌─────────────────────────────┐
│ Decrypt DEK with KMS │ ← One API call
│ dek = kms.decrypt( │
│ encrypted_dek │
│ ) │
└─────────┬───────────────────┘
│
▼
┌─────────────────────────────┐
│ Decrypt data with DEK │
│ plaintext = decrypt( │
│ ciphertext, dek │
│ ) │
└─────────┬───────────────────┘
│
▼
┌─────────────┐
│ Plaintext │
└─────────────┘
AWS KMS Envelope Encryption
Python Example:
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import json
kms = boto3.client('kms')
KMS_KEY_ID = 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
def encrypt_file(plaintext, kms_key_id):
"""Encrypt file using envelope encryption with AWS KMS"""
# 1. Generate DEK (plaintext key)
response = kms.generate_data_key(
KeyId=kms_key_id,
KeySpec='AES_256'
)
dek_plaintext = response['Plaintext'] # DEK (plaintext)
dek_encrypted = response['CiphertextBlob'] # DEK encrypted by KMS
# 2. Encrypt data with DEK
aesgcm = AESGCM(dek_plaintext)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# 3. Return envelope (encrypted DEK + ciphertext)
return {
'encrypted_dek': dek_encrypted,
'ciphertext': ciphertext,
'nonce': nonce,
}
def decrypt_file(envelope, kms_key_id):
"""Decrypt file using envelope encryption with AWS KMS"""
# 1. Decrypt DEK with KMS
response = kms.decrypt(
CiphertextBlob=envelope['encrypted_dek']
)
dek_plaintext = response['Plaintext']
# 2. Decrypt data with DEK
aesgcm = AESGCM(dek_plaintext)
plaintext = aesgcm.decrypt(
envelope['nonce'],
envelope['ciphertext'],
None
)
return plaintext
# Usage
plaintext = b"Sensitive data to encrypt"
envelope = encrypt_file(plaintext, KMS_KEY_ID)
decrypted = decrypt_file(envelope, KMS_KEY_ID)
assert decrypted == plaintext
Google Cloud KMS Envelope Encryption
Python Example:
from google.cloud import kms
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
def encrypt_with_gcp_kms(plaintext, project_id, location_id, key_ring_id, key_id):
"""Encrypt using envelope encryption with GCP KMS"""
# Initialize KMS client
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id)
# 1. Generate DEK locally
dek = AESGCM.generate_key(bit_length=256)
# 2. Encrypt data with DEK
aesgcm = AESGCM(dek)
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
# 3. Encrypt DEK with KMS
encrypt_response = client.encrypt(
request={'name': key_name, 'plaintext': dek}
)
encrypted_dek = encrypt_response.ciphertext
return {
'encrypted_dek': encrypted_dek,
'ciphertext': ciphertext,
'nonce': nonce,
}
def decrypt_with_gcp_kms(envelope, project_id, location_id, key_ring_id, key_id):
"""Decrypt using envelope encryption with GCP KMS"""
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id)
# 1. Decrypt DEK with KMS
decrypt_response = client.decrypt(
request={'name': key_name, 'ciphertext': envelope['encrypted_dek']}
)
dek = decrypt_response.plaintext
# 2. Decrypt data with DEK
aesgcm = AESGCM(dek)
plaintext = aesgcm.decrypt(
envelope['nonce'],
envelope['ciphertext'],
None
)
return plaintext
Benefits of Envelope Encryption
- Performance: Local encryption (fast) + one KMS call (small overhead)
- Cost: Fewer KMS API calls
- Scalability: Can encrypt large files without KMS payload limits
- Key rotation: Rotate master key without re-encrypting data (just re-encrypt DEKs)
Database Encryption
Transparent Data Encryption (TDE)
What: Database-level encryption that encrypts entire database files.
How it works:
- Database generates master key
- Database encrypts data pages before writing to disk
- Database decrypts data pages after reading from disk
- Applications see plaintext (transparent)
Pros:
- No application changes required
- Encrypts all data (tables, indexes, logs)
- Protects backups
- Minimal performance overhead (~3-10%)
Cons:
- Database has full access to keys
- All-or-nothing (can't encrypt specific columns)
- Doesn't protect against application-level attacks
PostgreSQL TDE
Status: Not natively supported (as of PostgreSQL 16).
Alternatives:
- pgcrypto extension: Column-level encryption
- Filesystem-level encryption: LUKS, dm-crypt
- Cloud provider TDE: AWS RDS, Google Cloud SQL
pgcrypto Example (Column-Level Encryption):
-- Enable pgcrypto extension
CREATE EXTENSION pgcrypto;
-- Encrypt data
INSERT INTO users (email, encrypted_ssn)
VALUES (
'user@example.com',
pgp_sym_encrypt('123-45-6789', 'encryption-password')
);
-- Decrypt data
SELECT
email,
pgp_sym_decrypt(encrypted_ssn::bytea, 'encryption-password') AS ssn
FROM users;
Best Practice: Use envelope encryption with KMS instead of hardcoded password:
-- Store encrypted DEK alongside data
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
encrypted_ssn BYTEA,
encrypted_dek BYTEA, -- DEK encrypted by KMS
dek_nonce BYTEA
);
-- Application encrypts SSN with DEK, encrypts DEK with KMS
-- Store both encrypted_ssn and encrypted_dek
MySQL TDE
Support: MySQL 5.7+ (Enterprise), MariaDB 10.1+
Setup:
-- Enable TDE plugin
INSTALL PLUGIN keyring_file SONAME 'keyring_file.so';
-- Configure keyring in my.cnf
[mysqld]
early-plugin-load=keyring_file.so
keyring_file_data=/var/lib/mysql-keyring/keyring
-- Create encrypted table
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(255),
ssn VARCHAR(11)
) ENCRYPTION='Y';
-- Encrypt existing table
ALTER TABLE users ENCRYPTION='Y';
Key Rotation:
-- Rotate master key
ALTER INSTANCE ROTATE INNODB MASTER KEY;
MongoDB Encryption at Rest
Support: MongoDB 3.2+ (Enterprise, Atlas)
Configuration (mongod.conf):
security:
enableEncryption: true
encryptionKeyFile: /etc/mongodb-keyfile
# Or use KMIP (Key Management Interoperability Protocol)
security:
enableEncryption: true
kmip:
serverName: kmip.example.com
port: 5696
clientCertificateFile: /etc/mongodb-client.pem
Encrypted Storage Engine:
- Uses AES-256-CBC
- Encrypts data files, journals, logs
- Keys stored in keyfile or KMIP server
- Automatic decryption on read
Encrypted Collections (Client-Side Field Level Encryption - CSFLE):
const { ClientEncryption } = require('mongodb-client-encryption');
// Setup encryption options
const encryptionOptions = {
keyVaultNamespace: 'encryption.__keyVault',
kmsProviders: {
aws: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}
}
};
// Create encrypted client
const client = new MongoClient(uri, {
autoEncryption: encryptionOptions
});
// Create data key
const encryption = new ClientEncryption(client, encryptionOptions);
const dataKeyId = await encryption.createDataKey('aws', {
masterKey: {
key: 'arn:aws:kms:us-east-1:123456789012:key/12345678',
region: 'us-east-1'
}
});
// Create collection with encrypted fields
await db.createCollection('users', {
validator: {
$jsonSchema: {
properties: {
ssn: {
encrypt: {
keyId: [dataKeyId],
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
}
}
}
}
}
});
// Insert encrypted data (automatic)
await db.collection('users').insertOne({
name: 'John Doe',
ssn: '123-45-6789' // Automatically encrypted
});
SQLite Encryption
Native: SQLite doesn't support encryption natively.
Extensions:
- SQLCipher: Open-source SQLite extension
- SEE (SQLite Encryption Extension): Commercial
SQLCipher Setup (Python):
from pysqlcipher3 import dbapi2 as sqlite3
# Connect to encrypted database
conn = sqlite3.connect('encrypted.db')
conn.execute("PRAGMA key='strong-password'")
# Set encryption algorithm
conn.execute("PRAGMA cipher='aes-256-cbc'")
# Create table and insert data
conn.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)')
conn.execute("INSERT INTO users (email) VALUES ('user@example.com')")
conn.commit()
Key Derivation: SQLCipher uses PBKDF2-HMAC-SHA512 by default.
Rekey (change password):
conn.execute("PRAGMA rekey='new-strong-password'")
File and Disk Encryption
Linux: LUKS (Linux Unified Key Setup)
What: Standard disk encryption on Linux.
Features:
- Full-disk or partition encryption
- Multiple key slots (up to 8 passphrases/keys)
- Uses dm-crypt kernel module
- Supports AES, XTS mode
Setup:
# Format partition with LUKS
sudo cryptsetup luksFormat /dev/sdb1
# Open encrypted partition
sudo cryptsetup luksOpen /dev/sdb1 encrypted_disk
# Create filesystem
sudo mkfs.ext4 /dev/mapper/encrypted_disk
# Mount
sudo mount /dev/mapper/encrypted_disk /mnt/encrypted
# Auto-mount on boot (add to /etc/crypttab)
echo "encrypted_disk /dev/sdb1 none luks" | sudo tee -a /etc/crypttab
# Add to /etc/fstab
echo "/dev/mapper/encrypted_disk /mnt/encrypted ext4 defaults 0 2" | sudo tee -a /etc/fstab
Key Management:
# Add new key slot
sudo cryptsetup luksAddKey /dev/sdb1
# Remove key slot
sudo cryptsetup luksRemoveKey /dev/sdb1
# Backup LUKS header
sudo cryptsetup luksHeaderBackup /dev/sdb1 --header-backup-file luks-header-backup
# Restore LUKS header
sudo cryptsetup luksHeaderRestore /dev/sdb1 --header-backup-file luks-header-backup
Key File (instead of passphrase):
# Generate key file
dd if=/dev/urandom of=/root/keyfile bs=1024 count=4
chmod 600 /root/keyfile
# Add key file to LUKS
sudo cryptsetup luksAddKey /dev/sdb1 /root/keyfile
# Open with key file
sudo cryptsetup luksOpen /dev/sdb1 encrypted_disk --key-file /root/keyfile
eCryptfs (Enterprise Cryptographic Filesystem)
What: Stacked filesystem encryption (per-directory).
Use Case: Encrypt user home directories.
Setup:
# Install eCryptfs
sudo apt-get install ecryptfs-utils
# Setup encrypted directory
mkdir ~/Private
sudo mount -t ecryptfs ~/Private ~/Private
# Configure encryption options (interactive)
# - Key type: passphrase
# - Cipher: aes
# - Key bytes: 32 (AES-256)
# - Plaintext passthrough: no
# - Filename encryption: yes
# Add to /etc/fstab for auto-mount
/home/user/Private /home/user/Private ecryptfs defaults 0 0
Encrypting Home Directory:
# Migrate existing home directory to encrypted
ecryptfs-migrate-home -u username
Windows: BitLocker
What: Full-disk encryption on Windows (Pro/Enterprise).
Setup (via GUI):
- Control Panel → BitLocker Drive Encryption
- Turn on BitLocker for drive
- Choose authentication method (password, smart card, TPM)
- Save recovery key
- Encrypt drive
Setup (via PowerShell):
# Enable BitLocker with password
Enable-BitLocker -MountPoint "C:" -PasswordProtector -Password (ConvertTo-SecureString "password" -AsPlainText -Force)
# Backup recovery key
Backup-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId (Get-BitLockerVolume -MountPoint "C:").KeyProtector[0].KeyProtectorId -Path "C:\recovery-key.txt"
# Encrypt drive
Resume-BitLocker -MountPoint "C:"
TPM (Trusted Platform Module): Hardware chip that stores encryption keys.
Recovery Key: 48-digit recovery password, stored in Active Directory or printed.
macOS: FileVault 2
What: Full-disk encryption on macOS.
Setup (via GUI):
- System Preferences → Security & Privacy → FileVault
- Turn On FileVault
- Choose recovery method (iCloud or recovery key)
- Restart to encrypt
Setup (via command line):
# Enable FileVault
sudo fdesetup enable
# Check status
sudo fdesetup status
# List enabled users
sudo fdesetup list
Recovery Key: 24-character alphanumeric key, store securely.
File-Level Encryption (GPG)
What: Encrypt individual files with GPG (GNU Privacy Guard).
Setup:
# Generate GPG key pair
gpg --full-generate-key
# Encrypt file
gpg --encrypt --recipient user@example.com file.txt
# Decrypt file
gpg --decrypt file.txt.gpg > file.txt
Symmetric Encryption (password-based):
# Encrypt
gpg --symmetric --cipher-algo AES256 file.txt
# Decrypt
gpg --decrypt file.txt.gpg > file.txt
Cloud KMS Integration
AWS KMS
Features:
- Managed key storage
- FIPS 140-2 Level 2 validated
- Automatic key rotation (annual)
- Audit logging (CloudTrail)
- Integration with AWS services (S3, EBS, RDS)
Setup:
# Create KMS key
aws kms create-key --description "My encryption key"
# Create alias
aws kms create-alias --alias-name alias/my-key --target-key-id 12345678-1234-1234-1234-123456789012
# Enable automatic key rotation
aws kms enable-key-rotation --key-id 12345678-1234-1234-1234-123456789012
Python SDK:
import boto3
kms = boto3.client('kms')
# Encrypt data directly (max 4 KB)
response = kms.encrypt(
KeyId='alias/my-key',
Plaintext=b'Secret data',
EncryptionContext={'Department': 'Finance'}
)
ciphertext = response['CiphertextBlob']
# Decrypt
response = kms.decrypt(
CiphertextBlob=ciphertext,
EncryptionContext={'Department': 'Finance'}
)
plaintext = response['Plaintext']
# Generate data key (for envelope encryption)
response = kms.generate_data_key(
KeyId='alias/my-key',
KeySpec='AES_256'
)
dek_plaintext = response['Plaintext']
dek_encrypted = response['CiphertextBlob']
Encryption Context: Additional authenticated data (AAD) for additional security.
Key Policies (IAM-like permissions for keys):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow use of the key for encryption",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:GenerateDataKey"
],
"Resource": "*"
}
]
}
Google Cloud KMS
Features:
- Managed key storage
- FIPS 140-2 Level 3 validated (HSM option)
- Automatic key rotation (configurable)
- Audit logging (Cloud Audit Logs)
- Integration with GCP services (GCS, GCE, BigQuery)
Setup:
# Create key ring
gcloud kms keyrings create my-keyring --location us-east1
# Create key
gcloud kms keys create my-key \
--location us-east1 \
--keyring my-keyring \
--purpose encryption
# Enable automatic rotation (90 days)
gcloud kms keys update my-key \
--location us-east1 \
--keyring my-keyring \
--rotation-period 90d \
--next-rotation-time 2025-12-01T00:00:00Z
Python SDK:
from google.cloud import kms
client = kms.KeyManagementServiceClient()
# Key resource name
key_name = 'projects/my-project/locations/us-east1/keyRings/my-keyring/cryptoKeys/my-key'
# Encrypt
plaintext = b'Secret data'
encrypt_response = client.encrypt(
request={'name': key_name, 'plaintext': plaintext}
)
ciphertext = encrypt_response.ciphertext
# Decrypt
decrypt_response = client.decrypt(
request={'name': key_name, 'ciphertext': ciphertext}
)
plaintext = decrypt_response.plaintext
IAM Permissions:
# Grant encrypt/decrypt permissions
gcloud kms keys add-iam-policy-binding my-key \
--location us-east1 \
--keyring my-keyring \
--member serviceAccount:my-app@my-project.iam.gserviceaccount.com \
--role roles/cloudkms.cryptoKeyEncrypterDecrypter
Azure Key Vault
Features:
- Managed key storage
- FIPS 140-2 Level 2 validated (Premium: HSM)
- Automatic key rotation (configurable)
- Audit logging (Azure Monitor)
- Integration with Azure services (Storage, SQL, VMs)
Setup:
# Create Key Vault
az keyvault create \
--name my-keyvault \
--resource-group my-rg \
--location eastus
# Create key
az keyvault key create \
--vault-name my-keyvault \
--name my-key \
--protection software \
--kty RSA \
--size 2048
# Enable automatic rotation
az keyvault key rotation-policy update \
--vault-name my-keyvault \
--name my-key \
--value '{"lifetimeActions":[{"trigger":{"timeAfterCreate":"P90D"},"action":{"type":"Rotate"}}],"attributes":{"expiryTime":"P2Y"}}'
Python SDK:
from azure.identity import DefaultAzureCredential
from azure.keyvault.keys.crypto import CryptographyClient, EncryptionAlgorithm
credential = DefaultAzureCredential()
key_url = "https://my-keyvault.vault.azure.net/keys/my-key"
crypto_client = CryptographyClient(key_url, credential)
# Encrypt
plaintext = b"Secret data"
result = crypto_client.encrypt(EncryptionAlgorithm.rsa_oaep, plaintext)
ciphertext = result.ciphertext
# Decrypt
result = crypto_client.decrypt(EncryptionAlgorithm.rsa_oaep, ciphertext)
plaintext = result.plaintext
Access Policies:
# Grant permissions to application
az keyvault set-policy \
--name my-keyvault \
--object-id <app-object-id> \
--key-permissions encrypt decrypt get list
HashiCorp Vault
What: Open-source secrets management and encryption service.
Features:
- Encryption as a Service (Transit Secrets Engine)
- Dynamic secrets
- Secrets versioning
- Audit logging
- Self-hosted or managed (HCP Vault)
Setup:
# Start Vault dev server (testing only)
vault server -dev
# Set VAULT_ADDR
export VAULT_ADDR='http://127.0.0.1:8200'
# Enable Transit secrets engine
vault secrets enable transit
# Create encryption key
vault write -f transit/keys/my-key
Encrypt/Decrypt:
# Encrypt
vault write transit/encrypt/my-key plaintext=$(echo "Secret data" | base64)
# Output:
# Key Value
# ciphertext vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM96F4qzA==
# Decrypt
vault write transit/decrypt/my-key ciphertext="vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM96F4qzA=="
# Output (base64 encoded):
# plaintext U2VjcmV0IGRhdGE=
# Decode
echo "U2VjcmV0IGRhdGE=" | base64 -d
# Output: Secret data
Python SDK (hvac):
import hvac
import base64
client = hvac.Client(url='http://127.0.0.1:8200', token='dev-token')
# Encrypt
plaintext = b'Secret data'
response = client.secrets.transit.encrypt_data(
name='my-key',
plaintext=base64.b64encode(plaintext).decode('utf-8')
)
ciphertext = response['data']['ciphertext']
# Decrypt
response = client.secrets.transit.decrypt_data(
name='my-key',
ciphertext=ciphertext
)
plaintext = base64.b64decode(response['data']['plaintext'])
Key Rotation:
# Rotate key (creates new version)
vault write -f transit/keys/my-key/rotate
# Re-encrypt data with new key version
vault write transit/rewrap/my-key ciphertext="vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM96F4qzA=="
Key Rotation
Why Rotate Keys?
Reasons:
- Limit blast radius: Compromise of old key doesn't affect new data
- Compliance: Many standards require periodic rotation (e.g., PCI-DSS)
- Cryptographic hygiene: Reduce ciphertext exposure
- Mitigate key exposure: Employee departure, system compromise
How Often?:
- Master keys: Annually
- Data encryption keys (DEKs): Monthly or per dataset
- Secrets (passwords, API keys): 90 days
- Emergency rotation: Immediately on suspected compromise
Rotation Strategies
Strategy 1: Re-encrypt All Data (Complete Rotation)
How:
- Generate new key
- Decrypt all data with old key
- Encrypt all data with new key
- Update key references
- Delete old key (after grace period)
Pros: Clean, simple Cons: Expensive (re-encrypt all data), downtime, risky
Use Case: Small datasets, infrequent rotation.
Example:
def rotate_complete(old_key, new_key, data_files):
for file_path in data_files:
# Read encrypted data
with open(file_path, 'rb') as f:
ciphertext = f.read()
# Decrypt with old key
plaintext = decrypt(ciphertext, old_key)
# Encrypt with new key
new_ciphertext = encrypt(plaintext, new_key)
# Write back
with open(file_path, 'wb') as f:
f.write(new_ciphertext)
Strategy 2: Envelope Encryption (Zero-Downtime Rotation)
How (using key hierarchy):
- Generate new KEK (Key Encryption Key)
- Re-encrypt all DEKs with new KEK
- Data stays encrypted with DEKs (no change)
- Update KEK references
- Delete old KEK
Pros: Fast (only re-encrypt DEKs, not data), no downtime Cons: Requires key hierarchy
Use Case: Large datasets, cloud KMS.
Flow:
Before Rotation:
┌────────────────┐
│ Old KEK │
└───────┬────────┘
│
▼
┌───────────────
…(truncated)