ESPCrypto wraps the ESP32 hardware crypto blocks (SHA, AES-GCM/CTR, RSA/ECC) with guardrails, automatic fallbacks, and high-level helpers (JWTs, salted hashes) that work in both ESP-IDF and Arduino builds.
- GitHub Actions builds against the ESP32 Arduino core
3.3.3via Espressif's board manager URL (IDF 5.x generation) and caches the toolchains to keep PlatformIO/Arduino builds in sync foresp32,esp32-s3,esp32-c3, andesp32-p4boards. - Runtime code gates the mbedTLS 2.x (ESP-IDF 4.x) and 3.x (ESP-IDF 5.x) API differences—including the ESP AES-GCM alt streaming signatures—so PlatformIO/Arduino builds succeed regardless of which ESP-IDF revision a board package ships.
- Device fingerprinting prefers
esp_read_macfromesp_mac.hwhen present and falls back toesp_efuse_mac_get_default, so Arduino/PlatformIO board packages that droppedesp_efuse_mac.hstill build.
- SHA256/384/512 helpers that try the ESP parallel SHA engine first and fall back to mbedTLS when the accelerator (or platform) is unavailable.
- AES-GCM and AES-CTR utilities with a safe
aesGcmEncryptAutothat generates a random 12-byte IV, optional nonce-reuse debug guard, and capability introspection viaESPCrypto::caps(). - RSA/ECC signing + verification helpers (PKCS#1 v1.5 + ECDSA) that power HS256/RS256/ES256 JWT flows or stand-alone signatures.
CryptoKey+KeyHandleabstractions withMemoryKeyStore,NvsKeyStore, andLittleFsKeyStorefor alias/versioned key rotation plus cached mbedTLS contexts to avoid repeated parsing.- Device-bound HKDF helper
deriveDeviceKey(...)that seeds from an optional persistent NVS secret and the chip fingerprint so long-term symmetric keys are not hard-coded. - Buffer-friendly span overloads for SHA digests and AES-GCM encrypt/decrypt that write into caller-provided buffers to reduce heap churn on large payloads, plus streaming contexts (
ShaCtx,HmacCtx,AesCtrStream,AesGcmCtx) for chunked workloads. - Nonce strategies for AES-GCM auto IVs: random 96-bit (default), counter+random hybrid, or boot-counter based with optional NVS persistence to avoid reuse under long-lived keys.
- Modern lanes: ChaCha20-Poly1305 for CPUs without AES accel, X25519 ECDH helper, and ECDSA DER↔raw helpers to interop with JOSE stacks. (Ed25519/EdDSA stay capability-gated to platform support.)
- HMAC/HKDF/PBKDF2 (SHA-256/384/512) building blocks with policy enforcement for PBKDF2 iteration counts; password hashing uses these primitives and constant-time verification.
- Structured
CryptoStatus+CryptoResult<T>with span-friendly overloads to reduce heap churn and keep error handling uniform;SecureBuffer/SecureStringzeroize sensitive data on scope exit. - Full JWT builder/validator that uses ArduinoJson v7
JsonDocuments, fillsiat/exp/nbffields, enforces issuer/audience, and exposes both friendly errors and structured status codes. - Ready-to-flash example plus Unity tests under
test/test_esp_cryptowith NIST/RFC vectors for SHA, AES-GCM, HKDF, PBKDF2, JWT, and password hashing regressions.
examples/basic_hash_and_aes– SHA plus AES-GCM with auto IV/tag handling and structured status.examples/jwt_and_password– HS256 JWT creation/verification and password hashing/verification.examples/advanced_primitives– Capability/policy introspection, SecureBuffer/String, HMAC/HKDF/PBKDF2, AES-CTR streaming, and RSA/ECDSA signing flows.examples/keys_and_streaming– Keystore usage, streaming SHA/AES-GCM, nonce strategies, and device-bound key derivation.examples/bench_crypto– Tiny on-device timing loops for SHA and AES-GCM to gauge perf per board.examples/jwks_rotation– JWKS verification with rotatingkidvalues for HS256.
The basic AES example shows SHA and AES-GCM in one go:
#include <Arduino.h>
#include <ESPCrypto.h>
#include <vector>
void setup() {
Serial.begin(115200);
std::vector<uint8_t> key(32, 0x01);
std::vector<uint8_t> plaintext = {'h', 'e', 'l', 'l', 'o'};
String digest = ESPCrypto::shaHex("esptoolkit");
auto gcm = ESPCrypto::aesGcmEncryptAuto(key, plaintext);
if (gcm.ok()) {
auto decrypted = ESPCrypto::aesGcmDecrypt(key, gcm.value.iv, gcm.value.ciphertext, gcm.value.tag);
(void)decrypted;
}
}
void loop() {}Run examples/basic_hash_and_aes via PlatformIO/Arduino to see the full output.
Cache parsed keys, rotate aliases, and derive symmetric keys without shipping long-lived secrets in firmware:
#include <ESPCrypto.h>
void rotate_keys() {
MemoryKeyStore memory;
KeyHandle current{String("jwt_auth"), 2};
// Store a PEM private key and reload it as a cached CryptoKey
const char *pem = "-----BEGIN PRIVATE KEY-----...";
ESPCrypto::storeKey(memory, current, CryptoSpan<const uint8_t>(reinterpret_cast<const uint8_t *>(pem), strlen(pem)));
auto loaded = ESPCrypto::loadKey(memory, current, KeyFormat::Pem, KeyKind::Private);
if (loaded.ok()) {
auto sig = ESPCrypto::rsaSign(loaded.value,
CryptoSpan<const uint8_t>(reinterpret_cast<const uint8_t *>("payload"), 7),
ShaVariant::SHA256);
(void)sig;
}
// Derive a device-bound symmetric key using HKDF + NVS-backed seed
auto derived = ESPCrypto::deriveDeviceKey("provisioning", CryptoSpan<const uint8_t>(), 32);
if (derived.ok()) {
// use derived.value as an AES or HMAC key without embedding long-term secrets
}
}NvsKeyStore persists keys with optional encrypted NVS, LittleFsKeyStore stores blobs on LittleFS when mounted, and MemoryKeyStore stays in-RAM for tests or ephemeral rotations.
CryptoResult<std::vector<uint8_t>> shaResult(...)/shaHex(...)– SHA256/384/512 with optional hardware preference (default on) and structured status codes.CryptoResult<GcmMessage> aesGcmEncryptAuto(...)+aesGcmDecrypt(...)– 128/192/256-bit AES-GCM with random IVs, optional AAD, 16-byte tags, and policy-enforced IV length;aesCtrCrypt(...)covers stream-like CTR use cases.CryptoResult<std::vector<uint8_t>> rsaSign/eccSignandrsaVerify/eccVerify– Wrap mbedTLS PK contexts while enforcing minimum key sizes unlessallowLegacyis enabled.CryptoKeyhelpers for RSA/ECC reuse parsed PK contexts; pair them withKeyHandlealiases in aKeyStoreto rotate versions without reparsing PEM/DER.CryptoResult<void> sha(...)andaesGcmEncrypt/Decrypt(...)span overloads – write digests/ciphertext/tag into caller-owned buffers to avoid heap allocations on large payloads.- Streaming helpers:
ShaCtx/HmacCtxfor incremental hashing/HMAC,AesCtrStreamfor chunked CTR flows, andAesGcmCtxfor AAD + payload streaming with tag verification. GcmNonceOptionslets you pick random, counter+random, or boot-counter IV strategies (with optional NVS persistence) when usingaesGcmEncryptAuto(...).- JWT additions: JWK/JWKS verification helper, leeway support, multi-audience/typ enforcement, and DER↔raw ECDSA helpers to match JOSE encodings.
- ChaCha20-Poly1305 encrypt/decrypt helpers and X25519 shared-secret derivation for devices where AES accel varies. XChaCha20-Poly1305 and Ed25519/EdDSA APIs currently return
Unsupporteduntil the toolchain provides those primitives. CryptoResult<String> createJwtResult(...)/verifyJwtResult(...)– Build HS256/RS256/ES256 JWTs with autoiat/expfields and get back structured status plus the friendly error string versions.CryptoResult<std::vector<uint8_t>> hmac/hkdf/pbkdf2andhashString/verifyString– HMAC/HKDF/PBKDF2 building blocks; password hashes stay in the$esphash$v1$cost$salt$hashenvelope and compare in constant time.CryptoCaps caps()andSecureBuffer/SecureString– Introspect hardware acceleration availability and zeroize sensitive buffers on scope exit.
JwtSignOptions lets you set issuer, subject, audience, expiresInSeconds, notBefore, issuedAt, and keyId. JwtVerifyOptions can enforce issuer/audience matches, require expiration, and accept externally supplied clocks (e.g., SNTP time). Header/payload data stays as ArduinoJson v7 JsonDocuments, so you can merge them with doc.set(...) or stream them over serial for debugging. Use createJwt/verifyJwt for friendly strings or createJwtResult/verifyJwtResult for structured status codes.
verifyJwtWithJwks consumes an in-memory JWKS (JsonDocument) and picks keys by kid, with support for leeway, multi-audience payloads, typ enforcement, and crit header allowlists. ECDSA raw/DER conversion helpers are available when interoping with JOSE stacks that send compact raw signatures.
CryptoPolicy(default: RSA ≥ 2048 bits, PBKDF2 iterations ≥ 1024, GCM IV ≥ 12 bytes) is readable viaESPCrypto::policy()and adjustable withsetPolicy(...); setallowLegacy = trueto opt into weaker parameters.- AES-GCM can enable debug nonce-reuse detection via
ESPCRYPTO_ENABLE_NONCE_GUARD(tiny LRU cache keyed by IV + key fingerprint). constantTimeEqandSecureBuffer/SecureStringkeep comparisons and cleanup timing-safe.
- Constant-time coverage:
constantTimeEqunderpins password verification and HS256 JWT checks; other primitives lean on ESP-IDF/mbedTLS implementations and should be treated as best-effort constant-time rather than hardened side-channel countermeasures. - Hardware acceleration: SHA, AES-CTR, and AES-GCM try the ESP hardware blocks first and fall back to mbedTLS software paths;
ESPCrypto::caps()reports what is active at runtime. Random bytes come fromesp_fill_randomon-device and fromstd::random_deviceonly for host builds/tests. - Best-effort hardening: password hashes stay in a structured envelope with policy-enforced PBKDF2 costs, AES-GCM enforces IV length and offers an optional nonce-reuse guard, and sensitive buffers zeroize on scope exit or failure paths.
- Threat model: aimed at network-connected ESP32-class devices where attackers can send arbitrary inputs. It does not attempt to defend against physical capture, power/EM/fault-injection side channels, or secure element/key storage requirements; review your board’s secure boot/flash encryption story separately.
hashString emits $esphash$v1$<cost>$<salt>$<hash> so you can persist passwords without storing secrets. Costs map to 2^cost PBKDF2 iterations (default 10 ⇒ 1024) and will auto-bump to the policy minimum iteration count unless allowLegacy is enabled. verifyString accepts any string in that envelope, decodes the salt/hash, replays PBKDF2, and compares in constant time.
Hardware exercises run via PlatformIO Unity tests under test/test_esp_crypto, including KATs for SHA-2, AES-GCM (with tag checks), HKDF, PBKDF2, JWT HS256 round-trips, and password hashing. Host-side CMake just stubs out tests (ESP-IDF primitives are unavailable when cross-compiling for CI).
MIT — see LICENSE.md.
- Discover other libraries: https://github.com/orgs/ESPToolKit/repositories
- Website: https://www.esptoolkit.hu/
- Support the project: https://ko-fi.com/esptoolkit
- Visit the website: https://www.esptoolkit.hu/