tegrity and confidentiality simultaneously. Avoid CBC mode due to padding oracle vulnerabilities.
* In Transit: TLS 1.3. Disable TLS 1.2 and below. Enforce forward secrecy via ECDHE key exchange.
3. Configure KMS: Create a Customer Master Key (CMK) or KEK in your cloud provider's KMS. Enable automatic key rotation (typically 365 days). Restrict key usage via least-privilege IAM policies.
4. Implement Envelope Encryption Pattern:
* Generate a unique DEK for each data item or logical partition.
* Encrypt the payload with the DEK.
* Encrypt the DEK with the KEK using the KMS API.
* Store the encrypted payload and the encrypted DEK together. Never store the plaintext DEK.
5. Integrate TLS: Configure all service-to-service communication with mutual TLS (mTLS) where possible. Use service mesh sidecars for transparent encryption if mTLS is too complex to implement manually.
Code Example: Envelope Encryption in TypeScript
This example demonstrates the envelope encryption pattern using Node.js crypto module and a mock KMS interface. In production, replace the mock KMS with AWS SDK, GCP Cloud KMS, or Azure Key Vault client.
import crypto from 'crypto';
import { KMSClient, EncryptCommand, DecryptCommand } from "@aws-sdk/client-kms";
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12; // 96 bits is recommended for GCM
const TAG_LENGTH = 16;
interface EncryptedBlob {
ciphertext: string;
encryptedKey: string;
iv: string;
tag: string;
}
class EnvelopeEncryptionService {
private kms: KMSClient;
private keyId: string;
constructor(kmsRegion: string, keyId: string) {
this.kms = new KMSClient({ region: kmsRegion });
this.keyId = keyId;
}
/**
* Encrypts data using envelope encryption.
* Returns a blob containing the encrypted data, encrypted DEK, IV, and auth tag.
*/
async encrypt(plaintext: string): Promise<EncryptedBlob> {
// 1. Generate a unique DEK for this operation
const dek = crypto.randomBytes(32);
const iv = crypto.randomBytes(IV_LENGTH);
// 2. Encrypt data with DEK
const cipher = crypto.createCipheriv(ALGORITHM, dek, iv);
let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
ciphertext += cipher.final('base64');
const tag = cipher.getAuthTag();
// 3. Encrypt DEK with KEK via KMS
// In production, use the AWS SDK or equivalent
const encryptedKeyBuffer = await this.wrapKeyWithKMS(dek);
return {
ciphertext,
encryptedKey: encryptedKeyBuffer.toString('base64'),
iv: iv.toString('base64'),
tag: tag.toString('base64'),
};
}
/**
* Decrypts an encrypted blob.
* Requires access to KMS to unwrap the DEK.
*/
async decrypt(blob: EncryptedBlob): Promise<string> {
// 1. Unwrap DEK using KEK from KMS
const encryptedKeyBuffer = Buffer.from(blob.encryptedKey, 'base64');
const dek = await this.unwrapKeyWithKMS(encryptedKeyBuffer);
// 2. Decrypt data with DEK
const iv = Buffer.from(blob.iv, 'base64');
const tag = Buffer.from(blob.tag, 'base64');
const decipher = crypto.createDecipheriv(ALGORITHM, dek, iv);
decipher.setAuthTag(tag);
let plaintext = decipher.update(blob.ciphertext, 'base64', 'utf8');
plaintext += decipher.final('utf8');
return plaintext;
}
// Mock KMS operations. Replace with actual SDK calls.
private async wrapKeyWithKMS(key: Buffer): Promise<Buffer> {
const command = new EncryptCommand({
KeyId: this.keyId,
Plaintext: key,
});
const response = await this.kms.send(command);
return response.CiphertextBlob as Buffer;
}
private async unwrapKeyWithKMS(encryptedKey: Buffer): Promise<Buffer> {
const command = new DecryptCommand({
CiphertextBlob: encryptedKey,
});
const response = await this.kms.send(command);
return response.Plaintext as Buffer;
}
}
Architecture Decisions and Rationale
- Per-Item DEK vs. Shared DEK: Generate a unique DEK for each sensitive record. This limits the blast radius. If a DEK is compromised, only one record is exposed. If you use a shared DEK for a table, the entire dataset is at risk. The storage overhead is minimal (the encrypted DEK is ~256 bytes per record).
- KMS Integration: Use a managed KMS rather than self-managed HSMs unless regulatory requirements dictate physical control. Managed KMS provides high availability, automated rotation, and detailed audit logs via CloudTrail or equivalent.
- TLS Termination: Terminate TLS as close to the application as possible. Avoid terminating at the CDN or load balancer unless the internal network is fully trusted and encrypted via mTLS. Ideally, use mTLS between the load balancer and backend services.
- Key Rotation: Implement "Graceful Rotation." When a KEK rotates, existing data encrypted with old DEKs remains valid. New data uses the new KEK. The KMS handles the versioning transparently during decryption.
Pitfall Guide
1. Hardcoding Keys in Source Code
Mistake: Developers embed encryption keys in environment variables checked into version control or hardcoded in binary assets.
Impact: Any developer with repository access can decrypt production data. Automated scanners will flag this, but human error persists.
Fix: Keys must never exist in source code. Use KMS, HashiCorp Vault, or cloud secret managers. Access keys via runtime IAM roles, not static credentials.
2. Reusing Initialization Vectors (IVs)
Mistake: Using a static IV or reusing an IV with the same key in AES-GCM or AES-CBC.
Impact: In GCM, IV reuse destroys confidentiality and integrity, allowing attackers to recover the keystream and forge messages. In CBC, IV reuse enables pattern analysis.
Fix: Always generate a cryptographically random IV for every encryption operation. The IV does not need to be secret but must be unique per key.
3. Neglecting Key Rotation Policies
Mistake: Creating a KMS key and never rotating it.
Impact: Long-lived keys increase the window of exposure if a key is leaked. Compliance frameworks (PCI-DSS, SOC2) mandate rotation.
Fix: Enable automatic key rotation in KMS. For application-layer keys, implement rotation logic that re-encrypts data or marks old keys for decryption-only access.
4. Logging Encrypted Data with Keys
Mistake: Logging the plaintext key alongside the encrypted payload for debugging, or logging the decrypted value in error messages.
Impact: Log aggregation systems become a high-value target. SIEM tools may inadvertently store secrets.
Fix: Implement strict log filtering. Never log keys. If debugging is required, use tokenization or masking. Ensure log storage is encrypted and access-controlled.
5. Relying Solely on Infrastructure Encryption
Mistake: Assuming AWS S3 SSE-S3 protects data from the cloud provider or insider threats.
Impact: The provider holds the keys. A malicious insider at the provider or a compromised admin account can access data.
Fix: Use Customer Managed Keys (CMK) or Client-Side Encryption for high-sensitivity data. This ensures the provider cannot access data without explicit customer authorization.
6. TLS Downgrade Attacks
Mistake: Allowing TLS 1.0/1.1 or weak cipher suites for backward compatibility.
Impact: Attackers can force a connection to use a vulnerable protocol version, enabling POODLE or BEAST attacks.
Fix: Configure servers to enforce TLS 1.2 minimum, preferably TLS 1.3. Use tools like testssl.sh to validate configurations. Disable weak ciphers like RC4, 3DES, and CBC modes in TLS.
Mistake: Encrypting the payload but leaving metadata (headers, filenames, database indexes) in plaintext.
Impact: Metadata can reveal sensitive information. For example, an index on an encrypted email field might allow frequency analysis to infer content.
Fix: Encrypt metadata where possible. Use deterministic encryption or searchable encryption techniques if indexing is required, understanding the trade-offs in security strength.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| SaaS Multi-Tenant App | Envelope Encryption with Tenant-Specific DEKs | Isolates tenant data mathematically. Compromise of one tenant's key does not affect others. | Moderate (KMS API calls per operation) |
| Internal Analytics Dashboard | Infrastructure Encryption (SSE-KMS) | Sufficient for internal trust boundaries. Lower operational overhead. | Low |
| HIPAA/GDPR Regulated Data | Envelope Encryption + mTLS | Meets strict confidentiality and integrity requirements. Provides audit trail for key access. | High (Development effort + KMS costs) |
| High-Throughput IoT Stream | TLS 1.3 + Batch Encryption at Edge | Reduces per-message overhead. Data encrypted in batches before cloud ingestion. | Low (Edge compute cost) |
| Legacy System Migration | Transparent Data Encryption (TDE) + TLS | Minimizes code changes. Encrypts storage volumes and connections without app refactoring. | Low (Infrastructure only) |
Configuration Template
Terraform: AWS KMS Key and S3 Bucket with Envelope Encryption Support
resource "aws_kms_key" "app_data_key" {
description = "Key for envelope encryption of application data"
deletion_window_in_days = 30
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Id = "key-policy"
Statement = [
{
Sid = "EnableRootAccount"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowAppRole"
Effect = "Allow"
Principal = {
AWS = aws_iam_role.app_role.arn
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
]
Resource = "*"
}
]
})
tags = {
Environment = "production"
Purpose = "envelope-encryption"
}
}
resource "aws_kms_alias" "app_data_key_alias" {
name = "alias/app-data-key"
target_key_id = aws_kms_key.app_data_key.key_id
}
resource "aws_s3_bucket" "secure_data" {
bucket = "my-secure-data-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "secure_data" {
bucket = aws_s3_bucket.secure_data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.app_data_key.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_policy" "secure_data" {
bucket = aws_s3_bucket.secure_data.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyUnencryptedUploads"
Effect = "Deny"
Principal = "*"
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.secure_data.arn}/*"
Condition = {
StringNotEquals = {
"s3:x-amz-server-side-encryption" = "aws:kms"
}
}
}
]
})
}
Quick Start Guide
- Create KMS Key: In your cloud console, create a Customer Managed Key. Enable automatic rotation. Note the Key ID.
- Update Storage Config: Configure your database or object storage to use the KMS Key for server-side encryption. Enforce encryption via bucket/database policies.
- Integrate SDK: Add the KMS SDK to your application. Implement the envelope encryption helper class (as shown in Core Solution). Replace direct storage writes with encrypted blobs.
- Verify: Write a test record. Confirm the stored data is ciphertext. Attempt to read the record; verify decryption works. Check KMS audit logs to confirm key usage is recorded.
- Enforce Transit: Update application connection strings to use
?sslmode=require or equivalent. Validate TLS configuration using openssl s_client or browser developer tools.