MeshCore Security Architecture

Deep-dive into MeshCore encryption: AES-256-CTR channel traffic, ECDH key exchange for direct messages, channel key derivation, and practical security properties of public vs private channels.

MeshCore Encryption Overview

This page summarizes MeshCore's encryption as verified from the official source code. The key facts: AES-128 symmetric encryption, ECDH key exchange using Ed25519 keys transposed to X25519, and a 2-byte truncated MAC (derived from HMAC-SHA256) for message authentication.

Verified Encryption Summary

ComponentAlgorithmNotes
Symmetric cipherAES-128 ECB16-byte key (CIPHER_KEY_SIZE=16); zero-padding on final block
Message authentication2-byte truncated MAC (from HMAC-SHA256)CIPHER_MAC_SIZE=2; encrypt-then-MAC, MAC prepended before the ciphertext
Key exchangeECDH via X25519Ed25519 keys converted to X25519 for DH; AES-128 uses 16 bytes of the 32-byte shared secret, the MAC is keyed with the full 32
Identity keysEd2551932-byte public key, 64-byte private key
Advertisement signingEd25519 signaturePrevents node identity spoofing

Security Caveats

Common Misconceptions

Source: Official MeshCore repository source code. Verified 2026-05-03.

Understanding ECDH Key Exchange in MeshCore

Elliptic Curve Diffie-Hellman (ECDH) is the cryptographic mechanism MeshCore uses to establish a shared secret between two nodes without that secret ever being transmitted over the radio. This page explains the underlying mathematics, describes how MeshCore uses ECDH in practice, and contrasts it with Meshtastic static PSK.

The Diffie-Hellman Principle

The Diffie-Hellman key agreement protocol solves a specific problem: how can two parties who have never met agree on a shared secret while communicating over a channel that an adversary can fully observe?

The classical analogy uses paint mixing. Alice and Bob both start with a public colour (yellow). Alice mixes in her secret colour (red) to get orange and sends orange to Bob. Bob mixes in his secret colour (blue) to get green and sends green to Alice. Alice adds her secret red to the green she received and gets a specific brownish mixture. Bob adds his secret blue to the orange he received and gets the same mixture. Both arrive at an identical colour without either secret colour ever being sent.

The mathematical version replaces paint with modular exponentiation in a finite group. The group structure makes forward computation easy but reversal computationally infeasible: the discrete logarithm problem.

The Elliptic Curve Variant

Classic Diffie-Hellman requires large key sizes (2048-4096 bits) for adequate security. Elliptic Curve DH achieves equivalent security with much shorter keys: a 256-bit ECC key provides roughly the same margin as a 3072-bit RSA key. This matters enormously for embedded LoRa nodes where flash, RAM, and CPU are scarce.

MeshCore node identities are Ed25519 keypairs (Curve25519-family). For the ECDH key exchange, the Ed25519 keys are transposed to their X25519/Curve25519 (Montgomery) form to compute the shared secret. Curve25519 was designed by Daniel J. Bernstein for high-performance constant-time implementation on small processors. It avoids the implementation pitfalls such as timing side channels and weak curve parameters that have plagued other ECC curves. The public key is 32 bytes; the Ed25519 private key is 64 bytes (PRV_KEY_SIZE = 64). The 32-byte public key is stored in NVS and transmitted in advertisement packets.

In Curve25519 ECDH:

shared_secret = scalar_mult(my_private_key, their_public_key)
 = scalar_mult(their_private_key, my_public_key) // identical result

The scalar_mult function is a point multiplication on the elliptic curve. Computing it in the forward direction is efficient; reversing it to recover a private key from a public key and shared secret requires solving the elliptic curve discrete logarithm problem, for which no polynomial-time algorithm is known.

How MeshCore Uses ECDH in Practice

Step 1: Static Keypair Generation

At first boot, each MeshCore node generates an Ed25519 private key (64 bytes) from the platform hardware RNG and derives the corresponding 32-byte public key. Both are written to non-volatile storage and persist across reboots. These are static keypairs because they remain fixed for the lifetime of the firmware installation, as opposed to the ephemeral keypairs used per-session in TLS 1.3.

Step 2: Public Key Advertisement

The public key is included in the node periodic advertisement packet and propagated hop-by-hop across the mesh using MeshCore controlled-flood mechanism. Over successive advertisement cycles, nodes learn the public keys of peers whose adverts they receive and store them in their contact list. No prior direct contact is required before a secure direct message can be sent.

Step 3: Shared Secret Derivation on Demand

When node A wants to send an encrypted direct message to node B, it computes:

shared_secret_AB = Curve25519(A_private_key, B_public_key)

When node B receives the message, it computes:

shared_secret_AB = Curve25519(B_private_key, A_public_key)

Both operations produce the same 32-byte value. This shared secret is used as the AES-128 key (16 bytes of it) used to encrypt and decrypt the message body; the full 32-byte secret also keys the HMAC. MeshCore uses AES-128, not AES-256. The shared secret is cached in RAM after first derivation to avoid recomputing it on every subsequent message to the same peer.

Step 4: AES Encryption with the Derived Key

The derived AES-128 key encrypts the payload in ECB mode with zero-padding on the final block. MeshCore does not use CTR or GCM mode and there is no per-packet nonce; ECB encrypts each 16-byte block independently. A timestamp embedded in each message body helps vary the plaintext. Note: ECB leaks equality of identical 16-byte plaintext blocks, a known weakness. After encryption, a 2-byte truncated HMAC-SHA256 MAC (encrypt-then-MAC) is prepended to the ciphertext, and the result is placed in the packet payload and transmitted.

Comparison with Meshtastic Static PSK

PropertyMeshCore ECDH (direct messages)Meshtastic Static PSK
Key material transmitted over radioPublic keys only; shared secret never transmittedPSK never transmitted but must be distributed out-of-band to all participants
Key uniqueness per pairEach node pair has a unique shared secretAll nodes in channel share the same key
Compromise of one deviceExposes messages to and from that device onlyExposes all channel messages past and future
Forward secrecyNone: keys are static (not ephemeral), so disclosure of a private key retroactively decrypts all recorded traffic for that node.None: historical traffic decryptable if PSK obtained
Key rotationReflash firmware or clear NVS to generate new keypairChange PSK on all channel members simultaneously
Setup complexityAutomatic: keys generated at boot and exchanged via mesh advertisementsManual: PSK must be configured identically on all nodes
CPU cost per messageAES only after first exchange; ECDH result cached per peerAES only
Effective against passive recording plus later key disclosureNo — keys are static, not ephemeral. If a node's private key is ever recovered, all previously recorded traffic to and from that node can be decrypted. MeshCore direct messages do NOT provide forward secrecy.No: PSK disclosure retroactively decrypts all recorded traffic

There is no key-revocation mechanism. If a device is lost or stolen, other nodes continue to trust and encrypt to its public key until each is manually updated. Plan for manual contact-list cleanup after any device compromise. To rotate a node's own keys you must reflash firmware or clear NVS to generate a new keypair.

Practical Implications for Message Privacy

For direct messages, the ECDH model means two nodes can communicate privately without pre-arranging any shared secret. An eavesdropper who captures every radio packet including the advertisement floods carrying both public keys cannot reconstruct the shared secret and cannot decrypt the messages.

The key operational risk is device compromise. The private key stored in NVS flash is the single point of failure for a node message privacy. On platforms without hardware-enforced flash encryption, physical access to a device is equivalent to possessing the private key. Treat any captured or unaccounted-for device as a key compromise event and reflash it with new keys before returning it to service.

The ECDH model also provides an implicit mutual authentication property. Because ECDH produces the correct shared secret only when both private keys are used, impersonating node B to node A requires producing ciphertext derivable from B private key, which is infeasible without that key. An adversary who has only the public key cannot derive the shared secret and so cannot trivially spoof a node identity. Two caveats apply: the on-wire MAC is only 2 bytes (truncated HMAC-SHA256), which gives roughly 1-in-65,536 forgery resistance per attempt and is weak against active forgery attempts; and this authentication is only as good as the public key you hold. MeshCore learns keys automatically from unsigned-name advertisement floods with no out-of-band verification, so an attacker who injects an advert with their own key under a chosen name can be added as a contact. Verify a contact's full public key out-of-band before trusting direct-message authentication.

Channel Security and Private Networks

MeshCore's channel system organizes mesh traffic into communities of interest. Understanding what public and private mean in the MeshCore context is essential for anyone deploying MeshCore in environments where confidentiality matters.

How Channel Keys Work

Each MeshCore channel is identified by a name and protected by a 16-byte (128-bit) secret key. The channel name and channel secret are separate values - the name is a human-readable label, while the secret is the actual cryptographic key used to encrypt and authenticate channel traffic. Channel traffic is encrypted with AES-128 in ECB mode and carries a 2-byte (16-bit) truncated HMAC-SHA256 MAC.

Channel configuration (name + secret) can be shared between devices via QR code, using the format:

meshcore://channel/add?name=ChannelName&secret=<32-hex-chars>

where the secret is a 16-byte value expressed as 32 hexadecimal characters (verify the exact scheme against the MeshCore QR code formats page before hand-constructing a link). Note that the default public channel uses a well-known, publicly documented key (8b3387e9c5cdea6ac9e5edbaa115cd72), so do not confuse the public default key with a private secret you generate.

Public Channels

A public channel uses a well-known channel name and a shared secret that is distributed openly within a community (the default public channel's key is publicly documented). Any node configured with the same channel name and secret can join and read all traffic. Because the key is publicly known, public-channel traffic is readable by anyone in radio range — it is not private or secure against observers.

Public channels are appropriate for:

Do not use a public channel for sensitive emergency coordination. Public channels offer no confidentiality (the key is publicly known) and no sender authentication (any participant can forge messages from any name). For emergency nets, use a private channel with a secret key, and treat even that as group-shared, not per-sender authenticated.

Nodes on a public channel broadcast their advertisements including display names and public keys to all other nodes. Traffic analysis (who communicates with whom, at what signal strength) is visible to all participants and to any passive radio receiver tuned to the correct LoRa parameters.

Private Channels

A private channel uses a channel secret that is kept confidential within the intended community. Only nodes configured with the correct channel secret can decode traffic on that channel.

Private channels are appropriate for:

Configuring a Private MeshCore Channel

  1. Generate a channel secret: Use a cryptographically random 16-byte value. A password manager or command like openssl rand -hex 16 works well. Avoid predictable values.
  2. Configure the channel in the MeshCore app: Go to channel settings and enter the channel name and secret.
  3. Share the channel configuration: Use the QR code export feature in the MeshCore app to share the channel with trusted members. Anyone who receives the QR code will have access to the channel.

What Channel Encryption Provides and Does Not Provide

Channel encryption provides:

Channel encryption does NOT provide:

For communications requiring stronger guarantees, use direct (unicast) messages, which use per-pair ECDH key agreement providing individual authentication and stronger confidentiality. Note the caveats: direct messages still use AES-128-ECB, a 16-bit (2-byte) MAC, and static keys (no forward secrecy), and their authentication depends on having verified the peer's public key out-of-band.

Source: MeshCore QR code format documentation (github.com/meshcore-dev/MeshCore)