MLS Integration and Message Security
What is MLS (Messaging Layer Security)?
Section titled “What is MLS (Messaging Layer Security)?”MLS is the gold standard for group messaging security. It has been standardized as RFC 9420. A simple way to think about it is as “TLS for group chats”.
The protocol provides:
-
Forward Secrecy: Compromise of current keys doesn’t affect past messages.
-
Post-Compromise Security: New key material recovers security after a breach.
-
Group Key Agreement: Efficient key management for large groups.
-
Asynchronous Operation: Members can be added while offline.
-
Scalability: Supports groups consisting of thousands of members.
Why choose MLS over the Signal Protocol?
Section titled “Why choose MLS over the Signal Protocol?”Feature | Signal Protocol | MLS |
---|---|---|
Group Size | Efficient for small groups | Scales to 50,000+ members |
Forward Secrecy | Yes | Yes |
Post-Compromise Security | Yes | Yes |
Standardization | Signal-specific | IETF RFC 9420 |
Asynchronous Operation | Limited | Full support |
Future-Proofing | Signal controls | Industry standard |
MLS Cipher Suites
Section titled “MLS Cipher Suites”At the moment, Cryptid supports one MLS cipher suite:
- MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519
- Encryption: AES-128-GCM
- Best for: Modern devices with hardware AES acceleration
- Performance: Very fast with AES-NI or ARM Crypto Extensions
I’m exploring support for MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 for devices without AES hardware (older hardware, embedded systems). I’ll update this section once I decide.
Common components:
- Key Exchange: X25519 (DHKEM)
- Hash: SHA-256
- Signatures: Ed25519
- Security Level: 128-bit
Both cipher suites are equally secure. Clients negotiate during group creation based on hardware capabilities.
Message Format
Section titled “Message Format”All communication uses this unified message format:
struct SecureMessage { // Cryptographically random message_id: [u8; 32], // MLS group identifier group_id: [u8; 32], // MLS-encrypted payload mls_ciphertext: Vec<u8>, // Device signature over ciphertext sender_signature: Ed25519Signature, // Creation timestamp timestamp: u64, // Type information message_type: MessageType,}
enum MessageType { // UTF-8 text content Text, // File with metadata Media { mime_type: String }, // Group management SystemOperation, // Initial contact establishment ContactRequest,}
MLS Group Types
Section titled “MLS Group Types”Direct Message Groups (1:1 Chat)
Section titled “Direct Message Groups (1:1 Chat)”struct DirectMessageGroup { // SHA256 of sorted device IDs group_id: [u8; 32], // Exactly 2 devices participants: [DeviceIdentity; 2], // Current epoch and keys mls_state: MLSGroupState, created_at: u64,}
// Group ID is deterministic: same for both participantsfn create_dm_group_id(device1: &[u8; 32], device2: &[u8; 32]) -> [u8; 32] { let mut sorted = [*device1, *device2]; sorted.sort(); // Ensures same ID regardless of who creates the group sha256(&sorted.concat())}
Multi-User Groups
Section titled “Multi-User Groups”struct MultiUserGroup { // Cryptographically random group_id: [u8; 32], // Who created the group founder_device: [u8, 32], // Current members members: Vec<GroupMember>, // Current MLS epoch and keys mls_status: MLSGroupState, // Devices with admin privileges admin_devices: HashSet<[u8; 32]>, created_at: u64,}
struct GroupMember { device_id: [u8; 32], // From MLS group context public_key: Ed25519PublicKey, // For message routing delivery_addresses: Vec<String>, // Which member added them added_by: [u8; 32], added_at: u64, participation_status: ParticipationStatus,}
MLS KeyPackages
Section titled “MLS KeyPackages”What Are KeyPackages?
Section titled “What Are KeyPackages?”KeyPackages are one-time-use cryptographic credentials that enable asynchronous group additions. They allow someone to add you to a group even when you’re offline.
KeyPackage structure:
struct KeyPackage { protocol_version: ProtocolVersion, cipher_suite: CipherSuite, init_key: HPKEPublicKey, // Ephemeral public key for Welcome encryption leaf_node: LeafNode { encryption_key: HPKEPublicKey, // Ephemeral encryption key signature_key: Ed25519PublicKey, // Device identity key credential: BasicCredential { identity: device_id, // Device identity }, }, signature: Ed25519Signature, // Signed by device's private key}
Key properties:
- One-time use: Each KeyPackage can only be used to add to one group
- Pre-generated: Devices upload 50-100 KeyPackages to servers
- Ephemeral keys: Each KeyPackage has unique HPKE keys
- Device identity: All KeyPackages share the same device credential
KeyPackage Lifecycle
Section titled “KeyPackage Lifecycle”1. Generation
Section titled “1. Generation”Devices generate KeyPackages containing:
- Device credential (device_id, signature key)
- Ephemeral HPKE keys (unique per KeyPackage)
- Self-signature proving device ownership
2. Upload
Section titled “2. Upload”Public KeyPackages are uploaded to the device’s primary server
//POST /api/v1/keypackage/upload{ "device_id": "...", "key_packages": [/* 100 public KeyPackages */], "signature": "...", "timestamp": 1758315663}
3. Fetching
Section titled “3. Fetching”When adding someone to a group, fetch their KeyPackage:
// GET /api/v1/keypackage/fetch?device_id={device_id}// Server returns one KeyPackage and deletes it (one-time use){ "device_id": "...", "key_package": "...", "remaining": 99}
4. Group Addition
Section titled “4. Group Addition”Use the KeyPackage to add the member:
// Add Alice to group using her KeyPackagelet (commit, welcome, _) = group.add_members( provider, &adder_keypair, &[alice_keypackage])?;
// Welcome is encrypted with Alice's KeyPackage public keysend_welcome(&alice.delivery_addresses, welcome)?;
5. Welcome Decryption
Section titled “5. Welcome Decryption”Alice uses her stored private key to decrypt the Welcome message
// Alice has the private key from when she generated the KeyPackagelet staged_join = StagedWelcome::new_from_welcome( provider, &config, welcome, None, // Ratchet tree included in Welcome (RatchetTreeExtension))?;
let alice_group = staged_join.into_group(provider)?;
6. Rotation
Section titled “6. Rotation”When a KeyPackage count drops below 20, upload more:
// Background taskif device.remaining_keypackages() < 20 { let new_keypackage = device.generate_key_packages(100); upload_keypackages(server, new_kps)?;}
Why KeyPackages Are Separate from Contacts
Section titled “Why KeyPackages Are Separate from Contacts”ShareableIdentityBundle (for contact exchange) does NOT include KeyPackages:
- Bundles are long-lived (shared via QR codes)
- KeyPackages are ephemeral (used once)
- Bundles would need constant regeneration
Instead, bundles specify keypackage_server
where KeyPackages can be fetched on-demand.
See Contact Exchange and Trust Establishment for complete KeyPackage management details.
MLS Security Properties Explained
Section titled “MLS Security Properties Explained”Forward Secrecy
Section titled “Forward Secrecy”MLS uses something called TreeKEM to continuously rotate encryption keys. What this means is that even if an attacker gets today’s keys, yesterday’s messages still remain secure. Keys are deleted after they’re used, not just replaced.
Post-Compromise Security
Section titled “Post-Compromise Security”Let’s say Alice’s device gets compromised. Since keys are rotated, her security will be recovered during the next rotation. New members joining the group will trigger key rotation and active members regularly update keys to heal from potential compromises.
Ratchet Tree Extension
Section titled “Ratchet Tree Extension”All Cryptid groups MUST enable the RatchetTreeExtension
for simpler Welcome handling.
Why this matters:
When joining a group via Welcome, new members need:
- The Welcome message (encrypted with their KeyPackage)
- The ratchet tree (group’s current key state)
Without RatchetTreeExtension:
- Ratchet tree must be sent out-of-band
- Adds complexity and potential failure points
With RatchetTreeExtension:
- Ratchet tree is included in the Welcome message
- Single message contains everything needed to join
- Simpler, more reliable
This is required in the Cryptid protocol to ensure reliable group joins.
Group Authentication
Section titled “Group Authentication”Every MLS operation (adding members, sending messages, etc) is cryptographically authenticated. This makes it impossible to forge group membership or operations. All members can verify all group operations and history.
Device Identity in MLS
Section titled “Device Identity in MLS”Important: MLS uses device_id (not delivery addresses) for authentication.
What MLS sees:
// MLS credential contains device identitystruct BasicCredential { identity: device_id, // Permanent device identity (32 bytes)}
// When encrypting a messagefn encrypt_message(plaintext: &[u8], group: &MlsGroup) -> Vec<u8> {// MLS automatically includes:// - Sender's device_id (from credential)// - Message content// - Authentication data// - Epoch information}
What MLS doesn’t see:
- Delivery addresses (used for routing, not authentication)
- Server domains
- Routing metadata
Privacy implications:
- Group members know each other’s device_ids (MLS requirement)
- Recipients can block by device_id (survives address rotation)
- Servers don’t learn device_ids from messages (addresses used for routing)
Separation of concerns:
Component | Identity | Routing |
---|---|---|
MLS | device_id | Not involved |
Servers | Hidden | delivery_address |
Contacts | device_id | delivery_addresses |
This separation ensures:
- MLS handles cryptographic identity
- Servers handle message routing
- Neither learns more than necessary
Implementation: OpenMLS
Section titled “Implementation: OpenMLS”Cryptid uses and recommends OpenMLS for MLS protocol implementation.
OpenMLS features leveraged:
- Automatic key rotation (TreeKEM)
- RatchetTreeExtension support
- Staged operations for validation
- Multiple cipher suite support
- TLS serialization for wire format
See OpenMLS documentation for implementation details.