[Bug]: nonces are only randomized with 31 bits, this make it rare but possible to see duplicated nonces in the wild threatening confidentiality of messages #4042
Replies: 4 comments 4 replies
-
If someone knows where I could find a database of historical meshtastic messages (let's say captured from MQTT) I would like to see this, we could check for duplicate I guess we would see one or two ranges overlap, but they would use |
Beta Was this translation helpful? Give feedback.
-
The packet ID in the lora header will be truncated to 32 bit anyway, so this can not be changed. Doesn't mean the rest of the concerns are not valid, but i am closing this as it's a protocol restraint. Only reason this is defined as 64 bit is to make the nonce algo happy. |
Beta Was this translation helpful? Give feedback.
-
After sleeping on it I think we can fix it without breaking the protocol. packetRam = 0
packetLimit = 0
def generatePacketId():
if packetRam == 0:
packetRam = readPacketIdFromFlash()
if packetRam == 0:
packetRam = randomUint32()
limit = packetRam + 2**16
if limit == 0: limit += 1 # limit wrapped around to zero, skip it
storePacketIdIntoFlash(limit)
return packetId
packetRam++
if packetRam == 0: packetRam++ # we just wrapped around 0, skip it
if limit == packetRam: # we reached the value stored into the flash, bump it before proceeding
limit = packetRam + 2**16
if limit == 0: limit += 1 # limit wrapped around to zero, skip it
storePacketIdIntoFlash(limit)
return packetRam In english this stores the current packet ID into ram with a granularity of An other idea could be for modules with RTC use the RTC to seed |
Beta Was this translation helpful? Give feedback.
-
These nonce incrementing schemes aren't so great when you consider that many users are constantly reflashing devices-- better would be to use a cipher mode that isn't vulnerable to IV reuse. The messages sent in meshtastic are all very small, so the extra overhead of a iv misuse resistant scheme (e.g. AEC-GCM-SIV) shouldn't be substantial. |
Beta Was this translation helpful? Give feedback.
-
Category
Other
Hardware
Not Applicable
Firmware Version
current
Description
The IV initialization looks incorrect, it use 96 bits IV, but ~65* of them are fixed, so only ~31* bits are randomized.
It used to be stored in flash but apparently this did not worked (see 5a4fab2).
It now picks a random 31 bits number together with the lower 32bits of the mac address:
firmware/src/mesh/Router.cpp
Line 105 in b43c7c0
firmware/src/mesh/CryptoEngine.cpp
Lines 30 to 37 in b43c7c0
The crypto code use 64 bits packet ids, but the packet id type is defined as 32 bits:
firmware/src/mesh/MeshTypes.h
Line 10 in b43c7c0
There is then a zero-extension when calling encrypt:
firmware/src/mesh/Router.cpp
Line 424 in b43c7c0
If you use a 64bits number but 33 of them are fixed, you only gain 31 bits of security.
Due to the birth paradox assuming the randomness source is good you would need ~58k reboots to reach a 50% duplicate nonce chance on one node. This does not cause an issue across nodes because they each have a different nodeid.
The number of reboots is lower because each messages then incrementally update the packet id, so a reboot might only consume a handful of nonces or many thousands depending on how many messages were sent.
Given there are many thousand nodes each rolling for theses odds independently it is likely a some of duplicate nonces will be used some day if they weren't already.
The consequences of duplicated nonces with AES-CTR is that it becomes possible to perform known plaintext attack to decrypt one message knowing the other.
This is way worst than it sounds because it is possible to bruteforce each bit independently. That means if we know a tiny bit about the structure of the message (which for meshtastic we do, they are often nodeinfo, or chat messages) we can extend our guess with new information gained.
Independent information like exact GPS positions or Battery % are unlikely to be recoverable.
But we know chat messages are likely UTF8 and likely to form meaningful sentences, so you could bruteforce character by character until it makes meaningful words and sentences, this should be easy to do on a modern CPU and trivial on a modern GPU.
Combined with #4030 attack, you could replay a malicious with a custom content. Upside because there is no MAC or AEAD it's impossible to know if you are actually correct in your complete guess, so completely random pieces of information like arbitrary numbers or GPS position would be hard to impossible to confidently break if they align.
If one message is shorter than the other, only the shared length can be decrypted.
Because nonces are incremented sequentially, you wouldn't get a random duplicates from time to time.
You would see all overlapped messages back-to-back duplicates.
Checking for duplicated nonces is trivial, the nodeid and packetid are sent in plaintext (which is normal, there is hardly a way around that within meshtastic's tradeoffs context).
This could be improved by using a duplicate nonce resistant cipher like
AES-GCM-SIV
or deriving the nonce from the message hash or many other things. This looks like it would be a breaking change either way.*I say ~65bits because it is possible to roll something in the high 31 bits then the message increment would roll into 32 bits.
So 64 out of 96 bits are fixed, 31 bits are randomized and 1 is very likely to be 0.
Relevant log output
Beta Was this translation helpful? Give feedback.
All reactions