DEV Community

makepkg
makepkg

Posted on

Building a Hardware TOTP Authenticator on ESP32: The Memory Management Nightmare

Building a Hardware TOTP Authenticator on ESP32: The Memory Management Nightmare

I wanted a hardware 2FA device I could trust. Not a phone app that's always online, not a closed-source token - something open, auditable, and completely under my control.

So I built one on the ESP32 T-Display. Turns out, making a secure device on a microcontroller with 520KB RAM teaches you a lot about resource constraints.

The Problem: BLE + WiFi = Crash

My device needed to do two things:

  1. Generate TOTP codes (requires accurate time via NTP/WiFi)
  2. Type passwords via Bluetooth (requires BLE HID keyboard)

Sounds simple, right?

Here's what I learned the hard way:

The ESP32's BLE stack alone eats ~70KB of RAM. When you enable WiFi on top of that, heap memory becomes critically tight. Running both simultaneously? Random crashes. Memory fragmentation. Unexpected reboots.

// This will crash after a few minutes:
void bad_approach() {
    WiFi.begin(ssid, password);
    BLEDevice::init("SecureGen");
    // 💥 Heap exhausted
}
Enter fullscreen mode Exit fullscreen mode

The Solution: Strict Mode Separation

Instead of trying to run everything at once, I separated the device into distinct modes:

TOTP Mode:

  • WiFi ON for NTP sync
  • Get accurate time
  • WiFi OFF immediately after sync
  • Use millis() for countdown between syncs

Password Manager Mode:

  • WiFi completely disabled
  • Only BLE active
  • Pure offline operation

BLE Transmission:

  • WiFi always OFF before connecting
  • BLE connects, types password, disconnects
  • No coexistence needed
void safe_approach() {
    if (mode == TOTP_MODE) {
        sync_time_via_ntp();
        WiFi.disconnect(true); // Force disconnect
        WiFi.mode(WIFI_OFF);
    }

    if (need_ble_transmission) {
        ensure_wifi_off(); // Double check
        init_ble_keyboard();
        send_password();
        deinit_ble_keyboard();
    }
}
Enter fullscreen mode Exit fullscreen mode

Monitoring Heap Memory

I added aggressive heap monitoring to catch memory leaks early:

void check_heap() {
    Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
    Serial.printf("Min free heap: %d bytes\n", ESP.getMinFreeHeap());

    if (ESP.getFreeHeap() < 20000) {
        // Critical! Take action
        emergency_cleanup();
    }
}
Enter fullscreen mode Exit fullscreen mode

The getMinFreeHeap() function was crucial - it shows the lowest point your heap reached, helping you spot fragmentation issues.

Other Memory Optimization Tricks

1. Stack Size Matters

BLE callbacks run in their own task. Default stack size wasn't enough:

// Increase BLE task stack
xTaskCreatePinnedToCore(
    ble_task,
    "BLE",
    8192,  // Increased from default 4096
    NULL,
    5,
    NULL,
    0
);
Enter fullscreen mode Exit fullscreen mode

2. String Handling

Avoid String class on ESP32 - it fragments heap. Use char[] arrays:

// Bad (heap fragmentation):
String message = "Hello " + username + "!";

// Good (stack allocation):
char message[64];
snprintf(message, sizeof(message), "Hello %s!", username);
Enter fullscreen mode Exit fullscreen mode

3. WiFi Events Cleanup

WiFi events can leak memory if not handled properly:

WiFi.disconnect(true);  // true = erase credentials
WiFi.mode(WIFI_OFF);
delay(100);  // Let it settle
esp_wifi_stop();
esp_wifi_deinit();
Enter fullscreen mode Exit fullscreen mode

The Result

After these optimizations:

  • Stable operation for days without reboot
  • Min free heap stays above 50KB (safe zone)
  • No more random crashes
  • Clean mode transitions

Lessons Learned

  1. ESP32 is powerful, but not unlimited - you're still working with embedded constraints
  2. Measure everything - heap monitoring saved me countless hours
  3. Mode separation works - don't try to do everything simultaneously
  4. WiFi/BLE coexistence is hard - avoid it if you can

The Project

The full source code is available on GitHub: SecureGen

Features:

  • TOTP authenticator (RFC 6238)
  • Encrypted password manager (AES-256)
  • BLE HID keyboard
  • LE Secure Connections with MITM protection
  • Web management interface
  • Battery powered

If you're building anything with ESP32 + BLE + WiFi, I hope this saves you some debugging time!

What's Next?

In the next post, I'll cover BLE Security implementation - specifically LE Secure Connections, Numeric Comparison pairing, and why iOS is way more strict than Android.


Questions? Comments? Drop them below! I'm still learning and would love to hear how others solved similar problems.

esp32 #iot #embedded #security #bluetooth #opensource

Top comments (0)