ExWapp.Signal.Session (ExWapp v0.1.2)

Copy Markdown View Source

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

chain_state()

@type chain_state() :: %{
  key: binary(),
  counter: non_neg_integer(),
  message_keys: %{required(non_neg_integer()) => binary()}
}

encrypted_message()

@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()
}

session_state()

@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

create_session(local_identity, remote_bundle)

@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.

decrypt(session, ciphertext)

@spec decrypt(session_state(), binary()) ::
  {:ok, binary(), session_state()} | {:error, term()}

Decrypts a regular Signal message.

decrypt_prekey_message(message, local_identity, local_prekeys)

@spec decrypt_prekey_message(binary(), map(), map()) ::
  {:ok, binary(), session_state()} | {:error, term()}

Decrypts a PreKeySignalMessage (initial message from a new sender).

decrypt_prekey_message_with_session(session, message)

@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.

encrypt(session, plaintext)

@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.

encrypt_prekey_message(session, plaintext, bundle_info)

@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.