If you haven't seen SecureGen before — it's an open-source hardware security device on the LILYGO T-Display ESP32. TOTP/HOTP authenticator, encrypted password manager, BLE HID keyboard, and a web management cabinet with 8 layers of application-level security. No cloud, no app, no trust required.
Two major releases dropped since the last post. Here's what changed and why it was technically interesting.
v2.0.0 — The Security Rewrite
AES-256-GCM Transport Encryption
The original web transport used XOR — fast to implement, completely wrong for production. v2.0 replaced it with a full ECDH P-256 key exchange + HKDF-derived AES-256-GCM session key. Every request and response body is now encrypted end-to-end, with GCM providing authenticated encryption — tampered data is rejected, not just unreadable.
This runs without TLS certificates. The device has no CA infrastructure, no HTTPS, and works in AP mode with no internet. The encrypted channel is entirely application-layer.
PIN-Encrypted Device Key
Before v2.0, the master device key was derived from hardware parameters only (MAC, chip ID, flash ID). Useful but insufficient — physical access to the chip meant access to the key material.
Now the key is encrypted with the user's PIN via PBKDF2-HMAC-SHA256 at 25,000 iterations. The PIN never leaves the device. Without it, the encrypted key file on LittleFS is useless even if the flash is extracted.
Persistent PIN Lockout
Five failed PIN attempts across reboots locks the device permanently. The attempt counter survives power cycles — stored encrypted in LittleFS. No soft resets to bypass it.
Secure Memory Wipe Before Deep Sleep
Before entering deep sleep, the device zeroes device key, TOTP secrets, passwords, and session keys from RAM. On wake, everything requires re-authentication. This prevents cold boot attacks on battery-powered devices.
HOTP Support
Counter-based OTP (RFC 4226) added alongside TOTP. HOTP works in Offline and AP modes — no time sync required. Hold both buttons on the HOTP screen to generate the next code. The counter increments atomically and is written to flash after button release, preventing desync on battery power.
QR Code Import / Export
Add TOTP keys by scanning a QR code (camera or file upload) directly in the web cabinet. Export any key as a QR code displayed on the TFT screen. The offline decrypt_export.html tool lets you decrypt, inspect, and edit key files without the device connected.
v2.1.0 — Stability, Hardware, and UX
DS3231 RTC Module Support
TOTP requires accurate time. Without WiFi there's no NTP. The solution: a DS3231 hardware clock module (I2C, SDA=21/SCL=22, ~$2 on AliExpress) that maintains ±2ppm accuracy independently of network connectivity.
With RTC connected, TOTP works correctly in AP mode and fully air-gapped Offline mode. The module is configured once via the web cabinet — it keeps time even when the device is off.
Light Sleep Crash Fix
This one was subtle. On battery power, esp_light_sleep_start() caused a crash-on-wake via GPIO0 hardware interaction. The fix: pseudo-sleep — CPU clocked down to 40 MHz, display suspended, peripherals held. Functionally identical sleep behavior, no crash.
Boot Mode System
Three network modes are now first-class: WiFi, AP, and Offline. The default is saved to config and applied on boot. A 2-second prompt at startup lets you override with button presses.
"Reboot with Web Server" button in the web cabinet now works correctly even when boot mode is set to Offline — it sets a one-shot flag that bypasses the mode prompt entirely and forces WiFi on next boot.
Navigation Guard
The web cabinet fires ~4 encrypted requests during initialization (keys, PIN settings, theme, battery). Switching tabs before these complete broke the UI state.
Fix: async IIFE runs all init requests sequentially, sets appInitialized = true when done. Tab switching is blocked with a localized toast until initialization completes. No parallel requests — ESP32 async web server doesn't handle simultaneous encrypted sessions well under memory pressure.
Multilingual Interfac
Web cabinet now supports English, Russian, German, Chinese (Simplified), and Spanish. Language selection persists via localStorage. All UI strings go through tr() — adding a new language is one object in the i18n map.
Bug Fixes Worth Noting
HOTP display corruption. When switching from a TOTP key to an HOTP key, the old progress bar rendered on top of "HOTP - Static Code" text. Root cause: drawLayout() set lastTimeRemaining = -999 as a sentinel, but updateTOTPCode(-1) saw -1 != -999 as true only once — after that -1 == -1 skipped the redraw. Fix: eraseLoaderArea() also resets lastTimeRemaining = -999, forcing a full redraw after the loader completes.
HOTP notification showed "TOTP code copied". The copy handler checked keysData[index].type === 'hotp' — but the actual field is keysData[index].t === 'H'. One character difference, wrong notification for every HOTP copy.
Password reorder endpoint. The /api/passwords/reorder endpoint was registered in 4 of the required 6 locations — missing from both tunnel dispatchers. Requests tunneled through the obfuscated endpoint silently failed. Added to both dispatchers following the existing pattern.
Drag-and-drop key reorder mixed up timer/progress elements. DOM rows were physically moved but element IDs (timer-0, progress-0) stayed bound to old positions. Fix: call updateKeysTable(keysData) after every drag-drop to rebuild the table with correct IDs.
What's Next
- ECDH P-256 → X25519 migration (~400ms → ~80ms key exchange)
- Port to T-Display S3 (PSRAM, USB HID, larger screen)
- Flash encryption and secure boot via sdkconfig
- ATECC608 secure element support
Flash from browser (Chrome/Edge): https://makepkg.github.io/SecureGen/flash
GitHub: https://github.com/makepkg/SecureGen
Hackster.io: https://www.hackster.io/makepkg/securegen-open-source-totp-authenticator-password-manager-c350d6





Top comments (0)