attacker[active]principal Alice[ knows private alongterm galongterm = PUBKEY(alongterm)]principal Bob[ knows private blongterm, bs generates bo gblongterm = PUBKEY(blongterm) gbs = PUBKEY(bs) gbo = PUBKEY(bo) gbssig = SIGN(blongterm, gbs)]// The prekey bundle. In signal.vp this line reads [gblongterm].Bob -> Alice: gblongterm, gbssig, gbs, gboprincipal Alice[ _ = SIGNVERIF(gblongterm, gbs, gbssig)? generates ae1, ae2, m1 gae1 = PUBKEY(ae1) gae2 = PUBKEY(ae2) amaster = HASH(DH_KEX(gbs, alongterm), DH_KEX(gblongterm, ae1), DH_KEX(gbs, ae1), DH_KEX(gbo, ae1)) ark, ack = HKDF(amaster, DH_KEX(gbs, ae2), nil) akenc = HKDF(nil, MAC(ack, nil), nil) e1 = AEAD_ENC(akenc, m1, HASH(galongterm, gblongterm, gae2))]Alice -> Bob: [galongterm], gae1, gae2, e1principal Bob[ bmaster = HASH(DH_KEX(galongterm, bs), DH_KEX(gae1, blongterm), DH_KEX(gae1, bs), DH_KEX(gae1, bo)) brk, bck = HKDF(bmaster, DH_KEX(gae2, bs), nil) bkenc = HKDF(nil, MAC(bck, nil), nil) m1_d = AEAD_DEC(bkenc, e1, HASH(galongterm, gblongterm, gae2))?]queries[ confidentiality? m1 authentication? Alice -> Bob: e1]
×
confidentiality? m1
Contradiction found
The attacker publishes an identity key of its own, plus a prekey it signed with that key. Alice’s checked signature verification passes, and all four X3DH secrets are then derived against a key the attacker holds.
✓
authentication? Alice → Bob: e1
Holds
Bob accepts nothing. His checked decryption halts, because Alice keyed the message to the attacker rather than to him.
Attack trace
| 1.Attacker constructs PUBKEY(nil) from nil.
| 2.Attacker constructs SIGN(nil, PUBKEY(nil)) from nil, PUBKEY(nil).
| 3.Attacker replaces gblongterm, gbs, gbo, gbssig (sent by Bob to Alice) with PUBKEY(nil), PUBKEY(nil), PUBKEY(nil), SIGN(nil, PUBKEY(nil)).
| 4.Alice’s SIGNVERIF(gblongterm, gblongterm, gbssig)? passes — its inputs are attacker-controlled.
| 5.Attacker observes e1 on the wire.
| 6.Attacker observes galongterm on the wire.
| 7.Attacker constructs DH_KEX(gblongterm, alongterm) from galongterm, nil.
| 8.Attacker observes gae1 on the wire.
| 9.Attacker constructs DH_KEX(gblongterm, ae1) from gae1, nil.
| 10.Attacker constructs amaster from DH_KEX(gblongterm, alongterm), DH_KEX(gblongterm, ae1), DH_KEX(gblongterm, ae1), DH_KEX(gblongterm, ae1).
| 11.Attacker observes gae2 on the wire.
| 12.Attacker constructs DH_KEX(gblongterm, ae2) from gae2, nil.
| 13.Attacker constructs ack from amaster, DH_KEX(gblongterm, ae2), nil.
| 14.Attacker constructs MAC(ack, nil) from ack, nil.
| 15.Attacker constructs akenc from nil, MAC(ack, nil), nil.
| 16.Attacker opens e1 with akenc, obtaining m1.
>m1 (m1) is obtained by Attacker.