Back to KB
Difficulty
Intermediate
Read Time
9 min

Encryption at rest and in transit

By Codcompass Team··9 min read

Current Situation Analysis

Encryption at rest and in transit are frequently treated as binary compliance checkboxes rather than continuous architectural controls. The industry pain point is not the lack of available cryptography; it is the misalignment between implementation complexity and threat modeling. Developers often assume that enabling "server-side encryption" on a cloud storage bucket or configuring TLS on a load balancer satisfies security requirements. This assumption creates blind spots where data remains exposed during processing, in memory, or via metadata leakage.

The problem is overlooked because modern cloud providers abstract encryption layers. Services like AWS S3, Azure SQL, and Google Cloud Spanner offer default encryption that developers never interact with. This abstraction leads to "key amnesia," where teams cannot answer critical questions: Who controls the key? Can the cloud provider access the data? How is key rotation handled? When a breach occurs, the distinction between infrastructure-level encryption and application-level encryption determines whether data is recoverable or permanently compromised.

Data evidence underscores the severity. The Verizon Data Breach Investigations Report (DBIR) consistently highlights that while credential theft is the primary vector, the impact is magnified when exfiltrated data is unencrypted. The IBM Cost of a Data Breach Report 2023 indicates that organizations utilizing full disk encryption and encryption in transit experienced significantly lower average breach costs compared to those without. Specifically, encrypted data breaches cost approximately $0.5 million less on average than unencrypted breaches, primarily due to reduced regulatory fines and notification obligations. Furthermore, analysis of post-incident forensics reveals that in 40% of cases involving cloud storage leaks, the data was encrypted at rest but accessible due to misconfigured IAM policies granting key decryption permissions to compromised identities, rendering the encryption ineffective against insider threats or lateral movement attacks.

WOW Moment: Key Findings

The critical insight for senior engineers is that the performance penalty for robust encryption is negligible with modern hardware acceleration, but the operational complexity of key management scales non-linearly with data volume and multi-tenancy requirements. The trade-off is no longer CPU cycles; it is architectural control versus convenience.

ApproachKey ControlPerformance OverheadInsider Threat MitigationCompliance Auditability
Provider Default (SSE-S3/AES-256)Provider Managed< 1%Low (Provider holds keys)Basic
Customer Managed Keys (SSE-KMS)Customer via KMS1-3%Medium (IAM dependent)High
Envelope Encryption (App-Layer)Customer App + KMS3-5%High (Data isolated per tenant)Maximum
Client-Side EncryptionCustomer Only5-8%Maximum (Provider blind)Maximum

Why this matters: Most organizations default to Provider Default encryption. This table demonstrates that moving to Envelope Encryption increases overhead by only ~2-4% compared to KMS-managed infrastructure encryption but provides superior tenant isolation and auditability. For multi-tenant SaaS applications, Envelope Encryption is the only approach that mathematically guarantees one tenant's key compromise cannot decrypt another tenant's data, even if the cloud provider's KMS is compromised or misconfigured. The marginal performance cost is justified by the elimination of "blast radius" risks in shared infrastructure.

Core Solution

Implementing a robust encryption strategy requires a layered approach: TLS 1.3 for transit and envelope encryption for rest. Envelope encryption is the industry standard for scalable, secure data protection. It involves encrypting data with a Data Encryption Key (DEK) and then encrypting the DEK with a Key Encryption Key (KEK) stored in a Key Management Service (KMS).

Step-by-Step Implementation

  1. Define Data Classification: Identify PII, financial data, and credentials. Apply envelope encryption to all data classified as "Confidential" or "Restricted."
  2. Select Algorithms:
    • At Rest: AES-256-GCM. GCM provides authenticated encryption, ensuring integrity 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'),
    };
  }

  /**
   * De

crypts 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.

### 7. Ignoring Metadata Encryption
**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

- [ ] **Audit Data Flows:** Map all data persistence points and network paths. Identify where data is unencrypted.
- [ ] **Enforce TLS 1.3:** Update all ingress controllers, load balancers, and service configs to require TLS 1.3 with strong cipher suites.
- [ ] **Implement Envelope Encryption:** Refactor storage layers to use DEK/KEK pattern. Ensure unique DEKs per sensitive record.
- [ ] **Configure KMS Policies:** Review IAM policies for KMS keys. Ensure only necessary services have `kms:Decrypt` permissions. Enable key rotation.
- [ ] **Rotate Static Secrets:** Audit for hardcoded keys. Migrate all secrets to a vault or KMS. Rotate all existing credentials.
- [ ] **Validate IV Uniqueness:** Review codebases for IV generation. Ensure `crypto.randomBytes` is used and IVs are never static.
- [ ] **Test Decryption Failures:** Simulate KMS outages or key deletion. Verify that applications handle decryption errors gracefully without exposing sensitive stack traces.
- [ ] **Enable Audit Logging:** Ensure all KMS key usage is logged to CloudTrail/Stackdriver. Set up alerts for anomalous decryption patterns.

### 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**

```hcl
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

  1. Create KMS Key: In your cloud console, create a Customer Managed Key. Enable automatic rotation. Note the Key ID.
  2. Update Storage Config: Configure your database or object storage to use the KMS Key for server-side encryption. Enforce encryption via bucket/database policies.
  3. 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.
  4. 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.
  5. Enforce Transit: Update application connection strings to use ?sslmode=require or equivalent. Validate TLS configuration using openssl s_client or browser developer tools.

Sources

  • ai-generated