End-to-end encryption is usually explained in one sentence: only the sender and the recipient can read the message. That sentence is correct and almost useless — it describes the guarantee without explaining how two devices that have never met, communicating over a server neither of them trusts, actually establish a shared secret and keep it safe message after message, year after year. This article opens that up: how the initial handshake works when the recipient is offline, how the encryption key changes on every single message, and what E2EE deliberately leaves unprotected.
Key takeaways
- E2EE is established through a key agreement protocol, not a shared password — the Signal Protocol's X3DH handshake lets two devices agree on a secret even when one of them is offline at the time.
- Once a session exists, the Double Ratchet algorithm derives a brand-new key for every message, giving forward secrecy (past messages stay safe if a key leaks) and post-compromise security (the session heals itself after a compromise).
- Group chats cannot just run the same handshake pairwise at scale — Sender Keys trade a small amount of forward secrecy for encrypting each message once instead of once per recipient.
- E2EE protects content, not metadata — who talked to whom, when, and how often is still visible to the server in most implementations.
- The hardest unsolved problem in production E2EE is not cryptography, it is key verification: proving the public key you received actually belongs to your contact, not to an attacker in the middle.
What "established" actually means
Symmetric ciphers like AES need both sides to already hold the same key. That works fine once a shared secret exists, but it does not explain how it got there in the first place — you cannot send the key over the same channel you are trying to protect, and you cannot expect two strangers' apps to have pre-shared anything. Establishing E2EE means solving that bootstrap problem: deriving a shared secret between two devices using only public information, over a network controlled by a party neither device trusts.
The mechanism almost every modern secure messenger uses is the Signal Protocol, designed by Trevor Perrin and Moxie Marlinspike. It runs Signal itself, WhatsApp, and the encrypted tier of Google Messages (RCS). Its handshake is called X3DH — Extended Triple Diffie-Hellman — and it solves a problem that a plain Diffie-Hellman exchange cannot: starting a session with someone who is not online right now.
The X3DH handshake: agreeing on a secret with someone who is offline
A phone call requires both parties to pick up at the same time. A text message does not — you send it, and it waits. Messaging apps need to build encryption with the same asynchronous property: Alice should be able to start an encrypted conversation with Bob even if Bob's phone is off.
X3DH achieves this by having each user pre-publish a small bundle of public keys to the server before they need them:
- Identity key (IK) — a long-term key pair that identifies the device. It rarely changes and is what safety-number verification ultimately checks.
- Signed prekey (SPK) — a medium-term key pair, rotated periodically (the X3DH spec suggests an interval on the order of weeks to a month), signed by the identity key so a recipient can verify it actually came from that device.
- One-time prekeys (OPK) — a batch of single-use key pairs. The server hands one out per new incoming session and discards it after use, and each device periodically uploads fresh ones to replenish the pool.
When Alice wants to message Bob for the first time, she fetches one of these bundles from the server (never a private key — only the public halves). She then computes three or four separate Diffie-Hellman exchanges between combinations of her own keys and Bob's:
DH1= her identity key with Bob's signed prekeyDH2= her ephemeral (freshly generated, one-time) key with Bob's identity keyDH3= her ephemeral key with Bob's signed prekeyDH4= her ephemeral key with Bob's one-time prekey, if one was available
She concatenates the results and runs them through a key derivation function (HKDF) to produce a single shared secret. Bob, once he comes online, has all the private halves needed to compute the identical value himself — DH is commutative in exactly the way that makes this work. Neither party ever transmits the secret itself; they each independently compute the same number from a mix of long-term, medium-term, and one-time keys.
Why four separate DH computations instead of one? Each one buys a specific property:
DH1andDH2bind the session to both parties' long-term identities — this is what makes the exchange authenticated, not just secret.DH3(andDH4when a one-time key was available) adds fresh, disposable material so that if a long-term identity key is later compromised, past sessions negotiated before the compromise cannot be recomputed. This is the beginning of forward secrecy, before the ratchet even starts.- The one-time prekey specifically defends against a server that lies about the signed prekey — using each OPK exactly once limits how much a malicious or compromised server can replay.
The output of X3DH is a single 256-bit shared secret. On its own, using this one secret for every message for the life of the conversation would be exactly the "one leaked key exposes everything" problem this article opened with. That secret is not the end of the story — it is the seed for the Double Ratchet.
The Double Ratchet: a new key for every message
A ratchet, mechanically, only turns one way. The Double Ratchet algorithm borrows that name deliberately: the encryption state only ever moves forward, and there is no way to wind it back to recover a previous key from a later one.
It combines two ratchets running together:
The symmetric-key ratchet. Each side maintains a "chain key." Every time a message is sent, the current chain key is run through a hash function (HMAC) to produce two outputs: a message key, used to encrypt that one message and then discarded, and a new chain key, which replaces the old one. Message keys are never reused and cannot be derived from each other in reverse — a hash function only runs forward. If an attacker records ciphertext and later steals a chain key, they can decrypt every message sent after that point, but nothing before it.
The Diffie-Hellman ratchet. The symmetric ratchet alone still has a weakness: a stolen chain key compromises everything going forward indefinitely. To fix that, every time the conversation "turns" — roughly, every time the other party replies — each side generates a fresh ephemeral DH key pair, performs a new Diffie-Hellman exchange with the other party's latest public key, and mixes the result into the chain key. This means a new piece of fresh, unpredictable entropy enters the system on a regular cadence that an attacker cannot predict or precompute.
The combination gives two distinct guarantees that are often conflated but are not the same thing:
- Forward secrecy — compromising today's key does not expose yesterday's messages. Provided by the symmetric ratchet: old chain keys are already gone, hashed forward and discarded.
- Post-compromise security (the Double Ratchet spec calls this "break-in recovery"; also informally called "self-healing") — compromising today's key does not expose tomorrow's messages either, because the next DH ratchet step injects new randomness the attacker never saw. Provided by the DH ratchet.
Together they mean a single point-in-time compromise — a stolen device, a coerced key — has a blast radius, not a permanent break. The conversation heals itself within a message or two of fresh DH exchanges, which is a materially different security model from "the key is compromised, the conversation is compromised forever."
Group messaging: why the pairwise protocol does not scale
X3DH plus the Double Ratchet describes a one-to-one session. A group of 200 people does not have "a session" — it has up to 200×199 potential pairwise sessions, and encrypting every message once per recipient would mean 200 separate encryption operations (and 200x the bandwidth) for a single text.
Signal's answer is Sender Keys. Instead of pairwise ratchets between every member, each participant generates one symmetric "sender key" and distributes it to every other group member individually, over their already-established pairwise Double Ratchet sessions (this initial distribution is the expensive, once-per-membership-change part). After that, the sender encrypts a group message exactly once with their sender key, and every member who has received that key can decrypt it directly — no per-recipient encryption on the hot path.
This buys huge performance gains at the cost of some ratchet properties: a sender key does not roll forward per-message the same way a pairwise Double Ratchet chain does, so it provides weaker forward secrecy within its own lifetime, and it must be explicitly rotated whenever membership changes — someone leaving a group means every remaining member has to regenerate and redistribute a new sender key, or the departed member's copy would still decrypt future messages. This is precisely why removing someone from a large, high-churn group is measurably more expensive than sending them a normal message — the rotation is doing real cryptographic work, not just a database update.
The problem cryptography alone cannot solve: verifying keys
Everything above assumes Alice actually received Bob's public keys from the server, not an attacker's. If the server (or anyone who can act as it) hands Alice an attacker-controlled key bundle instead, X3DH runs perfectly and produces a perfectly valid shared secret — with the wrong person. This is a classic man-in-the-middle attack, and no amount of key derivation math fixes it, because the math never checks whose key it received.
The mitigation is out-of-band verification: Signal and WhatsApp render a safety number (a fingerprint derived from both parties' identity keys) that users can compare in person, by voice, or by scanning a QR code. If the numbers match, the identity keys are genuine and no one is intercepting the exchange. This step is optional, most users never do it, and that gap — not the cryptography — is where the realistic attacks against E2EE messengers actually happen: a compromised or coerced key-distribution server, not a broken cipher.
What this buys you, and what it does not
Pros:
- Confidentiality survives server compromise. Because the server only ever holds ciphertext (and, in X3DH, public keys), a breached database, a subpoenaed backup, or a rogue employee cannot expose message content — there is nothing readable to hand over.
- Forward secrecy limits the damage of key theft. A stolen device or a compromised long-term key does not retroactively unlock a user's entire message history.
- Post-compromise security means the system recovers. Unlike a single static key, a compromised Double Ratchet session repairs itself within a few messages.
- Group messaging scales without per-recipient re-encryption, thanks to Sender Keys — usable performance at sizes where pairwise encryption would fall over.
Cons:
- Metadata is untouched. The server still sees who is messaging whom, how often, from what IP, and for how long — research consistently shows metadata alone reveals sensitive relationship and behavior patterns, sometimes more reliably than content would.
- Endpoint compromise bypasses everything. E2EE protects data in transit and at rest on the server — not on a device already compromised by malware or physical access. Decrypted messages are, by definition, readable on the device that decrypted them.
- Key verification is manual and mostly skipped. The protocol is only as strong as its weakest link, and for most users that link is "I never checked the safety number."
- It is expensive to bolt on after the fact. Features that assume server-side visibility — search, spam detection, link previews, content moderation, backups — all have to be rebuilt or redesigned around ciphertext the server cannot read. This is exactly the migration Meta's engineering team documented for Messenger, and it is also why Instagram's 2026 rollback of E2EE from Direct Messages was a product and regulatory decision, not a cryptographic one — the mechanism this article describes was already built and working. We cover that reversal, and how E2EE fits alongside AES, TLS, and post-quantum cryptography in a wider security architecture, in Encryption Explained.
- Group membership changes cost real cryptographic work. Removing a member requires rotating and redistributing sender keys, not flipping an access-control flag.
Designing for it, not bolting it on
The recurring lesson across every production E2EE system — Signal, WhatsApp, iMessage — is that the parts of a messaging product that seem unrelated to cryptography (search, notification previews, spam filters, backups, multi-device sync) are the parts that actually determine whether E2EE is feasible. A key exchange protocol and a ratchet are well-understood, publicly reviewed building blocks at this point; the hard engineering is everywhere the server used to have visibility and now does not.
If you are building or auditing a product that handles sensitive user data — health records, financial communications, legal correspondence — the design decision about end-to-end encryption has to happen at the architecture stage, not as a retrofit. That is exactly the kind of gap we look for in an AI-generated code audit: places where a feature quietly assumes server-side access to plaintext that the security model promised would never exist. If you want a second set of eyes on how encryption fits into your application's architecture, get in touch.
References
- Perrin, T. & Marlinspike, M. (2016). The X3DH Key Agreement Protocol. Signal.
- Perrin, T. & Marlinspike, M. (2016). The Double Ratchet Algorithm. Signal.
- Cohn-Gordon, K. et al. (2016). A formal security analysis of the Signal messaging protocol. Cryptology ePrint Archive.
- WhatsApp (2024). WhatsApp Security Whitepaper.
- EFF (2013). Why metadata matters.
- Meta Engineering (2024). End-to-end encryption on Messenger.

