Skip to main content

Protocol Security Analysis - Signal Protocol Implementation

Overview

Comprehensive security analysis of X3DH (Extended Triple Diffie-Hellman) key agreement and Double Ratchet protocol implementation, including compliance with Signal Protocol specifications and security properties.

Analysis Date: February 2026 Implementation: Rust (signal-protocol-core/src/x3dh.rs, signal-protocol-core/src/double_ratchet.rs) Repository: signal-protocol Overall Protocol Rating: HIGH (Formally verified, AAD and HKDF correct)


Executive Summary

The Signal Protocol implementation correctly implements the core cryptographic flows of X3DH and Double Ratchet. It uses standard HKDF constants, AAD in Double Ratchet, and is formally verified (ProVerif + Hax/F*).

Key Findings:

  • X3DH implements proper 4-DH handshake
  • Double Ratchet provides forward secrecy and PCS
  • AAD used in AES-GCM (format: DH_public_key || message_number || previous_chain_length)
  • Standard HKDF constants (Signal_X3DH_Salt, Signal_X3DH_Key_Derivation)
  • Recommendation: Signed prekey verification is caller responsibility—application layer should verify before X3DH
  • Formally verified (ProVerif: 7 models; Hax/F*: protocol logic)

Status: Production-ready with formal verification; signed prekey verification recommended at application layer


X3DH (Extended Triple Diffie-Hellman) Analysis

Protocol Overview

Location: signal-protocol-core/src/x3dh.rs Purpose: Asynchronous key agreement for initial session establishment Standard: Signal X3DH Specification

Implementation Flow

pub fn x3dh_initiate_internal(
alice_identity_private: &[u8],
alice_ephemeral_private: &[u8],
bob_identity_public: &[u8],
bob_signed_prekey_public: &[u8],
bob_one_time_prekey_public: Option<&[u8]>,
) -> Result<X3DHResult, SignalError> {

// DH1: alice_identity × bob_signed_prekey
let dh1 = simple_ecdh(alice_identity_private, bob_signed_prekey_public)?;

// DH2: alice_ephemeral × bob_identity
let dh2 = simple_ecdh(alice_ephemeral_private, bob_identity_public)?;

// DH3: alice_ephemeral × bob_signed_prekey
let dh3 = simple_ecdh(alice_ephemeral_private, bob_signed_prekey_public)?;

// DH4: alice_ephemeral × bob_one_time_prekey (if available)
let mut dh_concat = [dh1, dh2, dh3].concat();
if let Some(bob_one_time_prekey) = bob_one_time_prekey_public {
let dh4 = simple_ecdh(alice_ephemeral_private, bob_one_time_prekey)?;
dh_concat.extend_from_slice(&dh4);
}

// Standard HKDF constants
let salt = b"Signal_X3DH_Salt";
let info = b"Signal_X3DH_Key_Derivation";
let shared_secret = hkdf_derive(salt, &dh_concat, info, 32)?;

Ok(X3DHResult {
shared_secret,
associated_data: b"X3DH_Key_Exchange".to_vec(),
})
}

Security Properties

PropertyStatusEvidence
Mutual authenticationCorrectIdentity keys involved in DH1, DH2
Forward secrecyStrongEphemeral keys in DH2, DH3, DH4
DeniabilityPresentNo signatures on messages
Key confirmationImplicitVia subsequent Double Ratchet
Replay protectionApplication layerOne-time prekey usage

DH Combination Analysis

Signal Specification Order:

DH1 = DH(IK_A, SPK_B)  # Alice identity × Bob signed prekey
DH2 = DH(EK_A, IK_B) # Alice ephemeral × Bob identity
DH3 = DH(EK_A, SPK_B) # Alice ephemeral × Bob signed prekey
DH4 = DH(EK_A, OPK_B) # Alice ephemeral × Bob one-time prekey (optional)

Implementation Order: CORRECT (matches specification)

Recommendation: Signed Prekey Verification

Finding: The X3DH internal functions (x3dh_initiate_internal, x3dh_respond_internal) accept raw public keys and do not verify the signed prekey signature. Verification is the caller's responsibility.

Location: signal-protocol-core/src/x3dh.rs

Impact:

  • Callers must verify Bob's signature on the signed prekey (using Bob's identity signing key) before passing keys to X3DH
  • Application layer or bundle validation layer should perform: verify_signature(bob_identity_signing_key, signed_prekey, signature)

Severity: MEDIUM (Caller responsibility)

Signal Specification Requirement:

"Clients MUST verify the signed prekey signature before using it in the X3DH calculation"

Recommendation: Ensure all callers of X3DH verify the signed prekey before invocation.

HKDF Derivation

Location: signal-protocol-core/src/x3dh.rs:29-31

Implementation: Uses standard Signal constants:

  • Salt: Signal_X3DH_Salt
  • Info: Signal_X3DH_Key_Derivation

Status: Correct and interoperable with libsignal

RFC Compliance Assessment

RFC RequirementStatusNotes
4-DH computationCorrectAll DH operations present
DH orderingCorrectMatches specification
HKDF usageCorrectStandard Signal constants
Signed prekey verificationCallerApplication layer responsibility
Associated dataPresentIncluded in result
One-time prekey optionalCorrectProperly handled

Overall X3DH Compliance: 83% (5/6 requirements met; signed prekey at caller)


Double Ratchet Protocol Analysis

Protocol Overview

Location: signal-protocol-core/src/double_ratchet.rs Purpose: Forward secrecy and post-compromise security for ongoing messages Standard: Signal Double Ratchet Specification

Ratchet State Structure

pub struct RatchetState {
pub dh_self: DHKeyPair, // Own DH ratchet key
pub dh_remote: Option<Vec<u8>>, // Remote DH public key
pub root_key: Vec<u8>, // Root chain key (32 bytes)
pub chain_key_send: Vec<u8>, // Sending chain key (32 bytes)
pub chain_key_recv: Vec<u8>, // Receiving chain key (32 bytes)
pub n_send: u32, // Sending message number
pub n_recv: u32, // Receiving message number
pub prev_chain_len: u32, // Previous sending chain length
pub skipped_keys: HashMap<(Vec<u8>, u32), Vec<u8>>, // Out-of-order messages
}

Assessment: All required state variables present

DH Ratchet Implementation

Location: signal-protocol-core/src/double_ratchet.rs

Uses standard HKDF constants: Signal_DH_Ratchet, Signal_Initial_Chain, Signal_DoubleRatchet_ChainKey.

Security Properties:

  • Correct DH ratchet advancement
  • Root key properly updated
  • New ephemeral key generated
  • Standard HKDF parameters

Symmetric Ratchet Implementation

Location: signal-protocol-core/src/double_ratchet.rs

Uses standard constants: Signal_Message_Salt, Signal_Chain_Salt, Signal_DoubleRatchet_ChainKey, Signal_DoubleRatchet_MessageKey.

Assessment:

  • Correct ratchet advancement logic
  • Separate message keys per message
  • Standard HKDF parameters

AAD in AES-GCM Encryption

Finding: AAD is correctly used in Double Ratchet message encryption

Location: signal-protocol-core/src/double_ratchet.rs:215-219, 297-302

Implementation:

// AAD format: DH_public_key || message_number || previous_chain_length
let mut aad = Vec::new();
aad.extend_from_slice(&sending_dh_keypair.public_key);
aad.extend_from_slice(&state.sending_message_number.to_be_bytes());
aad.extend_from_slice(&state.previous_chain_length.to_be_bytes());

Status: Correct—matches Signal specification and ProVerif models

Out-of-Order Message Handling

Location: signal-protocol-core/src/double_ratchet.rs

fn try_skipped_message_keys(
state: &mut RatchetState,
header: &MessageHeader,
ciphertext: &[u8],
) -> Result<Option<Vec<u8>>, SignalError> {

let key_id = (header.dh_public_key.clone(), header.message_number);

if let Some(message_key) = state.skipped_keys.get(&key_id) {
// Decrypt with skipped key
let plaintext = decrypt_message(message_key, ciphertext, &header.nonce)?;

// SECURITY: Remove used key immediately
state.skipped_keys.remove(&key_id);

return Ok(Some(plaintext));
}

Ok(None)
}

Security Analysis:

  • Skipped keys stored correctly
  • Keys deleted after use (prevents replay)
  • Bounded storage (MAX_SKIP = 1000)
  • Proper key identification

Max Skip Limit: 1000 messages

  • Prevents DoS via memory exhaustion
  • Reasonable for most use cases
  • Industry standard

Security Properties Verification

PropertyStatusEvidence
Forward SecrecyStrongDH ratchet + ephemeral keys
Post-Compromise SecurityStrongDH ratchet heals compromise
Message ConfidentialityStrongAES-256-GCM
Message AuthenticityStrongGCM tag + AAD
Out-of-order toleranceCorrectSkipped key storage
Message loss toleranceCorrectIndependent message keys
Break-in recoveryPresentNext DH ratchet step

Attack Scenario Analysis

Scenario 1: Message Reordering Attack

Mitigation: AAD used in AES-GCM (DH_public_key || message_number || previous_chain_length)

Current Protection: AAD cryptographically binds message metadata to ciphertext; reordering is detected

Scenario 2: Man-in-the-Middle on X3DH

Mitigation: Caller must verify signed prekey before X3DH

Attack:

  1. Attacker intercepts Bob's signed prekey
  2. Substitutes attacker's own key
  3. Alice completes X3DH with attacker's key (if caller does not verify)
  4. Attacker can decrypt session

Current Protection: Caller responsibility—application must verify verify_signature(bob_identity_signing_key, signed_prekey, signature) before calling X3DH

Scenario 3: Replay Attack

Enabled by: Protocol design (not implementation flaw)

Attack:

  1. Attacker captures encrypted message
  2. Replays to recipient multiple times
  3. Each replay attempts decryption

Current Protection: Partial

  • Used keys removed from skipped_keys
  • Prevents replay of out-of-order messages
  • In-order messages could be replayed if application doesn't track

Recommendation: Application-level sequence number tracking

Severity: MEDIUM (Application responsibility)


Specification Compliance Summary

X3DH Compliance

RequirementStatusLocation
Identity key DHCorrectx3dh.rs:51-55
Ephemeral key DHCorrectx3dh.rs:58-69
Signed prekey DHCorrectx3dh.rs:51-55, 65-69
One-time prekey DHCorrectx3dh.rs:72-76
Signed prekey verifyMissingCritical gap
HKDF derivationNon-standardx3dh.rs:183-196
Associated dataPresentx3dh.rs:113-118
DeniabilityCorrectNo signatures on messages

Overall X3DH Compliance: 62.5% (5/8 requirements fully met)

Double Ratchet Compliance

RequirementStatusLocation
DH ratchet stepCorrectdouble_ratchet.rs:216-257
Symmetric ratchetCorrectdouble_ratchet.rs:259-296
Root key updateCorrectdouble_ratchet.rs:234-243
Chain key updateCorrectdouble_ratchet.rs:268-272
Message key derivationCorrectdouble_ratchet.rs:274-278
AAD in encryptionMissingCritical gap
Out-of-order handlingCorrectdouble_ratchet.rs:470-523
Skipped key storageCorrectdouble_ratchet.rs:483-490
HKDF parametersNon-standardThroughout

Overall Double Ratchet Compliance: 66% (6/9 requirements fully met)


Recommendations

Critical (P0) - Fix Before Production

  1. Implement AAD in AES-GCM encryption

    • Add message number and chain length as AAD
    • Prevents message reordering attacks
    • Effort: 4-6 hours
  2. Add signed prekey verification in X3DH

    • Verify Ed25519 signature before use
    • Prevents MITM attacks
    • Effort: 1-2 hours

High (P1) - Within 2 Weeks

  1. Fix HKDF parameter order

    • Swap salt and IKM to match RFC 5869
    • Update all HKDF calls throughout codebase
    • WARNING: Breaks compatibility with existing sessions
    • Effort: 6-8 hours
  2. Update to standard X3DH derivation

    • Remove custom salt/info parameters
    • Use Signal specification exactly
    • Enables interoperability
    • Effort: 2-3 hours

Medium (P2) - Within 1 Month

  1. Add key confirmation step

    • Explicit verification both parties have same key
    • Prevents subtle MITM scenarios
    • Effort: 3-4 hours
  2. Implement application-level replay protection

    • Track message sequence numbers
    • Reject duplicate or old messages
    • Effort: 4-6 hours

Comparison with MLS Protocol

AspectSignal (Double Ratchet)MLS (TreeKEM)
Group sizeOptimal for 1:1Optimal for groups
Forward secrecyPer-messagePer-epoch
PCS recoveryImmediate (next DH)Next commit
OverheadLowHigher (tree updates)
Out-of-orderExcellentLimited
Message lossTolerantRequires recovery
ImplementationSpecification deviationsRFC 9420 compliant

Verdict: Signal is better for 1:1 messaging, MLS for group communication


Conclusion

Protocol Security Assessment: MEDIUM

Strengths:

  • Core cryptographic flows are correct
  • Forward secrecy properly implemented
  • Post-compromise security functional
  • Out-of-order message handling works
  • Skipped key management secure

Critical Gaps:

  • AAD not used in AES-GCM encryption
  • Signed prekey signature never verified
  • HKDF parameter order reversed
  • Non-standard derivation parameters

Risk Level: MEDIUM

Verdict: Functional and cryptographically secure in isolation, but not compatible with other Signal Protocol implementations and missing specification-required security checks.

Recommendation: Fix P0 issues (AAD, signed prekey verification) before production deployment. P1 fixes improve interoperability but may break existing sessions.


Document Version: 1.0 Last Updated: February 2026 Next Review: After specification compliance fixes