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 Component Algorithm Notes Symmetric cipher AES-128 ECB 16-byte key (CIPHER_KEY_SIZE=16); zero-padding on final block Message authentication 2-byte truncated MAC (from HMAC-SHA256) CIPHER_MAC_SIZE=2; encrypt-then-MAC, MAC prepended before the ciphertext Key exchange ECDH via X25519 Ed25519 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 keys Ed25519 32-byte public key, 64-byte private key Advertisement signing Ed25519 signature Prevents node identity spoofing Security Caveats ECB mode leaks structure: ECB encrypts each 16-byte block independently, so identical plaintext blocks produce identical ciphertext blocks. A passive listener can detect repeated content, message patterns, and known headers without the key. Do not assume ECB hides patterns in templated or repetitive traffic. (MeshCore embeds a timestamp in each message to partially mitigate this.) The 2-byte (16-bit) MAC is weak: A truncated 16-bit MAC is an integrity check, not strong authentication. An active attacker can forge it by brute force in roughly 32,000 attempts. Channel "authentication" is also group-level only: any holder of the channel key can forge messages as any sender. No forward secrecy: Identity keys are static, so a single leaked private key decrypts all past and future recorded traffic for that node. There is no key revocation. The public/default channel key is publicly documented ( 8b3387e9c5cdea6ac9e5edbaa115cd72). Traffic on the public channel is readable by anyone; it is not private or secure against observers. Common Misconceptions Not AES-256: MeshCore uses AES-128, not AES-256. Key length (128-bit) is adequate; the real cryptographic limitations of MeshCore are the ECB cipher mode and the 2-byte (16-bit) truncated MAC described in the Security Caveats above — not the key size. Not CTR mode: The implementation uses ECB mode with zero-padding, not CTR or GCM mode. The official MeshCore website states "AES-128 encryption" - this matches the source code. 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 Property MeshCore ECDH (direct messages) Meshtastic Static PSK Key material transmitted over radio Public keys only; shared secret never transmitted PSK never transmitted but must be distributed out-of-band to all participants Key uniqueness per pair Each node pair has a unique shared secret All nodes in channel share the same key Compromise of one device Exposes messages to and from that device only Exposes all channel messages past and future Forward secrecy None: 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 rotation Reflash firmware or clear NVS to generate new keypair Change PSK on all channel members simultaneously Setup complexity Automatic: keys generated at boot and exchanged via mesh advertisements Manual: PSK must be configured identically on all nodes CPU cost per message AES only after first exchange; ECDH result cached per peer AES only Effective against passive recording plus later key disclosure No — 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: General-purpose mesh traffic Broad-access community traffic where confidentiality is not required Testing environments Wide-area mesh backbones carrying routing advertisements 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: Closed community networks such as neighbourhood groups, amateur radio clubs, and emergency response teams Commercial or industrial deployments requiring channel isolation Multi-tenant mesh scenarios where multiple independent groups share physical infrastructure Configuring a Private MeshCore Channel 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. Configure the channel in the MeshCore app: Go to channel settings and enter the channel name and secret. 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: Confidentiality of message content from nodes not on the channel (only for private channels with a secret key; the public channel's key is publicly known, so it provides no confidentiality) Group-level authentication only — any holder of the channel key can generate valid messages under any display name. There is no per-sender authentication on channel messages, and the 2-byte (16-bit) MAC is forgeable by an active attacker (on the order of ~32k attempts); it is an integrity check, not strong authentication. Channel encryption does NOT provide: Perfect forward secrecy - the same key is used indefinitely; compromise of the key reveals all past and future traffic Individual message authentication - unlike direct messages (which use per-pair ECDH keys), channel messages are authenticated only by possession of the shared key, so any key-holder (or anyone who obtained the key from a lost device or leaked QR code) can forge a message attributed to any other member Protection against traffic analysis - signal strength, transmission timing, and node identifiers are visible to any receiver tuned to the correct LoRa parameters 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)