Signal Protocol session management for WhatsApp encryption.
Implements:
- X3DH (Extended Triple Diffie-Hellman) key agreement
- Double Ratchet Algorithm for forward secrecy
- AES-256-CBC + HMAC-SHA256 message encryption (proper Signal format)
Summary
Functions
Creates a new session using X3DH key agreement.
Decrypts a regular Signal message.
Decrypts a PreKeySignalMessage (initial message from a new sender).
Decrypts a PreKeySignalMessage using an already-established session.
Encrypts a message using the session.
Encrypts a message and returns a PreKeySignalMessage for initial messages. Uses proper Signal Protocol wire format.
Types
@type chain_state() :: %{ key: binary(), counter: non_neg_integer(), message_keys: %{required(non_neg_integer()) => binary()} }
@type encrypted_message() :: %{ type: :prekey_message | :message, registration_id: non_neg_integer(), prekey_id: non_neg_integer() | nil, signed_prekey_id: non_neg_integer(), base_key: binary() | nil, identity_key: binary(), ciphertext: binary() }
@type session_state() :: %{ optional(:initiator_base_key) => binary(), remote_identity_key: binary(), local_identity_key: %{public: binary(), private: binary()}, root_key: binary(), sending_chain: chain_state() | nil, receiving_chain: chain_state() | nil, receiving_chains: %{optional(binary()) => chain_state()}, previous_counter: non_neg_integer(), ratchet_key: %{public: binary(), private: binary()} | nil, remote_ratchet_key: binary() | nil }
Functions
@spec create_session(map(), map()) :: {:ok, session_state(), binary()} | {:error, term()}
Creates a new session using X3DH key agreement.
Uses the recipient's prekey bundle to establish a shared secret.
@spec decrypt(session_state(), binary()) :: {:ok, binary(), session_state()} | {:error, term()}
Decrypts a regular Signal message.
@spec decrypt_prekey_message(binary(), map(), map()) :: {:ok, binary(), session_state()} | {:error, term()}
Decrypts a PreKeySignalMessage (initial message from a new sender).
@spec decrypt_prekey_message_with_session(session_state(), binary()) :: {:ok, binary(), session_state()} | {:error, term()}
Decrypts a PreKeySignalMessage using an already-established session.
WhatsApp can keep wrapping sync traffic as pkmsg even when a session already exists.
In that case, we must decrypt the inner SignalMessage with the current ratchet state
instead of re-running X3DH and replacing the session.
@spec encrypt(session_state(), binary()) :: {:ok, binary(), session_state()}
Encrypts a message using the session.
Returns the encrypted ciphertext and updated session state. Uses proper Signal Protocol wire format with AES-CBC + HMAC.
@spec encrypt_prekey_message(session_state(), binary(), map()) :: {:ok, binary(), session_state()}
Encrypts a message and returns a PreKeySignalMessage for initial messages. Uses proper Signal Protocol wire format.