Building Defense-in-Depth Encryption: A Cascading Cipher System
⚠️ NOTE: This document and related project is not finished. The details in this document are subject to change.
What if you could combine multiple encryption algorithms like layers of an onion, where each layer provides independent protection? What if breaking one encryption layer still left your data protected by two or three more?
In this article, we'll explore how to build a cascading cipher system that chains multiple encryption algorithms together for defense-in-depth security. We'll walk through a browser-based JavaScript implementation that combines MLS (Message Layer Security), Signal Protocol's Double Ratchet, Diffie-Hellman key exchange, and AES-GCM encryption—all working together to create a robust, multi-layered encryption solution.
Introduction to Cascading Ciphers
A cascading cipher (also known as multiple encryption or superencryption) is a cryptographic technique where plaintext is encrypted multiple times using different algorithms or keys. Think of it like securing a treasure with multiple locks, where each lock requires a different key.
The Core Concept
Each encryption layer:
- Processes the output of the previous layer
- Uses different keys and algorithms
- Operates independently of other layers
- Provides its own security guarantees
Why Cascade Encryption?
✅ Defense in Depth
- Multiple independent security barriers
- Compromise of one layer doesn't expose plaintext
- Hedging against algorithm-specific vulnerabilities
✅ Algorithm Diversity
- Combine different cryptographic primitives
- Mix authenticated encryption (AES-GCM) with key agreement (DH)
- Layer group security (MLS) with forward secrecy (Signal)
✅ Future-Proofing
- Protection against future algorithmic breaks
- If quantum computers break one layer, others remain
- Easy to add new layers as algorithms evolve
✅ Flexible Security Levels
- Add more layers for highly sensitive data
- Use fewer layers for performance-critical applications
- Configure different layer combinations for different use cases
Real-World Analogies
🏦 Bank Vault Security
- Outer perimeter fence (first layer)
- Building access control (second layer)
- Vault door combination (third layer)
- Safe deposit box lock (fourth layer)
Each layer provides independent protection—breaking one doesn't automatically compromise the others.
🧅 Onion Routing (Tor Network)
- Multiple layers of encryption
- Each relay only decrypts one layer
- Final destination requires all layers to be decrypted
- Similar concept to our cascading cipher
The Four Layers We'll Build
In this article, we'll implement a cascading cipher system with four production-ready layers:
- MLS (Message Layer Security) - RFC 9420 compliant group encryption
- Signal Protocol - Double Ratchet for forward secrecy
- Diffie-Hellman - ECDH key exchange with AES-GCM
- AES-GCM - Fast symmetric encryption with password-based keys
By the end, you'll have a complete, extensible encryption system that you can use in your own applications.
The Problem with Single-Layer Encryption
While modern encryption algorithms like AES-256 are incredibly strong, relying on a single layer of encryption introduces several risks:
Algorithm-Specific Vulnerabilities
Every encryption algorithm has potential weaknesses:
Examples of historical breaks:
- DES - Originally 56-bit, broken by brute force
- RC4 - Biases in keystream discovered
- MD5 - Collision attacks demonstrated
- SHA-1 - Practical collision attacks possible
Modern concerns:
- Quantum computing - Threatens RSA, ECC, and DH
- Side-channel attacks - Timing, power analysis, cache attacks
- Implementation bugs - Heartbleed, padding oracles, etc.
Single Point of Failure
With single-layer encryption, if that one layer fails, everything fails:
| Threat | Single-Layer Impact | Multi-Layer Impact |
|---|---|---|
| Algorithm break | ❌ Total compromise | ✅ Other layers still protect |
| Key leak | ❌ All data exposed | ✅ Need all keys to decrypt |
| Implementation bug | ❌ Bypass possible | ✅ Bug in one layer doesn't affect others |
| Quantum computing | ❌ Could break RSA/ECC | ✅ Mix quantum-resistant algorithms |
| Cryptanalysis advance | ❌ Single target for attacks | ✅ Must break multiple algorithms |
The Quantum Computing Threat
Quantum computers pose a significant future threat to current encryption:
Vulnerable to Quantum Attacks:
- ❌ RSA (Shor's algorithm)
- ❌ Elliptic Curve Cryptography (Shor's algorithm)
- ❌ Diffie-Hellman key exchange (Shor's algorithm)
Quantum-Resistant:
- ✅ AES-256 (Grover's algorithm only reduces to AES-128 equivalent)
- ✅ SHA-256/SHA-3 (quantum advantage limited)
- ✅ Lattice-based cryptography (believed quantum-resistant)
Defense Strategy: By cascading multiple layers including both quantum-vulnerable (ECC) and quantum-resistant (AES-256) algorithms, you hedge against future quantum threats.
Comparison: Single vs. Multi-Layer
The Cost-Benefit Analysis
Single-Layer Encryption:
- ✅ Fast - One encryption operation
- ✅ Simple - Easy to implement
- ❌ Risky - Single point of failure
- ❌ Not future-proof - Vulnerable to advances in cryptanalysis
Multi-Layer Encryption:
- ⚠️ Slower - Multiple encryption operations (but manageable: ~30-60ms)
- ⚠️ Complex - More code and key management
- ✅ Resilient - Multiple independent security barriers
- ✅ Future-proof - Hedges against algorithm breaks
- ✅ Defense in depth - Each layer provides independent protection
For high-security applications, the trade-off is clear: the modest performance cost is worth the significant security benefits.
When to Use Cascading Encryption
✅ Perfect Use Cases:
- Financial transactions
- Healthcare data
- Government communications
- Long-term data storage (data needs to stay secure for years)
- High-value intellectual property
- P2P encrypted messaging
⚠️ Consider Alternatives:
- Public website assets (no need for encryption)
- Low-latency streaming (single-layer AES-GCM sufficient)
- IoT devices with limited CPU (resource constraints)
- Throwaway/ephemeral data (not worth the complexity)
Architecture Overview
The cascading cipher system consists of a central manager that orchestrates multiple cipher layers. Each layer implements a standard interface, making the system extensible and modular.
System Architecture
Encryption Flow
During encryption, data flows through each layer sequentially:
The encryption process:
- Application provides plaintext and keys for all layers
- Manager validates keys and initializes processing
- Layer 1 (MLS) encrypts plaintext → intermediate ciphertext 1
- Layer 2 (Signal) encrypts intermediate 1 → intermediate ciphertext 2
- Layer 3 (DH) encrypts intermediate 2 → intermediate ciphertext 3
- Layer 4 (AES) encrypts intermediate 3 → final ciphertext
- Manager collects metadata from all layers and returns complete payload
Decryption Flow
Decryption reverses the process, applying layers in opposite order:
The decryption process:
- Application provides cascaded payload and keys
- Manager validates layer count and structure
- Layer 4 (AES) decrypts final ciphertext → intermediate 3
- Layer 3 (DH) decrypts intermediate 3 → intermediate 2
- Layer 2 (Signal) decrypts intermediate 2 → intermediate 1
- Layer 1 (MLS) decrypts intermediate 1 → original plaintext
- Manager returns plaintext to application
Core Components
CascadingCipherManager
The central orchestrator that:
- Manages the ordered list of cipher layers
- Routes data through layers during encryption
- Reverses the order during decryption
- Tracks metadata (timing, sizes, etc.)
- Handles errors and reports which layer failed
Key Methods:
class CascadingCipherManager {
addLayer(layer: CipherLayer): void
removeLayer(layerName: string): void
encrypt(plaintext: Uint8Array, keys: CipherKeys): Promise<CascadedPayload>
decrypt(payload: CascadedPayload, keys: CipherKeys): Promise<Uint8Array>
}
CipherLayer Interface
The standard interface that all cipher layers must implement:
interface CipherLayer {
readonly name: string;
readonly version: string;
encrypt(data: Uint8Array, keys: any): Promise<EncryptedPayload>;
decrypt(payload: EncryptedPayload, keys: any): Promise<Uint8Array>;
validateKeys?(keys: any): boolean;
initialize?(config: any): Promise<void>;
destroy?(): Promise<void>;
}
This interface ensures:
- ✅ Consistency - All layers work the same way
- ✅ Extensibility - Easy to add new cipher layers
- ✅ Type safety - TypeScript enforces the contract
- ✅ Testability - Mock layers for testing
CascadedPayload Structure
The result of cascading encryption contains everything needed for decryption:
interface CascadedPayload {
finalCiphertext: Uint8Array; // The fully encrypted data
layers: LayerMetadata[]; // Metadata for each layer
layerParameters: Record<string, any>[]; // Decryption parameters per layer
totalProcessingTime: number; // Total time taken (ms)
originalSize: number; // Plaintext size
finalSize: number; // Final ciphertext size
timestamp: number; // When encrypted
}
Data Flow Visualization
Here's what happens to a message as it flows through all four layers:
Size Overhead:
- MLS: ~16 bytes (authentication tag + metadata)
- Signal: ~36 bytes (DH public key + message number + auth tag)
- DH: ~32 bytes (salt + IV + auth tag)
- AES: ~28 bytes (salt + IV + auth tag)
- Total overhead: ~112 bytes (for this example)
The overhead is fixed per layer, so for larger messages (e.g., 1 MB file), the percentage overhead becomes negligible.
Layer Independence
One of the key benefits of this architecture is layer independence:
Each layer:
- Uses different keys
- Employs different algorithms
- Has different security properties
- Can be broken independently without compromising the others
This is the essence of defense in depth.
Available Cipher Layers
The cascading cipher system ships with four production-ready cipher layers. Each layer provides unique security properties and can be used independently or combined with others.
1. AESCipherLayer - Fast Symmetric Encryption
AES-GCM-256 with password-based key derivation (PBKDF2).
Security Properties
- ✅ Authenticated encryption - Detects tampering
- ✅ 256-bit key length - Strong symmetric security
- ✅ Password-based - Derive keys from human-memorable passwords
- ✅ Quantum-resistant - AES-256 remains strong against quantum attacks
- ❌ No forward secrecy - Same password = same key
How It Works
Code Example
// Reference: AESCipherLayer.ts in cryptography module
import { AESCipherLayer } from 'cryptography/CascadingCipher';
// Create AES layer
const aesLayer = new AESCipherLayer();
// Prepare data and keys
const plaintext = new TextEncoder().encode('Secret message');
const keys = { password: 'my-secure-password-123' };
// Encrypt
const encrypted = await aesLayer.encrypt(plaintext, keys);
// Returns:
// {
// ciphertext: Uint8Array,
// layerMetadata: {
// algorithm: 'AES-GCM-256',
// version: '1.0.0',
// timestamp: 1699564800000,
// inputSize: 14,
// outputSize: 42,
// processingTime: 8.5,
// metadata: {
// keyDerivation: 'PBKDF2',
// iterations: 100000
// }
// },
// parameters: {
// iv: Uint8Array(12),
// salt: Uint8Array(16)
// }
// }
// Decrypt
const decrypted = await aesLayer.decrypt(encrypted, keys);
// Returns: Uint8Array of original plaintext
Use Cases
- Fast baseline encryption when you need speed
- Password-protected data for user-encrypted content
- Outermost layer in cascading setup (fastest layer should be outermost)
- File encryption with user-provided passwords
Performance
- Key derivation: ~15-20ms (PBKDF2 with 100,000 iterations)
- Encryption: ~0.5-2ms (hardware-accelerated AES)
- Total: ~20-25ms per operation
2. DHCipherLayer - P2P Key Agreement
ECDH-P256 key exchange with HKDF-SHA256 key derivation and AES-GCM-256 encryption.
Security Properties
- ✅ Shared secret derivation - Two parties derive same key without sharing it
- ✅ Perfect forward secrecy (if ephemeral keys used)
- ✅ Elliptic curve cryptography - Efficient P2P key exchange
- ✅ HKDF key derivation - Proper key material expansion
- ⚠️ Quantum-vulnerable - ECDH can be broken by quantum computers
- ❌ No authentication (unless combined with signatures)
How It Works
Code Example
// Reference: DHCipherLayer.ts in cryptography module
import { DHCipherLayer } from 'cryptography/CascadingCipher';
// Generate DH key pairs for Alice and Bob
const aliceKeyPair = await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveKey', 'deriveBits']
);
const bobKeyPair = await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveKey', 'deriveBits']
);
// Create DH layer
const dhLayer = new DHCipherLayer();
// Alice encrypts using her private key and Bob's public key
const plaintext = new TextEncoder().encode('Secret message');
const aliceKeys = {
privateKey: aliceKeyPair.privateKey,
publicKey: bobKeyPair.publicKey
};
const encrypted = await dhLayer.encrypt(plaintext, aliceKeys);
// Returns:
// {
// ciphertext: Uint8Array,
// layerMetadata: {
// algorithm: 'DH-AES-GCM',
// version: '1.0.0',
// timestamp: 1699564800000,
// inputSize: 14,
// outputSize: 46,
// processingTime: 12.3,
// metadata: {
// keyExchange: 'ECDH-P256',
// keyDerivation: 'HKDF-SHA256',
// encryption: 'AES-GCM-256'
// }
// },
// parameters: {
// iv: Uint8Array(12),
// salt: Uint8Array(16),
// usedPreSharedSecret: false
// }
// }
// Bob decrypts using his private key and Alice's public key
const bobKeys = {
privateKey: bobKeyPair.privateKey,
publicKey: aliceKeyPair.publicKey
};
const decrypted = await dhLayer.decrypt(encrypted, bobKeys);
// Returns: Original plaintext
Alternative: Pre-derived Shared Secret
If you've already performed Diffie-Hellman key exchange separately, you can provide the shared secret directly:
// Perform DH exchange manually
const sharedSecretBits = await crypto.subtle.deriveBits(
{ name: 'ECDH', public: bobKeyPair.publicKey },
aliceKeyPair.privateKey,
256
);
const sharedSecret = new Uint8Array(sharedSecretBits);
// Use shared secret directly (faster - skips DH computation)
const keys = { sharedSecret };
const encrypted = await dhLayer.encrypt(plaintext, keys);
Use Cases
- P2P messaging - Secure communication between two peers
- Key agreement layer - Establish shared keys for other protocols
- WebRTC data channels - Secure P2P data transfer
- Collaborative tools - Screen sharing, whiteboard, etc.
Performance
- ECDH key exchange: ~5-10ms
- HKDF key derivation: ~1-2ms
- AES-GCM encryption: ~0.5-2ms
- Total: ~10-15ms per operation
3. MLSCipherLayer - Group Encryption
MLS (Message Layer Security) - RFC 9420 compliant group encryption protocol.
Security Properties
- ✅ End-to-end group encryption - Secure group messaging
- ✅ Forward secrecy - Past messages stay secure
- ✅ Post-compromise security - Recovers from key compromise
- ✅ Group member authentication - Verify all participants
- ✅ Dynamic group management - Add/remove members securely
- ✅ RFC 9420 compliant - Standardized protocol
How It Works
MLS uses a ratchet tree to derive shared group keys. Each epoch (round of updates) generates new keys, providing forward secrecy at the group level.
Code Example
// Reference: MLSCipherLayer.ts in cryptography module
import { MLSManager } from 'cryptography/Cryptography';
import { MLSCipherLayer } from 'cryptography/CascadingCipher';
// Initialize MLS manager
const mlsManager = new MLSManager('alice@example.com');
await mlsManager.initialize();
// Create a group
const groupId = 'my-secure-group';
await mlsManager.createGroup(groupId);
// Add Bob to the group (requires Bob's key package)
// await mlsManager.addMember(groupId, bobKeyPackage);
// Create MLS cipher layer
const mlsLayer = new MLSCipherLayer(mlsManager, groupId);
// Encrypt a message for the group
const plaintext = new TextEncoder().encode('Secret group message');
const keys = { mlsManager, groupId };
const encrypted = await mlsLayer.encrypt(plaintext, keys);
// Returns:
// {
// ciphertext: Uint8Array,
// layerMetadata: {
// algorithm: 'MLS',
// version: '1.0.0',
// timestamp: 1699564800000,
// inputSize: 21,
// outputSize: 89,
// processingTime: 25.7,
// metadata: {
// groupId: 'my-secure-group',
// epoch: 5,
// memberCount: 3,
// cipherSuite: 'MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519'
// }
// },
// parameters: {
// groupId: 'my-secure-group',
// senderIndex: 0,
// generation: 12
// }
// }
// Any group member can decrypt
const decrypted = await mlsLayer.decrypt(encrypted, keys);
// Returns: Original plaintext
MLS Features
Group Management:
// Add a member
await mlsManager.addMember(groupId, newMemberKeyPackage);
// Remove a member
await mlsManager.removeMember(groupId, memberId);
// Update own key (trigger new epoch)
await mlsManager.updateKey(groupId);
Key Rotation: MLS automatically rotates keys on group changes:
- Adding a member → New epoch → New keys
- Removing a member → New epoch → New keys
- Member key update → New epoch → New keys
This provides forward secrecy and post-compromise security at the group level.
Use Cases
- Group chats - Secure messaging for 3+ participants
- Team collaboration - Shared workspaces with E2E encryption
- Conference calls - Encrypted group video/audio
- Broadcast messaging - Secure one-to-many communication
Performance
- Group encryption: ~20-40ms (depends on group size)
- Key derivation: ~5-10ms
- Ratchet tree operations: ~10-20ms
- Total: ~30-60ms per operation
