Skip to content

Private group channel: outgoing messages encrypted with a different key/hash than the one used for incoming decryption, and channel chat view never shows correctly-decrypted incoming messages #3119

Description

@SeByDocKy

Summary

For a manually-created private group channel (name + hex secret key, not a hashtag/public channel), the app appears to maintain two different internal representations of the same channel:

One that is used to correctly decrypt incoming GRP_TXT packets against the PSK I entered (visible in the low-level packet log / "reception log" tool).
A different one, with a different derived channel hash, that is used when sending a message from the channel's chat screen.

As a result: (a) the chat screen never shows incoming messages for that channel, even though they are provably received and correctly decrypted, and (b) outgoing messages sent from the chat screen are broadcast under the wrong channel hash, so no other device configured with the channel's real PSK can decrypt them.

Environment
Device: Heltec V3 (companion firmware, from meshcore-dev/MeshCore)
App: MeshCore Open, Flutter client (github.com/zjs81/meshcore-open) — please post this issue there rather than meshcore-dev/MeshCore, since the likely root cause (see "Additional notes" below) points at app-side channel index handling; open an issue on meshcore-dev/MeshCore instead only if the companion firmware side turns out to be responsible.
(please fill in exact firmware/app version numbers before posting)
Second device on the mesh: custom ESPHome-based node (independent, from-scratch implementation of the GRP_TXT wire format, built directly from this repo's source: src/Utils.cpp encryptThenMAC/MACThenDecrypt, src/helpers/BaseChatMesh.cpp addChannel, src/Mesh.cpp createGroupDatagram). This implementation has been independently verified against FIPS-197 (AES-128), NIST/RFC 4231 (SHA-256/HMAC-SHA256) test vectors, and against this repo's source, so it is a reliable reference for what the "correct" channel hash and ciphertext should be.
Steps to reproduce
On the ESPHome node, generate a random 16-byte PSK: 50efaa43aba25de4ce1e736e8354625a (hex) / UO+qQ6uiXeTOHnNug1RiWg== (base64).
Compute the expected channel hash independently: channel_hash = SHA256(psk || 16 zero bytes)[0] = 0x23. Verified with two independent implementations (custom C++ matching this repo's algorithm, and plain Python hashlib).
In the app, create a new private channel, name test-debug-01, secret key (hex) = 50efaa43aba25de4ce1e736e8354625a.
Have the ESPHome node (configured with the same PSK) send a GRP_TXT message on this channel.
Open the app's low-level "reception log" — the incoming packet is shown with Hash du canal: 23, correctly matched to test-debug-01, and the plaintext is correctly decrypted and displayed. → This message never appears in the channel's chat screen, despite being correctly received and decrypted.
From the same channel's chat screen in the app, send a text message (e.g. "Test2").
On a listening device, the outgoing packet is captured with Hash du canal: ad — a different hash than 0x23, and not decryptable by any device configured with the PSK that was entered for this channel.

This was reproduced identically on two separate freshly-created channels with two different PSKs (first with a different PSK giving a wrong hash 0x85, then with the PSK described above giving 0x23 expected vs 0xad observed on send) — so it is not specific to one channel or one PSK.

Expected behavior
A message sent from the channel's chat screen should be encrypted using the exact PSK stored for that channel, producing the same channel hash (0x23 in this example) as used for incoming-message matching.
Incoming messages that are correctly identified and decrypted (as shown in the reception log) should appear in that channel's chat screen.
Actual behavior
Outgoing chat messages use a different (seemingly arbitrary, different each time) channel hash than the one associated with the channel's actual configured PSK.
Incoming messages that decrypt correctly and are logged with the right channel name/hash never appear in the chat screen for that channel.
Additional notes
This is not a PSK entry mistake on my end: the PSK used to derive the "expected" hash was generated with openssl rand -hex 16, entered into the app exactly, and independently verified byte-for-byte (hex ↔ base64 round-trip, and hash re-derivation with two independent tools).
This is reproducible after fully uninstalling and reinstalling the app (channel state appears to live on the companion device itself, not just the app).
I initially suspected a duplicate/stale channel entry from an earlier "join" attempt, but the same mismatch reproduces on a channel created for the very first time, with a name never used before, so a leftover duplicate does not fully explain it.
I'm using the MeshCore Open Flutter client (github.com/zjs81/meshcore-open). Looking at its source: outgoing channel messages are sent via buildSendChannelTextMsgFrame(channelIndex, text), which only transmits [cmd][txt_type][channel_idx][timestamp][text] over BLE — the PSK never leaves the app, encryption happens entirely on the companion device using whatever PSK is stored at channel_idx on the device. Channel.parsePskHex() (the hex-to-bytes parsing when creating a channel) looks correct and simple, so the entered PSK itself should not be at fault. This strongly suggests the app is sending to (or the companion is storing under) the wrong channel index — i.e. the index the chat screen is bound to for sending does not hold the same PSK as the index that correctly matches incoming packets. The app picks a new channel's index via _findNextAvailableIndex() in channels_screen.dart, which computes the first free slot from the app's locally cached channel list rather than re-querying the device — if that local cache is out of sync with what's actually stored on the companion (plausible after repeated create/join attempts or an app reinstall), the chat screen could end up bound to a stale index holding an old/wrong PSK from a previous attempt, while a different index (with the PSK I actually entered) exists on the device and is what the low-level reception log matches against.
Happy to provide raw packet captures (hex), the exact PSK/hash values used, or test further with additional freshly-created channels if that helps narrow down the cause.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions