A compact, dependency-free secure-channel protocol for IoT, implemented in modern C++17. It gives two devices that share a pre-shared key a mutually authenticated, encrypted, replay-protected channel with forward secrecy — built on cryptographic primitives written from scratch and validated against the official RFC/NIST test vectors.
Why it exists. Constrained IoT links often ship with home-grown "encryption" (XOR, custom hashes) that collapses under a real attacker. SecureIoT shows how to do it properly with standardized primitives and an auditable protocol — and backs the design with tests, fuzzing, sanitizers, a threat model, and a wire specification.
- 🔐 Real, standardized cryptography, from scratch — SHA-256, HMAC, HKDF, ChaCha20-Poly1305 AEAD, and X25519, each checked against its RFC/FIPS test vectors (no OpenSSL, no libsodium — zero third-party dependencies).
- 🤝 Mutually authenticated handshake — 3 messages, ephemeral X25519 + PSK, with a TLS-1.3-style HKDF key schedule and transcript-bound Finished MACs.
- ⏩ Forward secrecy — ephemeral keys are generated per handshake and wiped; a later PSK leak does not expose past sessions.
- 🛡️ AEAD record layer — ChaCha20-Poly1305 with the record header authenticated as associated data and a TLS-1.3-style per-record nonce.
- 🔁 Anti-replay — per-record sequence numbers plus a 64-entry IPsec-style sliding window that tolerates reordering but rejects replays and stale packets.
- 🌐 Transport-agnostic — runs over the provided TCP demo, or any datagram / serial / LoRa transport that delivers whole messages.
- ✅ Engineered like production code — 37 unit/integration tests, libFuzzer harnesses, ASan/UBSan-clean, CMake + Make builds, and CI on Linux and macOS.
| Property | How it is achieved |
|---|---|
| Confidentiality | ChaCha20-Poly1305 AEAD per record (RFC 8439) |
| Integrity / tamper | Poly1305 tag over header + ciphertext; verify-before-release |
| Mutual authentication | PSK mixed into the key schedule + Finished MACs in both directions |
| Forward secrecy | Ephemeral X25519 key agreement (RFC 7748), private keys wiped |
| Replay protection | Sequence numbers + 64-entry sliding window (RFC 4303 style) |
| Transcript integrity | SHA-256 transcript hash bound into both Finished MACs |
See the threat model for the attacker model, the full attack/defense matrix, and the explicit assumptions and limitations.
git clone https://github.com/Je1al/SecureIoT-Protocol.git
cd SecureIoT-Protocol
make # build the static library + tools
make test # run the full test suite (RFC vectors + attack scenarios)
make demo # in-process end-to-end demonstration
make bench # micro-benchmarksCMake is also supported:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build --output-on-failuremake demo runs a complete handshake, encrypts a record, and then shows the
three core attacks being rejected:
=== Handshake (ephemeral X25519 + PSK authentication) ===
-> ClientHello (65 bytes)
<- ServerHello (97 bytes)
-> ClientFinished(32 bytes)
Handshake complete. device.established=1 gateway.established=1
=== Secured record (ChaCha20-Poly1305) ===
plaintext : temperature=22.5C;humidity=41%
record : 170000000000000000001e82267dd51c45ec6693...
decrypted : temperature=22.5C;humidity=41%
=== Attack rejections ===
replay -> rejected
tampered byte -> rejected
injected junk -> rejected
next genuine -> accepted: status=ok
Terminal 1 — gateway:
./build/secure_server --port 9009 --passphrase "field-key-42"Terminal 2 — device (the --replay flag re-sends a captured record to prove the
server rejects it):
./build/secure_client --port 9009 --passphrase "field-key-42" --count 3 --replay# server
[server] handshake OK; session established
[server] telemetry #1: reading[0]=temperature=20C
[server] telemetry #2: reading[1]=temperature=21C
[server] telemetry #3: reading[2]=temperature=22C
[server] record REJECTED (replay/tamper/forgery)
Initiator (device) Responder (gateway)
| ClientHello (version, c_random, c_eph_pub) |
| -------------------------------------------> |
| ServerHello (version, s_random, s_eph_pub, |
| server_finished) |
| <------------------------------------------- |
| ClientFinished (client_finished) |
| -------------------------------------------> |
| application records (both ways) |
| <==========================================> |
Key schedule (computed identically on both sides):
dh = X25519(my_eph_priv, peer_eph_pub)
th = SHA-256("SecureIoT/1" || c_random || c_eph_pub || s_random || s_eph_pub)
prk = HKDF-Extract(salt = PSK, ikm = dh) # PSK ⇒ auth, dh ⇒ forward secrecy
k_i2r, k_r2i, iv_i2r, iv_r2i = HKDF-Expand(prk, "secure-iot v1 traffic keys", 88)
The full byte-level wire format, nonce construction, and receive-side processing are specified in docs/PROTOCOL.md.
#include "secure_iot/protocol/session.h"
using namespace secure_iot;
Session::Psk psk = /* 32 bytes, provisioned out of band */;
Session device(Role::kInitiator, psk);
Session gateway(Role::kResponder, psk);
// Handshake (exchange these byte buffers over any transport)
auto m1 = device.client_hello();
auto m2 = gateway.process_client_hello(m1);
auto m3 = device.process_server_hello(m2); // verifies the gateway
gateway.process_client_finished(m3); // verifies the device
// Secure messaging
auto record = device.seal({'h','e','l','l','o'});
auto plaintext = gateway.open(record); // std::optional; empty if rejectedinclude/secure_iot/ Public API
crypto/ sha256, hmac_sha256, hkdf, chacha20, poly1305, aead, x25519, random
protocol/ config, replay_window, session
util/ bytes (endian), ct (constant-time), serial (safe parsing)
src/ Implementations
tools/ offline_demo, secure_server, secure_client, benchmark
tests/ Unit tests + RFC/NIST vectors + attack scenarios
fuzz/ libFuzzer harnesses for the parsers
docs/ PROTOCOL.md, THREAT_MODEL.md, ARCHITECTURE.md
See docs/ARCHITECTURE.md for the layering and design decisions.
make test # 37 test cases: RFC/NIST vectors + protocol scenarios
# AddressSanitizer + UndefinedBehaviorSanitizer
cmake -S . -B build-asan -DSECUREIOT_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build-asan && ctest --test-dir build-asan --output-on-failure
# Coverage-guided fuzzing of the wire parsers (Clang)
make fuzz CXX=clang++
./build/fuzz_handshake -max_total_time=60
./build/fuzz_record -max_total_time=60Every primitive is checked against the published vectors of its standard (SHA-256 → FIPS 180-4; HMAC/HKDF → RFC 4231/5869; ChaCha20-Poly1305 → RFC 8439; X25519 → RFC 7748), and the protocol is checked end-to-end against replay, tampering, injection, and wrong-PSK attacks.
- Optional certificate / Ed25519-signature authentication alongside the PSK
- 0-RTT early data for reconnecting devices
-
no_std-style embedded build profile (no heap, fixed buffers) - Formal model of the handshake (Tamarin / ProVerif)
The cryptography is standardized and validated against official test vectors, but this is an independent implementation that has not been formally audited. For production, prefer a vetted library such as libsodium. See SECURITY.md for details and how to report issues.
Released under the MIT License.