Designing End-to-End Message Encryption: From First Principles to Real-World Architecture

Arpit Bhayani

Arpit Bhayani

Apr 05, 2021 • 8 min read

Play

Introduction: What is End-to-End Encryption?

Modern messaging platforms like WhatsApp, Signal, and Facebook Messenger handle billions of sensitive conversations daily. A core engineering guarantee of modern communication systems is End-to-End Encryption (E2EE).

E2EE ensures that when User A sends a message to User B, only User B can read it. Intermediate network relays, internet service providers (ISPs), malicious actors, and even the application’s own backend servers and databases cannot read the message contents.

To understand how E2EE functions under the hood, we can build a baseline implementation from first principles using public-key cryptography and digital signatures, contrast it with standard transport security (TLS/HTTPS), and examine the foundations of production-grade protocols like the Signal Protocol.


The Threat Model: Insecure Channels and MITM Attacks

Consider two parties, Alice (AA) and Bob (BB), chatting across a network. Without cryptographic safeguards:

  1. Eavesdropping (Sniffing): Any entity along the packet route (unsecured Wi-Fi routers, autonomous systems, ISPs) can inspect the plain text payload.
  2. Man-in-the-Middle (MITM) Attacks: An adversary can intercept network packets, modify the payload mid-flight, or impersonate Alice to Bob.

To achieve true privacy, our messaging system must satisfy two fundamental requirements:

  • Confidentiality: A message sent by Alice intended for Bob must only be readable by Bob, and vice versa.
  • Authenticity and Integrity: Bob must have mathematical certainty that the message was genuinely produced by Alice and was not modified in transit.
+---------+          Plaintext Channel (Vulnerable to MITM)          +---------+
| Alice   | ------------------------------------------------------> |   Bob   |
|         |             Adversary sniffs & alters data              |         |
+---------+                                                         +---------+

The Building Blocks: Public-Key Cryptography

At the core of asymmetric cryptography is the concept of a key pair: a Public Key (KpubK_{pub}) and a Private Key (KprivK_{priv}).

+---------------------------------------------------------------------------------+
|                               Asymmetric Key Pairs                              |
+---------------------------------------------------------------------------------+
|  Public Key (K_pub):   Shared publicly. Anyone can access it.                   |
|  Private Key (K_priv): Held strictly locally on the client device. Never shared.|
+---------------------------------------------------------------------------------+

Asymmetric cryptography provides two complementary properties:

  1. Encryption & Decryption (Confidentiality): A message encrypted with Bob’s public key (KpubBK_{pub}^B) can only be decrypted by Bob’s corresponding private key (KprivBK_{priv}^B). Ciphertext=Encrypt(M,KpubB)\text{Ciphertext} = \text{Encrypt}(M, K_{pub}^B) M=Decrypt(Ciphertext,KprivB)M = \text{Decrypt}(\text{Ciphertext}, K_{priv}^B)
  2. Digital Signatures (Authenticity & Non-Repudiation): A piece of data encrypted/signed using Alice’s private key (KprivAK_{priv}^A) can be verified by anyone using Alice’s public key (KpubAK_{pub}^A).

Designing a Baseline E2EE Protocol

Step 1: Ensuring Secrecy via Asymmetric Encryption

To solve confidentiality alone, Alice and Bob exchange public keys:

Alice                                                            Bob
  |                                                               |
  | 1. Encrypt message M with Bob's Public Key (K_pub^B)          |
  |    Ciphertext = Encrypt(M, K_pub^B)                           |
  |                                                               |
  | 2. Transmit Ciphertext                                        |
  |-------------------------------------------------------------->|
  |                                                               |
  |                                3. Decrypt using Bob's         |
  |                                   Private Key (K_priv^B)      |
  |                                   M = Decrypt(Ciphertext,     |
  |                                               K_priv^B)       |

Because only Bob has access to KprivBK_{priv}^B, nobody sniffing the wire—not even the server routing the packet—can decrypt the scrambled ciphertext.

While this guarantees confidentiality, it fails to guarantee authenticity. Because Bob’s public key (KpubBK_{pub}^B) is public knowledge, an attacker (Eve) can encrypt a malicious message using KpubBK_{pub}^B, forward it to Bob, and claim it originated from Alice. Bob has no way of knowing whether Alice or Eve authored the payload.


Step 2: Ensuring Authenticity with Digital Signatures

To solve identity verification, Alice must append a digital signature:

  1. Hashing: Alice computes a cryptographic digest of the raw message: H=Hash(M)H = \text{Hash}(M).
  2. Signing: Alice encrypts this hash with her own private key: Signature=Sign(H,KprivA)\text{Signature} = \text{Sign}(H, K_{priv}^A)
  3. Payload Construction: Alice encrypts the original message using Bob’s public key (C=Encrypt(M,KpubB)C = \text{Encrypt}(M, K_{pub}^B)) and attaches the signature: Envelope={C,Signature}\text{Envelope} = \{C, \text{Signature}\}
  4. Verification & Decryption by Bob:
    • Bob extracts the signature and verifies it using Alice’s public key (KpubAK_{pub}^A). If valid, Bob is guaranteed that the hash was generated by the holder of KprivAK_{priv}^A and that the content was not altered.
    • Bob decrypts the ciphertext CC using his private key (KprivBK_{priv}^B) to recover the plain text MM.
+-----------------------------------------------------------------------------------+
|                             Alice's Outgoing Packet                               |
+-----------------------------------------------------------------------------------+
|  [ Scrambled Ciphertext: Encrypt(M, K_pub^B) ]  +  [ Signature: Sign(H, K_priv^A) ]|
+-----------------------------------------------------------------------------------+

Transport-Layer Security (TLS) vs. True End-to-End Encryption

A common misunderstanding in system architecture is assuming that standard HTTPS/TLS equates to end-to-end encryption. Let’s compare the operational flow across systems.

Scenario A: Standard Chat Application with TLS (No E2EE)

In a standard web/mobile app, HTTPS secures the transport layer between clients and servers:

+---------+          TLS Session 1          +-------------------+          TLS Session 2          +---------+
| Alice   | ==============================> | API / Load Balancer| =============================> |   Bob   |
+---------+                                 +-------------------+                                 +---------+
                                                      |                                                      
                                                      v                                                      
                                            +-------------------+
                                            | Database (Plaintext|
                                            | Payload Stored)   |
                                            +-------------------+
  1. Alice sends plain text MM over TLS to the chat server.
  2. TLS terminates at the API Gateway / Load Balancer. The transport-level encryption is stripped away.
  3. The backend server inspects the raw string payload and persists it directly into a database (e.g., MySQL, Cassandra).
  4. The server establishes a new TLS connection with Bob and pushes the message down.

Verdict: The transport is secure against external wire-sniffers, but the service provider has complete visibility into conversations and raw database dumps reveal user messages.


Scenario B: True End-to-End Encryption (E2EE)

In an E2EE architecture, client-side encryption occurs before the payload hits the network:

+---------+                                 +-------------------+                                 +---------+
| Alice   |                                 | API / Load Balancer|                                 |   Bob   |
+---------+                                 +-------------------+                                 +---------+
     |                                                |                                                |
     | Encrypts payload at client-side                |                                                |
     | Payload: {Ciphertext, Signature}               |                                                |
     |                                                |                                                |
     | === HTTPS / TLS Transport ===================> |                                                |
     |                                                | Persists opaque blob                           |
     |                                                | into DB: {Ciphertext, Sig}                     |
     |                                                |                                                |
     |                                                | === HTTPS / TLS Transport ===================> |
     |                                                |                                                |
     |                                                |                                   Client-side: |
     |                                                |                            1. Verify signature |
     |                                                |                           2. Decrypt with K_priv

Verdict: The API server and database only ever store and forward opaque, unreadable blobs. Even under full server infrastructure compromise or database leakage, plain text cannot be extracted.


Key Management Architecture

An asymmetric system relies heavily on secure key segregation:

+-------------------------------------------------------------------------------+
| Key Storage Architecture                                                      |
+-------------------------------------------------------------------------------+
| Private Key (K_priv):                                                         |
| - Kept strictly on the client hardware.                                       |
| - Stored in secure hardware enclaves (iOS Keychain, Android KeyStore).        |
| - Never uploaded or backed up to messaging servers.                           |
|                                                                               |
| Public Key (K_pub):                                                           |
| - Publicly distributable metadata.                                            |
| - Stored centrally on a Key Distribution Center (KDC) / Profile Directory.    |
| - Queried by clients when initiating a chat with a new contact.               |
+-------------------------------------------------------------------------------+

A typical user profile directory schema on the server:

{
  "user_id": "usr_982341",
  "username": "bob_the_builder",
  "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----",
  "updated_at": 1617642961
}

Limitations of the Simple Model: Enter the Signal Protocol

The public-key encryption scheme detailed above forms the fundamental concept of E2EE, but production chat applications require significantly more advanced defenses against modern attack vectors.

The Need for Forward Secrecy

If Alice and Bob use static public/private key pairs for every message, a critical vulnerability emerges:

The Long-Term Compromise Problem: If Bob’s phone is compromised five years later and an attacker extracts his static KprivBK_{priv}^B, the attacker can decrypt every historical message ever sent to Bob that was recorded on intermediate network taps.

How Real-World Systems Work (Signal Protocol)

Production implementations (WhatsApp, Signal, FB Messenger Secret Conversations) rely on the Signal Protocol (co-developed by Moxie Marlinspike and Trevor Perrin), which introduces three major innovations:

  1. Extended Triple Diffie-Hellman (X3DH):
    • Establishes a shared secret key between two parties asynchronously, even if the recipient is offline, using multiple ephemeral and pre-published identity keys.
  2. The Double Ratchet Algorithm:
    • Derives a brand new, unique encryption key for every single message.
    • Combines a symmetric KDF (Key Derivation Function) ratchet with an asymmetric Diffie-Hellman (DH) ratchet.
    • Like a mechanical ratchet that only turns forward, once a key is used to encrypt or decrypt a message, it is permanently erased from memory.
  3. Break-in Recovery (Post-Compromise Security):
    • If an ephemeral ratchet key is compromised in transit, subsequent messages generate brand-new Diffie-Hellman secrets, automatically restoring secrecy without user intervention.

Summary & Key Takeaways

ConceptTransport Security (TLS)Baseline E2EEProduction E2EE (Signal Protocol)
Protection ScopeHop-by-hop (Client to Server)End-to-end (Client to Client)End-to-end (Client to Client)
Server VisibilityFull plain text visibleZero visibility (Opaque blobs)Zero visibility (Opaque blobs)
Key RotationPer TLS sessionStatic key pair per userPer-message unique keys (Double Ratchet)
Forward SecrecySession-level onlyNone (Static key compromise leaks history)Yes (Past and future messages protected)
AuthenticationServer cert verified by CADigital signatures (KprivAK_{priv}^A)X3DH / Cryptographic Handshakes
  1. TLS is not E2EE: Securing transport channels ensures data integrity against wire sniffers, but terminates trust at the central server.
  2. Confidentiality requires the Recipient’s Public Key: Only the intended recipient possesses the private key necessary to invert the ciphertext.
  3. Authenticity requires the Sender’s Private Key: Digital signatures prevent impersonation and guarantee message integrity.
  4. Private Keys Must Never Leave the Edge: Centralizing private keys completely invalidates the security premise of an E2EE architecture.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses