An off-grid encrypted mesh communicator built on ESP32 and LoRa. No internet, no SIM, no servers — just direct encrypted radio communication between nodes up to 10km line of sight, with automatic message hopping across a mesh network.
Every communication tool today routes through someone else's infrastructure. Carriers, cloud servers, ISPs — there's always a middleman. I wanted to build something where two people could talk and the only thing carrying their message was radio waves and physics.
This started as a weekend experiment and turned into a proper firmware project with AES-256 encryption, a mesh relay system, RTC-based duplicate suppression, and a full serial CLI for node configuration.
Each node is an ESP32 paired with a LoRa E5 Mini (SX1262 radio) operating at 865 MHz in the India ISM band. Nodes communicate peer-to-peer over LoRa — no routing protocol, no coordinator, no infrastructure.
Encryption — every message is encrypted with AES-256 CBC before it leaves the device. Each peer relationship has its own unique key, generated on-device using the ESP32 hardware RNG. If one key is compromised, no other conversation is affected.
Mesh hopping — if the destination node is out of direct range, any node in between that receives the packet will automatically re-broadcast it with a decremented TTL. The packet hops until it reaches the destination or TTL hits zero.
Duplicate suppression — every packet carries a DS3231 RTC timestamp. Each node maintains a per-peer ring buffer of recently seen timestamps. Duplicate packets from relay hops are silently dropped. Packets older than 120 seconds are rejected regardless.
Packet structure — the header (TO, FROM, TTL, TYPE, TIMESTAMP) is always plaintext so relay nodes can route without decrypting. Only the payload is encrypted. Relay nodes never see message content.
[ TO:4 | FROM:4 | TTL:1 | TYPE:1 | TIMESTAMP:4 | LEN:1 | HDR_CRC:1 ]
[ IV:16 ]
[ AES-256 encrypted payload: up to 180 bytes ]
[ PAYLOAD_CRC:2 ]
| Component | Part |
|---|---|
| Microcontroller | ESP32 DevKit |
| Radio | LoRa E5 Mini (SX1262) |
| RTC | DS3231 |
| Interface | USB Serial (CLI) |
Wiring
ESP32 GPIO16 → LoRa E5 Mini RX
ESP32 GPIO17 → LoRa E5 Mini TX
ESP32 GPIO21 → DS3231 SDA
ESP32 GPIO22 → DS3231 SCL
ESP32 3.3V → LoRa E5 Mini VCC, DS3231 VCC
ESP32 GND → LoRa E5 Mini GND, DS3231 GND
Layer 5 — Serial CLI (cli.cpp)
Layer 4 — Application logic (mesh.cpp)
Layer 3 — Crypto engine (crypto.cpp)
Layer 2 — Packet codec (lora.cpp)
Layer 1 — Hardware drivers (lora.cpp, mesh_fw.ino)
Files
mesh_fw.ino main sketch, DS3231 driver, node identity
config.h all tunable parameters in one place
types.h shared structs — Packet, Peer, Result
peers.cpp/h NVS-backed peer store, key management, dedup cache
crypto.cpp/h AES-256 CBC encrypt/decrypt, CRC, hardware RNG
lora.cpp/h LoRa E5 Mini UART AT driver, packet serialisation
mesh.cpp/h routing — rx dispatch, relay, send
cli.cpp/h serial command interface
Dependencies
No external libraries needed. Everything used is built into the ESP32 Arduino core:
Preferences— NVS flash storagembedTLS— AES-256 (bundled with ESP32 core)Wire— I2C for DS3231HardwareSerial— UART to LoRa E5 Mini
Setup
- Open
mesh_fw.inoin Arduino IDE - Set your node address and name at the top:
#define DEFAULT_ADDR "AAAA" // 4 chars, unique per node
#define DEFAULT_NAME "Node1"- Flash to ESP32
- Open Serial Monitor at 115200 baud
- Sync the RTC:
/rtc set 2024 01 15 10 30 00
- On both nodes, add each other as peers and set a shared key:
/key gen → generates and prints a random 32-byte key
/peer add BBBB Alice → add the other node
/key set BBBB <64 hex chars> → paste the same key on both sides
- Send a message:
/msg BBBB hello
/msg <addr> <text> send an encrypted message
/ping <addr> ping a node
/peer add <addr> <name> add a peer
/peer rm <addr> remove a peer
/peer list list all peers and key states
/key gen generate a random AES-256 key
/key set <addr> <hex> assign a key to a peer (64 hex chars)
/rtc print current epoch
/rtc set YYYY MM DD HH MM SS set the RTC
/info show node address and name
/stats tx/rx/relay/drop counters
/help print this list
Configured for India ISM band 865 MHz. Tunable in config.h:
Frequency 865.0 MHz
SF SF9
Bandwidth 125 kHz
Coding rate 4/8
TX power 14 dBm
At SF9 / BW125 the maximum payload fits within the 222-byte LoRa limit with room to spare. Range is 10+ km line of sight, 1-3 km urban depending on obstructions.
- Key exchange is manual — you generate a key and paste it on both sides. ECDH over BLE is the next step.
- No display yet — everything is through the serial CLI.
- Peer limit is 10 nodes.
- No broadcast/group messaging, only unicast.
- ECDH key exchange over BLE — tap two devices together to pair
- OLED display + button navigation
- Group chat with shared keys
- Signed packets — sender authentication
- Battery + enclosure for field deployment
MIT