Skip to content

fix(msgsecret): preserve caller origSender across @lid↔phone chat migration#1126

Open
dgattupalli696 wants to merge 2 commits intotulir:mainfrom
dgattupalli696:fix/msgsecret-lid-origsender
Open

fix(msgsecret): preserve caller origSender across @lid↔phone chat migration#1126
dgattupalli696 wants to merge 2 commits intotulir:mainfrom
dgattupalli696:fix/msgsecret-lid-origsender

Conversation

@dgattupalli696
Copy link
Copy Markdown

Reopens #1122 (auto-closed when the head branch was deleted; PR has been rebased onto current main with no conflicts and the fix is unchanged).

Bug

DecryptPollVote (and, by extension, any decryptMsgSecret caller) fails with

failed to decrypt secret message: cipher: message authentication failed

on DMs that WhatsApp has migrated to @lid addressing, whenever the poll's message secret was stored before the migration under the @s.whatsapp.net form of the sender.

Root cause

GetMessageSecret intentionally resolves @@s.whatsapp.netviawhatsmeow_lid_mapin its SQL query (seestore/sqlstore/store.go), so the row is found regardless of which form is passed in. But it also returns the **stored** sender form as realSender, and decryptMsgSecret unconditionally overwrites the caller's origSender with that value:lid

origSender, err := getOrigSenderFromKey(msg, origMsgKey)
...
baseEncKey, origSender, err := cli.Store.MsgSecrets.GetMessageSecret(ctx, msg.Info.Chat, origSender, origMsgKey.GetID())
...
secretKey, additionalData := generateMsgSecretKey(useCase, msg.Info.Sender, origMsgKey.GetID(), origSender, baseEncKey)

The HKDF context inside generateMsgSecretKey is built from the sender JID string. The peer that encrypted the vote derived its key using the sender form present on the echoed i.e. the same form the caller originally had inorigSender. Overriding with a differently-represented realSender produces a mismatched HKDF context and GCM auth fails.origMsgKey

Fix

Keep the caller's origSender for HKDF derivation (it matches the peer's derivation). If that fails, fall back to the stored realSender so legacy stores whose secrets predate any LID-aware behaviour still decrypt.

Reproduction

  1. Have an active DM that WhatsApp has migrated to @lid addressing on the peer's side.
  2. Send a poll from whatsmeow to that DM.
  3. Have the peer vote.
  4. Observe: cli.DecryptPollVote(ctx, evt) returns cipher: message authentication failed.

With this patch applied, decryption succeeds.

Notes

  • I've kept the fallback to realSender so any existing store that relied on the overwrite behaviour continues to work.
  • Encryption path (encryptMsgSecret) has the same override I haven't touched it because, for the sending side, the caller's origSender and the resolved realSender should coincide (we're about to encrypt to the peer whose form we used when storing). Happy to extend the change if a maintainer sees value.pattern

…ration

decryptMsgSecret was unconditionally overwriting the caller's origSender
with the realSender returned by GetMessageSecret. The SQL lookup resolves
@lid↔@s.whatsapp.net via whatsmeow_lid_map so the key is found regardless
of which form was stored, but realSender reflects the *stored* form — not
necessarily the form the peer used to derive the HKDF context.

Concretely: after a DM is migrated to @lid addressing, a poll secret stored
pre-migration under `<phone>@s.whatsapp.net` is still looked up
successfully when the vote arrives keyed on `<lid>@lid` (thanks to the
CASE in getMsgSecret). But decryptMsgSecret then replaces our caller's
`<lid>@lid` origSender with the stored `<phone>@s.whatsapp.net` one,
feeds that into generateMsgSecretKey, and produces an HKDF context that
no longer matches what the voter used — so gcmutil.Decrypt fails with
'cipher: message authentication failed'.

Fix: derive with the caller-supplied origSender (which matches the form
present on the echoed origMsgKey, i.e. the form the peer used). Keep a
fallback path that retries with realSender so legacy stores whose keys
predate LID-aware derivation continue to decrypt.

Observed symptom: DecryptPollVote returning 'cipher: message authentication
failed' for polls in DMs that WhatsApp has silently migrated to @lid.

Signed-off-by: dgattupalli <219828309+dgattupalli696@users.noreply.github.com>
@dgattupalli696 dgattupalli696 changed the title fix(msgsecret): preserve caller origSender across @lid<->phone chat migration fix(msgsecret): preserve caller origSender across @lid↔phone chat migration Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant