Known Limitations and Future Plans of Meshtastic's Encryption
Meshtastic’s security model sits at the intersection of multiple conflicting requirements. This document explains the requirements, the tradeoffs they represent, and characterizes the current limitations chosen to make Meshtastic usable. It also covers recent changes and upcoming fixes.
This information is not new, but until now was spread through Discord discussions, GitHub issues, and comments in the Meshtastic codebase. This document is an effort to plainly lay it out in one place.
History
First, Meshtastic was originally designed to work with closed, trusted groups only. Meshtastic channels are encrypted with a Pre-Shared Key (PSK) and AES-CTR. This encryption type does not include authentication, and as such, anyone with the PSK can send a message as any other user on that channel.
AES-CTR does have another weakness, in that it produces a cipher stream for a given Initialization Vector (IV), and the actual encryption step is done by XOR’ing the plaintext with that stream. In Meshtastic Channel messages, this IV is a combination of the sender’s Nodenum and the PacketID of the given message. This does mean that if an attacker can deduce the exact plaintext of an encrypted message, an attacker can re-use the Nodenum and PacketID combination to send spoofed messages, even without knowing the PSK of the channel. This is of limited use due to the requirement for PacketID and source NodeNum reuse, to get a matching IV.
In its first iteration, Meshtastic handled Direct Messages (DMs) by simply using the existing channel PSK, and marking DMs as only directed to the target node. This approach was acceptable when Meshtastic only being used by small groups, but the advent of public meshes with the potential for bad actors has changed the equation significantly. For about a year, Meshtastic has been intentionally adding features to harden it against this scenario, but with minimal compatibility breaks.
The PSK DMs in particular were recognized as a problem, and the new DM system was rolled out with Meshtastic 2.5. This system uses x25519 public key cryptography and AES-CCM to encrypt and authenticate DMs sent between nodes. These public keys are sent inside User packets encrypted with the existing channel PSKs.
Limitations
This system, by necessity, uses a Trust On First Use (TOFU) model. There is no central authority to sign user keys, and so nodes will store and retain the first public key announced for a given node number. This is essentially a hard requirement of a decentralized mesh network.
This issue is compounded by the limited memory for storing nodes on a Meshtastic node. The NodeDB is limited to 100 nodes on most hardware, and when more Nodes are seen on a network, the oldest and least interesting node rolls off the NodeDB to make room for new nodes. The exception to this is that favorited nodes are guaranteed not to be removed from the NodeDB.
The combination of the TOFU model and constrained hardware leads to a problem. When a node rolls off the NodeDB, the Meshtastic firmware has no way to confirm that a future User packet isn’t a spoof of that Node Number, with a different public key. This problem is made worse by the possibility that an attacker on the channel can quickly create fake nodes, and cause legitimate nodes to roll off the NodeDB sooner.
This attack was anticipated when the DM system was designed, and accepted as an inevitable result of building an ad-hoc, decentralized mesh. Again, there is no central authority to sign keys. As a result, multiple mitigations were built in to the system to minimize the actual usefulness of this attack.
Intended Behavior
First, while the NodeDB is limited to 100 nodes on embedded hardware, a connected mobile client has the ability to store information about many more nodes, and flag to the user when one of those nodes shows up with a different public key. This is what happens when a node on a client shows up with a red key icon. The firmware has seen a different public key for that node, and the mobile client knows that it has changed.
The second major mitigation is that nodes that are marked as favorite are never dropped from the NodeDB. Clients have now automatically mark nodes as favorite when a DM is sent to that node, further ensuring that the node a user was chatting with is still the same node.
The meshtastic firmware does also support falling back to the old DM behavior. This is tightly controlled via the use of the “pki_encrypted” boolean and the “public_key” byte field. When the firmware receives a packet from the mesh, if that packet is sent using the DM PKI encryption, the bool is marked true, and the source public key is copied into the bytes field. DMs using the old channel PSK method will still be received and processed, but the pki_encrypted field is set false.
Packets sent from a connected client may set the “pki_encrpyted” boolean to true, and populate the public_key byte field on a packet sent through the local API. If the boolean is set to true, then the packet will only be sent via a PKI DM. If the bytes are populated, the packet will only be sent if the public key in the API message matches the public key for that node in the local NodeDB. If the boolean is not set to true, the firmware will send the packet as a PKI DM if it is indeed sent to a single target, and the firmware has a public key for that target. Outgoing messages will fall back to using the less secure channel encryption if no public key is known for the remote node and the pki_encrypted bool is unset
The ED25519 Future
A few weaknesses of the Meshtastic system can be addressed by adding a message signing system to channel messages. As we still have limited NodeDB storage space and LoRa packets are still limited to 256 byte maximum size, it’s a virtual requirement that this signing system reuse the existing public/private key pairs, and the signatures be as small as possible.
There has been an additional suggestion repeatedly made, that the nodeNum should be derived from the publicKey, to minimize the ability of an attacker to spoof a User packet from an existing nodenum with a new public key. While the Meshtastic security team has generally agreed with the wisdom of this suggestion, it has been impractical to implement as a breaking change to the mesh. The addition of signed messages actually presents an opportunity to enforce this new nodenum source, while still remaining backwards compatible with the older system.
The ED25519 support is still being finalized as of the writing of this document, but the current prototype code uses the XEdDSA system from Signal to take existing X25519 signatures and re-use them as ED25519 signatures. While this does significantly improve the security assurances of the Meshtastic system, these signatures are 64 bytes long, which is significant given the LoRa message size.
Once a node has successfully signed a User message with this scheme, the HAS_XEDDSA_SIGNED bit is set on the NodeInfoLite bitfield, which is translated to the has_xeddsa_signed bool on a NodeInfo message.
Once this bit is set, unsigned user packets from this source will be dropped. To be determined is how other unsigned packets will be treated. At this point, the recommendation is that short packets, that could be signed, should be dropped when unsigned.