Amarula.Protocol.Messages.Media (amarula v0.1.0)
View SourceWhatsApp media encryption + upload, ported from Baileys
src/Utils/messages-media.ts.
Flow for an outgoing media message:
media_key= 32 random bytes.- HKDF-SHA256 expand
media_keyto 112 bytes with info "WhatsApp <Type> Keys" → iv(16) ++ cipher_key(32) ++ mac_key(32) ++ ref_key(rest). ciphertext = AES-256-CBC(cipher_key, iv, plaintext),mac = HMAC-SHA256(mac_key, iv ++ ciphertext)truncated to 10 bytes. The uploaded blob isciphertext ++ mac.file_sha256 = sha256(plaintext),file_enc_sha256 = sha256(ciphertext ++ mac).- Fetch a media connection (
<iq xmlns="w:m"><media_conn/>), PUT the blob tohttps://<host>/mms/<type>/<encSha256B64>?auth=..&token=.., getdirect_path/url. - Build the per-type message (e.g.
%Proto.Message{imageMessage: ...}).
decrypt/2 reverses 2–3 for a downloaded blob.
Summary
Functions
Decrypt a downloaded media blob (ciphertext ++ mac) with its media_key.
Verifies the MAC before decrypting. Returns the plaintext.
Download an encrypted media blob from a message's :directPath (or :url) and
decrypt it for type. ref is a map/struct with direct_path/directPath or
url, plus media_key/mediaKey. Returns {:ok, plaintext} (still possibly
compressed — history blobs are zlib-deflated; the caller inflates).
Encrypt plaintext for media type. Returns the uploadable blob plus the
hashes/key the message stanza needs.
Upload an encrypted blob to the media servers. conn is the Connection;
returns {:ok, %{direct_path: .., url: ..}}.
Types
Functions
@spec decrypt(binary(), binary(), media_type()) :: {:ok, binary()} | {:error, term()}
Decrypt a downloaded media blob (ciphertext ++ mac) with its media_key.
Verifies the MAC before decrypting. Returns the plaintext.
@spec download(map(), media_type()) :: {:ok, binary()} | {:error, term()}
Download an encrypted media blob from a message's :directPath (or :url) and
decrypt it for type. ref is a map/struct with direct_path/directPath or
url, plus media_key/mediaKey. Returns {:ok, plaintext} (still possibly
compressed — history blobs are zlib-deflated; the caller inflates).
@spec encrypt(binary(), media_type()) :: {:ok, %{ enc: binary(), media_key: binary(), file_sha256: binary(), file_enc_sha256: binary(), file_length: non_neg_integer() }}
Encrypt plaintext for media type. Returns the uploadable blob plus the
hashes/key the message stanza needs.
@spec upload(GenServer.server(), binary(), binary(), media_type()) :: {:ok, %{direct_path: String.t() | nil, url: String.t()}} | {:error, term()}
Upload an encrypted blob to the media servers. conn is the Connection;
returns {:ok, %{direct_path: .., url: ..}}.