Forward Secrecy for Meshtastic

Meshtastic is a pretty cool mesh communication system using LoRa, which lets it send very low bitrates over surprisingly long ranges with very little power. They’re great for telemetry and will happily run off a tiny battery and solar cell. Their security has some problems.

Channels are a Meshtastic “group chat”, so that any message sent to a channel can be read by any member of that channel. These can be made private by sharing a PSK, which is used to encrypt the messages with AES. There is no level of forward secrecy, so an attacker could store the encrypted messages, later get physical access to a node, read the PSK off the device over serial (or even Bluetooth), and decrypt any messages in the channel. Meshtastic devices are frequently left unattended in remote locations, so this would be a trivially easy attack to perform.

A very simple mitigation of the easiest version of this attack is to just stop allowing the PSK to be read from the admin interface. Making any attacker flash their own firmware or dump the flash storage would add a lot of friction. This post, however, is a much more complicated proposal for a scheme which would protect from even these slightly motivated attackers.

Constraints

So why not use something standard? Hardware constraints. Radio communication between nodes, especially distant nodes, is expensive and prone to failure, which makes interactive schemes onerous. Additionally, flash storage is limited and wears out relatively quickly from repeated writes. Erasing / overwriting old keys is very bad for the hardware.

A clock-synchronised symmetric ratchet

A vertical KDF chain against time: at each period n, the chain key ck_n and the input n go into a KDF, which outputs the message key mk_n and the next chain key ck_{n+1}.
The KDF chain.

Helpfully, many of these devices have a GPS module, which provides a synchronised clock. This motivates a clock-synchronised symmetric ratchet:

  • Preshare a chain key ck0, period X, epoch T.
  • Increment a counter n every X mins — n=⌊(t−T)/X⌋, where t is GNSS-time.
  • (ckn+1,mkn)=KDF(ckn,n), where mkn is the message key for period n.
  • Keep message keys mkn−1,…,mkn+1 in RAM; use these to receive messages.
  • Keep ckn−1 stored in flash memory, overwriting when n changes. Use this to rebuild the following keys after a power cycle.
  • Send messages using mkn.

A node which is added could be given ckn, and only read future messages. If a node is offline for a long time, it can just run the ratchet forward until it catches up.

Periods n-2 to n+1 running down the page. During period n, RAM holds mk_{n-1}, mk_n and mk_{n+1}, and flash holds ck_{n-1}. If the node is captured partway through period n, messages from period n-1 onwards and all future messages are exposed; period n-2 and earlier are safe.
What a node holds during period n, and what an attacker can read after capturing it at time t.

If an attacker reads key-state from the hardware, they’ll be able to decrypt up to the last 2X mins of messages but no more. Nodes can always decrypt messages sent within the last X mins, and this allowance includes clock-skew. There is a tradeoff here: larger values of X would be able to handle larger message delays, and be gentler on the hardware. Smaller values of X would decrease the window in which an attacker could read messages after taking a device. A value of {1,5,15,30} minutes seems reasonable, but I’m not the person to make that judgement.

This is very similar to Signal’s symmetric-key ratchet, except that it is advanced by the passage of time.

Storage and messages

This would need, I think, 36 bytes of flash storage: 32 bytes for the chain key and say 4 bytes for a counter. Flash storage is pretty fiddly though and I could imagine there might be some issues with power loss mid-write.

Worse, repeated writes to flash storage wear it down. Pages on these chips can be erased something like 10,000 to 100,000 times before failure. At X=30 mins, this could cause a failure after 7 months of uptime. NOR flash allows writing zeros over a block without erasing a page, which could provide a route around this problem, but lots of popular flash chips do not. Even if it were supported in hardware, from what I know of Meshtastic, this would be a pain to implement. Their existing file system uses wear-leveling which would totally break the forward secrecy.

It will not be obvious which n was used to encrypt a message, so we’d also need to add 2 least-significant bits of n in the message headers.

What this doesn’t give you

Unlike other (better) systems, this scheme provides no post-compromise security. If an attacker is able to get physical access to a device and read the key, they could read all future messages on the channel. Signal solves this by adding a Diffie-Hellman ratchet, in which both parties in an exchange update a shared DH secret in each step, which seeds the KDF chain. I can’t think of an equivalent which can tolerate such unreliable message delivery and doesn’t add a lot of messaging overhead, but if you have one I’d love to hear it.

A motivated attacker could spoof GNSS signals to make the device roll forward the ratchet, and then it would be non-operational until reprovisioned. This could be mitigated with some kind of independent time-keeping.

This would not work with a number of Meshtastic features which allow messages to be delivered very late, such as MQTT bridging or Store-and-Forward. It would also require every node that can read messages to have GNSS, or some other accurate, trusted clock.

Conclusions

Although it is a serious proposal, this is mostly for fun, and I’m keen to hear any comments. I think the security of Meshtastic private channels under this proposal would be a dramatic improvement, but still very flawed. This would reduce some opportunistic attacks and prevent easy “harvest now, decrypt later” attacks. Ultimately securing unattended devices from a motivated attacker would require hardware changes and physical security.

Leave any technical commentary on the GitHub discussion.