The Signal Protocol: X3DH and the Double Ratchet
Four ways to lose a conversation.
| Attacker compromises… | Past messages | Current message | Future messages |
|---|---|---|---|
| Long-term identity key | ? | ? | ? |
| Signed prekey | ? | ? | ? |
| One message key | ? | ? | ? |
| Full ratchet state | ? | ? | ? |
| Name | Meaning |
|---|---|
| Identity key \(IK\) | Per-user, never rotates |
| Signed prekey \(SPK\) | Rotated ~weekly, signed by \(IK\) |
| One-time prekey \(OPK\) | Used once, then destroyed |
| Ephemeral key \(EK\) | Fresh per session / per message |
| Root key \(RK\) | Seeds the next chain |
| Chain key \(CK\) | Advances per message |
| Message key \(MK\) | Encrypts exactly one message |
| \(\text{KDF}\) | HKDF, used everywhere below |
| AEAD | Encrypts with the header as associated data |
Bob is asleep. Alice wants to text him anyway.
E pluribus unum.
\[ \begin{aligned} &DH_1 = \text{DH}(IK_A,\; SPK_B) \quad &&\text{identity meets signed prekey} \\ &DH_2 = \text{DH}(EK_A,\; IK_B) \quad &&\text{ephemeral meets identity} \\ &DH_3 = \text{DH}(EK_A,\; SPK_B) \quad &&\text{base forward secrecy} \\ &DH_4 = \text{DH}(EK_A,\; OPK_B) \quad &&\text{FS vs } SPK_B \text{ leak} \end{aligned} \]
\[ SK \leftarrow \text{KDF}(DH_1 \,\|\, DH_2 \,\|\, DH_3 \,\|\, DH_4) \]
| Attacker compromises… | Past | Current | Future |
|---|---|---|---|
| Long-term identity key | safe | safe | safe |
| Signed prekey | safe | safe | safe |
| One message key | ? | ? | ? |
| Full ratchet state | ? | ? | ? |
X3DH hands you one secret. Now send 5000 messages.
Ex uno plures.
| Attacker compromises… | Past | Current | Future |
|---|---|---|---|
| Long-term identity key | safe | safe | safe |
| Signed prekey | safe | safe | safe |
| One message key | safe | that message | ? |
| Full ratchet state | safe | exposed | ? |
What if they steal the phone, then give it back?
Electric boogaloo.
| Attacker compromises… | Past | Current | Future |
|---|---|---|---|
| Long-term identity key | safe | safe | safe |
| Signed prekey | safe | safe | safe |
| One message key | safe | that message | safe |
| Full ratchet state | safe | exposed | recovers after 1 round trip |
Two ratchets. Who turns when?
\[ AD = \underbrace{IK_A \,\|\, IK_B}_{\text{from X3DH}} \,\|\, \underbrace{\text{ratchet pub} \,\|\, PN \,\|\, N}_{\text{the message header}} \]
Message 5 before message 3. Now what?
\[ \begin{aligned} &\textbf{Alice} \to A_0:\; RK_0 \to CK^s_0 \to MK,\; \text{header }(P_A, 0, 0) \\ &\textbf{Alice} \to A_1:\; CK^s_0 \to CK^s_1 \to MK,\; \text{header }(P_A, 0, 1) \\ &\textbf{Bob recv } A_0, A_1:\; \text{DH ratchet on } P_A \Rightarrow RK_1,\; CK^r \\ &\textbf{Bob} \to B_0:\; \text{new } P_B \Rightarrow RK_2,\; CK^s \to MK,\; \text{header }(P_B, 2, 0) \\ &\textbf{Alice recv } B_0:\; \text{DH ratchet on } P_B \Rightarrow RK_2,\; CK^r \end{aligned} \]
Non-repudiation isn’t all it’s cracked up to be.
How can we scale a double ratchet?
Abandon hope, all ye who…
What did we learn?
| Attacker compromises… | Past | Current | Future |
|---|---|---|---|
| Long-term identity key | safe | safe | safe |
| Signed prekey | safe | safe | safe |
| One message key | safe | that message only | safe |
| Full ratchet state | safe | exposed | recovers after 1 round trip |
Ask now, catch me after class, or email eoin@eoin.ai