Skip to main content

8 posts tagged with "encryption"

View All Tags

Building Defense-in-Depth Encryption: A Cascading Cipher System

· 49 min read
xoron
positive-intentions

⚠️ 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.

File Storage with useFS Hook in Functional Web Components

· 12 min read
xoron
positive-intentions

Building on the functional web components framework we've been developing in this series, today we're tackling one of the most powerful capabilities modern browsers offer: file system access. The useFS hook brings unified file storage to dim, bridging the File System Access API and Origin Private File System (OPFS) with optional encryption—all wrapped in a clean, functional interface.

ML-KEM for Beginners: A Complete Guide to Quantum-Resistant Encryption

· 11 min read
xoron
positive-intentions

Have you ever wondered how to protect your data from future quantum computers? What if I told you there's a way to encrypt messages that even quantum computers can't break?

In this beginner-friendly tutorial, we'll explore ML-KEM (Module-Lattice Key Encapsulation Mechanism), a quantum-resistant encryption algorithm that's becoming the new standard for secure communication. By the end of this guide, you'll understand what ML-KEM is, how it works, and how to use it in your own applications.

What is ML-KEM?

ML-KEM (formerly known as CRYSTALS-Kyber) is a post-quantum cryptographic algorithm standardized by NIST (National Institute of Standards and Technology). It's designed to be secure against attacks from both classical computers and quantum computers.

Why Do We Need Quantum-Resistant Encryption?

Most encryption algorithms used today (like RSA and ECC) rely on mathematical problems that are hard for classical computers but easy for quantum computers to solve. When quantum computers become powerful enough, they could break current encryption methods.

ML-KEM solves this problem by using mathematical structures (lattices) that are believed to be hard for both classical and quantum computers to break.

Key Concepts for Beginners

Before we dive into code, let's understand a few key concepts:

  1. Key Encapsulation Mechanism (KEM): Instead of directly encrypting data, ML-KEM creates a shared secret key that can then be used with symmetric encryption (like AES).

  2. Public Key vs Private Key:

    • Public key: Anyone can use this to encrypt messages to you (like a mailbox address)
    • Private key: Only you have this, and you use it to decrypt messages (like a mailbox key)
  3. Encapsulation: The process of creating an encrypted "package" containing a shared secret key

  4. Decapsulation: The process of extracting the shared secret key from the encrypted package

How ML-KEM Works: The Big Picture

Here's a simplified overview of how ML-KEM encryption works:

Getting Started: Installation

To use ML-KEM in your JavaScript/TypeScript project, you'll need the ML-KEM library. In our implementation, we use @hpke/ml-kem:

npm install @hpke/ml-kem

Step-by-Step Tutorial

Let's walk through a complete example of using ML-KEM, step by step.

Step 1: Generate a Key Pair

First, we need to generate a public/private key pair. The public key can be shared with anyone, while the private key must be kept secret.

import { MlKem768 } from "@hpke/ml-kem";

// Create an ML-KEM instance
const kem = new MlKem768();

// Generate a key pair
const keyPair = await kem.generateKeyPair();

console.log("Public key:", keyPair.publicKey);
console.log("Private key:", keyPair.privateKey);

What's happening here?

  • MlKem768 creates an ML-KEM instance using the 768-bit security level (equivalent to AES-192)
  • generateKeyPair() creates two keys:
    • Public key: 1184 bytes - safe to share publicly
    • Private key: 64 bytes - must be kept secret

Step 2: Encrypt a Message

Now let's encrypt a message using the public key. In ML-KEM, we don't encrypt the message directly. Instead, we:

  1. Create a shared secret using the public key (encapsulation)
  2. Use that shared secret to encrypt the message with AES-GCM
import { MLKEMCipherLayer } from "cryptography/CascadingCipher";

// Create the ML-KEM cipher layer
const layer = new MLKEMCipherLayer();

// The message we want to encrypt
const message = "Hello, Quantum-Resistant World!";
const plaintext = new TextEncoder().encode(message);

// Encrypt using the public key
const encrypted = await layer.encrypt(plaintext, {
publicKey: keyPair.publicKey
});

console.log("Encrypted:", encrypted);

What's in the encrypted result?

  • ciphertext: The encrypted message (AES-GCM encrypted)
  • parameters.encapsulated: The ML-KEM encapsulated key (1088 bytes)
  • parameters.iv: Initialization vector for AES (12 bytes)
  • parameters.salt: Salt for key derivation (16 bytes)

Step 3: Decrypt the Message

To decrypt, we use the private key to extract the shared secret, then use it to decrypt the message:

// Decrypt using the private key
const decrypted = await layer.decrypt(encrypted, {
privateKey: keyPair.privateKey
});

// Convert back to text
const decryptedMessage = new TextDecoder().decode(decrypted);
console.log("Decrypted:", decryptedMessage);
// Output: "Hello, Quantum-Resistant World!"

What's happening during decryption?

  1. ML-KEM extracts the shared secret from the encapsulated key using the private key (decapsulation)
  2. The shared secret is used to derive the AES key
  3. AES-GCM decrypts the ciphertext
  4. We get back the original message!

Complete Working Example

Here's a complete example that puts it all together:

import { MlKem768 } from "@hpke/ml-kem";
import { MLKEMCipherLayer } from "cryptography/CascadingCipher";

async function mlkemExample() {
// Step 1: Generate key pair
console.log("🔑 Generating ML-KEM key pair...");
const kem = new MlKem768();
const keyPair = await kem.generateKeyPair();
console.log("✅ Key pair generated!");
console.log(` Public key size: ${keyPair.publicKey.key.length} bytes`);
console.log(` Private key size: ${keyPair.privateKey.key.length} bytes`);

// Step 2: Encrypt a message
console.log("\n🔒 Encrypting message...");
const layer = new MLKEMCipherLayer();
const message = "This is a secret message!";
const plaintext = new TextEncoder().encode(message);

const encrypted = await layer.encrypt(plaintext, {
publicKey: keyPair.publicKey
});

console.log("✅ Message encrypted!");
console.log(` Encapsulated key size: ${encrypted.parameters.encapsulated.length} bytes`);
console.log(` Ciphertext size: ${encrypted.ciphertext.length} bytes`);

// Step 3: Decrypt the message
console.log("\n🔓 Decrypting message...");
const decrypted = await layer.decrypt(encrypted, {
privateKey: keyPair.privateKey
});

const decryptedMessage = new TextDecoder().decode(decrypted);
console.log("✅ Message decrypted!");
console.log(` Original: "${message}"`);
console.log(` Decrypted: "${decryptedMessage}"`);

// Verify they match
if (decryptedMessage === message) {
console.log("\n🎉 Success! Round-trip encryption/decryption works perfectly!");
}
}

// Run the example
mlkemExample().catch(console.error);

Output:

🔑 Generating ML-KEM key pair...
✅ Key pair generated!
Public key size: 1184 bytes
Private key size: 64 bytes

🔒 Encrypting message...
✅ Message encrypted!
Encapsulated key size: 1088 bytes
Ciphertext size: 48 bytes

🔓 Decrypting message...
✅ Message decrypted!
Original: "This is a secret message!"
Decrypted: "This is a secret message!"

🎉 Success! Round-trip encryption/decryption works perfectly!

Understanding the Encryption Process in Detail

Let's break down what happens under the hood when you encrypt with ML-KEM:

1. Key Encapsulation (Encryption Side)

// When you call layer.encrypt():
// Step 1: ML-KEM encapsulation
const { sharedSecret, enc } = await kem.encap({
recipientPublicKey: publicKey
});
// Result: sharedSecret (64 bytes) + enc (1088 bytes)

// Step 2: Derive AES key from shared secret
const salt = crypto.getRandomValues(new Uint8Array(16));
const aesKey = await deriveAESKey(sharedSecret, salt);

// Step 3: Encrypt message with AES-GCM
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await aesGcmEncrypt(message, aesKey, iv);

// Final result:
// - ciphertext: encrypted message
// - encapsulated: enc (1088 bytes)
// - salt: for key derivation
// - iv: for AES-GCM

2. Key Decapsulation (Decryption Side)

// When you call layer.decrypt():
// Step 1: ML-KEM decapsulation
const sharedSecret = await kem.decap({
recipientKey: privateKey,
enc: encrypted.parameters.encapsulated
});
// Result: sharedSecret (64 bytes) - same as encryption!

// Step 2: Derive AES key (same process as encryption)
const aesKey = await deriveAESKey(sharedSecret, encrypted.parameters.salt);

// Step 3: Decrypt message with AES-GCM
const plaintext = await aesGcmDecrypt(
encrypted.ciphertext,
aesKey,
encrypted.parameters.iv
);

Key Sizes and Performance

Understanding the sizes and performance characteristics helps you plan your application:

ComponentSizeNotes
Public Key1184 bytes (~1.16 KB)Safe to share publicly
Private Key64 bytesMust be kept secret
Encapsulated Key1088 bytes (~1.06 KB)Sent with each encrypted message
Shared Secret64 bytesUsed to derive AES key

Performance (typical values on modern hardware):

  • Key generation: ~5-15ms
  • Encryption (encapsulation): ~10-25ms
  • Decryption (decapsulation): ~10-25ms

Real-World Use Cases

ML-KEM is perfect for:

  1. Long-term data storage: Encrypt data that needs to stay secure for years
  2. Secure messaging: Protect messages from future quantum attacks
  3. File encryption: Encrypt files with quantum-resistant security
  4. API security: Secure API communications with post-quantum cryptography
  5. IoT devices: Protect devices that will be in use for many years

Common Questions

Q: Why is ML-KEM called a "Key Encapsulation Mechanism"?

A: ML-KEM doesn't encrypt your data directly. Instead, it creates an encrypted "package" (encapsulation) containing a shared secret key. This shared secret is then used with symmetric encryption (AES) to actually encrypt your data. This two-step process is more efficient than encrypting large amounts of data directly with public-key cryptography.

Q: Is ML-KEM slower than RSA or ECC?

A: ML-KEM operations are generally faster than RSA but slightly slower than ECC. However, the security benefit against quantum computers makes it worth the small performance cost. For most applications, the difference is negligible.

Q: Can I use ML-KEM with existing encryption?

A: Yes! ML-KEM works great alongside traditional encryption. Many systems use "hybrid" encryption: ML-KEM for key exchange + AES for data encryption. This provides defense-in-depth security.

Q: How do I store the keys securely?

A:

  • Public key: Can be stored anywhere (it's public!)
  • Private key: Should be stored encrypted, ideally in a hardware security module (HSM) or secure key storage
// Example: Store private key encrypted
const encryptedPrivateKey = await encryptKey(
privateKey.key,
userPassword
);
localStorage.setItem('mlkem_private_key', encryptedPrivateKey);

Security Best Practices

  1. Never share your private key: Keep it secret!
  2. Use secure random number generation: Always use crypto.getRandomValues() for salts and IVs
  3. Validate inputs: Check that keys and messages are the correct size
  4. Handle errors securely: Don't leak information about why decryption failed
  5. Rotate keys periodically: Generate new key pairs periodically for long-term security

Interactive Demo: Try It Yourself

Try encrypting and decrypting your own messages with this interactive demo:

What's Next?

Now that you understand the basics of ML-KEM, you can:

  1. Explore the implementation: Check out the ML-KEM cipher layer source code

  2. Learn about cascading ciphers: ML-KEM can be combined with other encryption layers for defense-in-depth security. Read our cascading cipher tutorial

  3. Try it in your project: Use ML-KEM in your own applications for quantum-resistant security

  4. Read the NIST standard: Learn more about the official ML-KEM specification (FIPS 203)

Summary

In this tutorial, we've learned:

  • What ML-KEM is: A quantum-resistant key encapsulation mechanism
  • Why it's important: Protects against future quantum computer attacks
  • How to use it: Generate keys, encrypt, and decrypt messages
  • How it works: Key encapsulation + AES-GCM encryption
  • Best practices: Security considerations and key management

ML-KEM is the future of secure encryption, and now you know how to use it! Whether you're building a messaging app, encrypting files, or securing API communications, ML-KEM provides the quantum-resistant security you need.

Have questions or want to learn more? Check out our cryptography repository or join our Discord community!


This tutorial is part of our ongoing research into post-quantum cryptography. For more technical deep-dives, check out our ML-KEM security audit documentation.

Building Scalable Group Messaging with MLS (Message Layer Security)

· 41 min read
xoron
positive-intentions

⚠️ WARNING: This document is not finished. The details in this document are subject to change.

End-to-end encrypted messaging for two people is a solved problem—Signal Protocol has set the gold standard. But what happens when you want to scale that security to group chats with dozens or hundreds of participants? Traditional pairwise encryption becomes a nightmare: N participants require N(N-1)/2 encrypted channels, each with its own key management overhead.

Enter MLS (Message Layer Security), the IETF's RFC 9420 standard designed specifically for scalable group messaging. MLS provides the same strong security guarantees as Signal Protocol—forward secrecy, post-compromise security, authentication—but does so efficiently for groups of any size.

In this article, we'll explore how MLS works, why it's a game-changer for group messaging, and walk through a complete browser-based implementation using the ts-mls library. We'll cover everything from the TreeKEM algorithm to practical P2P integration with WebRTC.

P2P Video Calls in Virtual Reality: A New Frontier for Decentralized Communication

· 8 min read
xoron
positive-intentions

We are excited to announce a transformative new feature in our app: Decentralized PeertoPeer (P2P) Video and Chat in Virtual Reality—directly within your browser as a web chat app. This feature represents a significant leap forward in enabling secure, immersive, and interactive communication experiences to let you chat instantly with anyone, anywhere.

Adapting the Signal Protocol for P2P Messaging

· 31 min read
xoron
positive-intentions

⚠️ WARNING: This document is not finished. The details in this document are subject to change.

Signal Protocol

The Signal Protocol has become the gold standard for end-to-end encrypted messaging, powering applications like WhatsApp, Signal, and Facebook Messenger. But what happens when you want to implement the same level of security in a truly peer-to-peer environment—one without centralized servers managing pre-keys and message routing?

In this article, we'll explore how to adapt the Signal Protocol's X3DH (Extended Triple Diffie-Hellman) key agreement and Double Ratchet algorithm for direct peer-to-peer communication over WebRTC. We'll discuss the challenges unique to P2P environments, propose practical solutions, and walk through a browser-based JavaScript implementation that maintains the security guarantees of the original protocol.

Security, Privacy and Authentication

· 7 min read
xoron
positive-intentions

In digital communication, security and privacy are the major initials. Our latest project—a chat application built with JavaScript—aims to provide a robust communication platform fortified with industry-grade encryption. While we aspire to offer one of the most secure chat experiences available, declaring it "the most secure chat app in the world" might be premature. This article delves into the technology behind our app, current security measures, and the remaining challenges. We also welcome feedback to help refine our approach.

Introducing Decentralized Chat

· 11 min read
xoron
positive-intentions
Positive Intentions Logo

Are you tired of compromising your privacy and security when sharing files online? What if there was a way to transfer data that was not only secure and efficient but also put you in complete control? Imagine a file sharing solution that combines cutting-edge encryption with the power of decentralized technology, all while being accessible from any device.

We've created just that. But how does our groundbreaking peer-to-peer file transfer app work, and what makes it different from traditional file sharing methods? Read on to discover how we're revolutionizing the way you share and store data, and why our approach to file sharing might be the game-changer you've been waiting for in the world of digital communication.