<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: makepkg</title>
    <description>The latest articles on DEV Community by makepkg (@makepkg).</description>
    <link>https://dev.to/makepkg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3767013%2F1ac51546-e9f7-45ac-925a-4169572042c0.jpeg</url>
      <title>DEV Community: makepkg</title>
      <link>https://dev.to/makepkg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/makepkg"/>
    <language>en</language>
    <item>
      <title>Building a 60 FPS Audio Visualizer and Web UI on a Single-Core RISC-V MCU (ESP32-C3 Architecture Deep Dive)</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Tue, 09 Jun 2026 10:50:09 +0000</pubDate>
      <link>https://dev.to/makepkg/building-a-60-fps-audio-visualizer-and-web-ui-on-a-single-core-risc-v-mcu-esp32-c3-architecture-2495</link>
      <guid>https://dev.to/makepkg/building-a-60-fps-audio-visualizer-and-web-ui-on-a-single-core-risc-v-mcu-esp32-c3-architecture-2495</guid>
      <description>&lt;p&gt;Every developer loves pushing hardware to its absolute limits. Recently, I set out to build an advanced &lt;strong&gt;Internet Radio (v4.6)&lt;/strong&gt; around the &lt;strong&gt;ESP32-C3&lt;/strong&gt; — a cost-effective, single-core 32-bit RISC-V microcontroller running at 160MHz with ~400KB of usable RAM.&lt;/p&gt;

&lt;p&gt;The challenge? Stream high-bitrate MP3 audio over WiFi, decode it on the fly, output it via I2S, drive an asynchronous HTTP Web Server, handle hardware interrupts, and render a smooth &lt;strong&gt;60 FPS audio spectrum visualizer&lt;/strong&gt; with 9 complex mathematics-driven animation modes on a monochrome OLED display. All of this on a &lt;em&gt;single core&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here is a deep dive into the architectural decisions, FreeRTOS task scheduling, and rendering pipelines that made it possible.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2aq4xoq6bpwprk0eoc0k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2aq4xoq6bpwprk0eoc0k.jpg" alt=" " width="800" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 1. FreeRTOS Task Architecture &amp;amp; Prioritization
&lt;/h2&gt;

&lt;p&gt;When dealing with a single-core MCU, true concurrency doesn't exist; we rely entirely on deterministic time-slicing via FreeRTOS. A single bottleneck in the display rendering or network stack will immediately cause audio stuttering (buffer underrun).&lt;/p&gt;

&lt;p&gt;To solve this, the processing chain is decoupled into independent tasks separated by thread-safe queues and strict priority constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Highest Priority ───&amp;gt;  [ Audio Decoder Task ]  Priority 4 (Time-critical)
                             │
                             ▼  (128KB Ring Buffer)
                       [ Network / WiFi Stack ] Priority 3 (Burst-driven)
                             │
                             ▼
                       [ Hardware Input ISR ]   Priority 2 (Interrupt debounced)
                             │
                             ▼
Lowest Priority  ───&amp;gt;  [ OLED Render &amp;amp; Web ]    Priority 1 (Frame-skipping allowed)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv136hx7joktr1wv66dag.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv136hx7joktr1wv66dag.gif" alt=" " width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Audio Priority Safeguard
&lt;/h3&gt;

&lt;p&gt;The MP3 decoding task takes absolute precedence. If the network drops packets or the web server hits a heavy asset request, FreeRTOS preempts those operations to feed the &lt;strong&gt;128KB audio ring buffer&lt;/strong&gt;. We enforce a &lt;strong&gt;2-second prebuffering threshold&lt;/strong&gt; before initializing the I2S DMA transmission.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 2. The 60 FPS Visualizer Engine: Pushing I2C to the Limit
&lt;/h2&gt;

&lt;p&gt;The standout feature of this system is the 9-mode real-time visualization matrix (including Cyberpunk Hexagons, 4D Tesseract projections, and Liquid Plasma).&lt;/p&gt;

&lt;p&gt;Getting 60 frames per second on an SSD1306 OLED via I2C while decoding audio is historically a bottleneck. Here is how it was bypassed:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. I2C Overclocking &amp;amp; Frame Skipping
&lt;/h3&gt;

&lt;p&gt;Standard I2C runs at 100kHz. We push the ESP32-C3 I2C hardware controller to &lt;strong&gt;400kHz (Fast Mode)&lt;/strong&gt;. To prevent the rendering loop from blocking the main loop during peak CPU cycles, an atomic frame-skip mechanism is implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode snippet of the priority-aware loop&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;visualizerTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pvParameters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;TickType_t&lt;/span&gt; &lt;span class="n"&gt;lastWakeTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xTaskGetTickCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audioDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDecoding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cpu_heavy_flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Drop frame to preserve audio integrity&lt;/span&gt;
            &lt;span class="n"&gt;vTaskDelayUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lastWakeTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdMS_TO_TICKS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Drop to 30 FPS&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;renderVisualizerStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_style&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Flushes 1024 bytes buffer over 400kHz I2C&lt;/span&gt;
        &lt;span class="n"&gt;vTaskDelayUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lastWakeTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdMS_TO_TICKS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Target 60 FPS&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Fast Fourier Transform (FFT) Allocation
&lt;/h3&gt;

&lt;p&gt;The 16-band spectrum analyzer reads the raw PCM data immediately post-decoding. To prevent heap fragmentation, the arrays for the visualization algorithms are allocated statically with &lt;code&gt;IRAM_ATTR&lt;/code&gt; attributes, ensuring the RISC-V core can perform fast data manipulation inside cache-backed internal memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 3. Asynchronous Web UI Architecture (LittleFS)
&lt;/h2&gt;

&lt;p&gt;The device hosts a fully responsive station manager, real-time logging, and system diagnostics page. Running a traditional synchronous web server would block the CPU for milliseconds during file reads, instantly killing the audio stream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Async Web Server + Custom Web API
&lt;/h3&gt;

&lt;p&gt;We utilized &lt;code&gt;ESPAsyncWebServer&lt;/code&gt; combined with a &lt;strong&gt;LittleFS&lt;/strong&gt; filesystem partition (1.4MB).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Blocking Architecture:&lt;/strong&gt; The async server processes HTTP requests in sockets via background LwIP stack events. When a user changes a station or slider, it transmits minimal JSON payloads via a RESTful API rather than reloading heavy HTML pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flash Protection Layer:&lt;/strong&gt; Changing volume or updating configuration files triggers an flash-write operation. To prevent early flash degradation and sudden performance drops, we implemented a &lt;strong&gt;5-second write-debounce cache&lt;/strong&gt;. If a user spams the volume encoder, the state is cached in RAM and only committed to &lt;code&gt;state.json&lt;/code&gt; inside LittleFS once the system is idle for 5 seconds.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivzcifskh211z7xss1i1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivzcifskh211z7xss1i1.png" alt=" " width="757" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔋 4. Low-Power Design: Deep Sleep Optimization
&lt;/h2&gt;

&lt;p&gt;For a desktop device, standby efficiency matters. Long-pressing the hardware encoder puts the system into an ultra-low-power &lt;strong&gt;Deep Sleep mode&lt;/strong&gt;, dropping power consumption to a microscopic &lt;strong&gt;~10µA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before entering sleep, the system executes an atomic exit routine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gracesfully halts the I2S DMA engine (preventing speaker "pop" noises).&lt;/li&gt;
&lt;li&gt;Flushes all volatile system metrics and rolling logs to LittleFS.&lt;/li&gt;
&lt;li&gt;Attaches an external wakeup interrupt to the encoder button pin (&lt;code&gt;GPIO2&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Commands the SSD1306 display controller to enter charge-pump shutdown mode via I2C.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🛠️ Memory Mapping &amp;amp; Footprint Breakdown
&lt;/h2&gt;

&lt;p&gt;Optimizing the 4MB external Flash memory allocation partition was crucial to balancing features and files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Partition&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App Partition (OTA)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;Compiled C++ Binary (FreeRTOS, Audio/Graphics engines)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LittleFS Filesystem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.4 MB&lt;/td&gt;
&lt;td&gt;Static Web UI assets, logs, station JSON configs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System NVS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100 KB&lt;/td&gt;
&lt;td&gt;Non-volatile storage for runtime variables, WiFi calibration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;By carefully managing task scheduling, optimizing peripheral busses (I2C/I2S), and shifting to non-blocking asynchronous software paradigms, it's completely viable to turn a budget $3 single-core chip like the ESP32-C3 into a high-performance multimedia device.&lt;/p&gt;

&lt;p&gt;The entire source code, along with wiring schemas, PlatformIO configuration profiles, and assembly bills of material, is fully open-source.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Check out the complete repository here:&lt;/strong&gt; &lt;a href="https://github.com/makepkg/ESP32-C3-Internet-Radio" rel="noopener noreferrer"&gt;https://github.com/makepkg/ESP32-C3-Internet-Radio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you worked with audio streaming or complex UI rendering on single-core microcontrollers? Let's discuss task scheduling and optimization tricks in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>embedded</category>
      <category>cpp</category>
      <category>radio</category>
    </item>
    <item>
      <title>SecureGen v2.2 + v2.3: Upgrading to ESP32-S3, Native USB HID, and Implementing Duress Decoy Vaults</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Mon, 08 Jun 2026 14:28:44 +0000</pubDate>
      <link>https://dev.to/makepkg/securegen-v22-v23-upgrading-to-esp32-s3-native-usb-hid-and-implementing-duress-decoy-vaults-4bfg</link>
      <guid>https://dev.to/makepkg/securegen-v22-v23-upgrading-to-esp32-s3-native-usb-hid-and-implementing-duress-decoy-vaults-4bfg</guid>
      <description>&lt;h2&gt;
  
  
  🚨 SecureGen v2.2 + v2.3: Upgrading to ESP32-S3, Native USB HID, and Implementing Duress Decoy Vaults
&lt;/h2&gt;

&lt;p&gt;A few months ago, I shared the security architecture of SecureGen—an open-source, air-gapped hardware password manager and TOTP authenticator built with application-layer AES-256-GCM encryption.&lt;/p&gt;

&lt;p&gt;Since then, the project has gone through two massive iterations (v2.2.0 and v2.3.0). We officially migrated the architecture to support the ESP32-S3, implemented native USB HID execution, and added a physical threat-model feature straight out of a spy movie: Duress PINs with instant memory zeroing and decoy vaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkj29abje7en9jw9s2ssr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkj29abje7en9jw9s2ssr.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a breakdown of what we built over the last two months and the engineering challenges behind them.&lt;br&gt;
🛠️ The Hardware Evolution: Moving to ESP32-S3 &amp;amp; Native USB-OTG&lt;/p&gt;

&lt;p&gt;While the original ESP32 was great, it lacked native USB capabilities, forcing us to rely purely on Bluetooth (BLE) for keyboard emulation.&lt;/p&gt;

&lt;p&gt;In v2.2.0, we introduced full support for the LILYGO T-Display-S3 board. This upgrade fundamentally changed the device’s capabilities:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8MB PSRAM &amp;amp; 1.9" Parallel Display (170×320): Massive UI upgrade and way more breathing room for handling concurrent encrypted web server sessions.

Hardware Accelerated AES: Cryptographic operations are now significantly faster compared to the legacy ESP32 chip.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi5ej3hwqm6j65w4922s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgi5ej3hwqm6j65w4922s.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Native USB HID Keyboard: Thanks to the S3's native USB-OTG, you can now plug the device directly into a server or PC and inject credentials over a hardware line—completely bypassing BLE pairing. You can switch this mode on or off via the local web cabinet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🔒 The Duress PIN: Memory Zeroing &amp;amp; Decoy Vaults&lt;/p&gt;

&lt;p&gt;In v2.3.0, we addressed physical coercion threat models. What happens if someone physically forces you to enter your PIN to unlock your hardware vault?&lt;/p&gt;

&lt;p&gt;We implemented a Multi-Layer Duress PIN system.&lt;/p&gt;

&lt;p&gt;[User Enters PIN]&lt;br&gt;
       │&lt;br&gt;
       ├──► Correct PIN ──► Decrypts Master Key ──► Loads Real Vault&lt;br&gt;
       │&lt;br&gt;
       └──► Duress PIN  ──► Triggers Advanced Memory Zeroing &lt;br&gt;
                                     │&lt;br&gt;
                                     ├──► Wipes RAM &amp;amp; Crypto Keys&lt;br&gt;
                                     └──► Swaps real LittleFS storage with Decoy Accounts&lt;/p&gt;

&lt;p&gt;When the Duress PIN is entered:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The device detects the duress flag before parsing the actual storage.

It triggers an advanced memory zeroing cycle, scrubbing all active session keys and real TOTP secrets from RAM.

The real vault file handles are isolated, and the firmware instantly swaps the interface with a completely valid, pre-configured Decoy Vault filled with fake accounts.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To an attacker, the device looks completely unlocked, but your real cryptographic keys are completely gone from volatile memory.&lt;br&gt;
🛡️ Cryptographic Polish &amp;amp; "Anti-Stupid" Verification&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzrjcvi1g9p129nhvgf4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzrjcvi1g9p129nhvgf4.png" alt=" " width="496" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also closed several logical security loopholes in the v2.3.0 release:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Credential Validation on Registration: The firmware now enforces strict checks preventing users from including their login name inside their master passwords.

Convincing Mimicry Headers: The obfuscation layer on the AsyncWebServer was upgraded. It now generates highly convincing decoy Authorization and Session headers to mask internal API endpoints, confusing anyone sniffing local Access Point (AP) mode traffic.

Persistent AP Lifecycles: Fixed a critical session lifecycle bug in AP mode where sessions could theoretically persist across soft AP restarts. Now, restarting AP mode strictly invalidates and flushes the token table.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🐛 Squashing the Brutal Bugs&lt;/p&gt;

&lt;p&gt;If you have ever developed an asynchronous web server on the ESP32 under memory pressure, you know it can be a nightmare. We squashed two major stability bugs:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AsyncWebServer Double-Send Crashes: Fixed a race condition in the HTTP request body handlers where a client disconnect during large chunked transfers triggered a double-send response, causing a hard panic.

The BLE Zero-Padding Bug: When broadcasting the random BLE PIN, the display logic was dropping leading zeros, showing 6 instead of 000006. This has been fixed to ensure strict zero-padded strings.

UI Element ID Drag-and-Drop Desync: Moving rows via drag-and-drop physically moved the DOM elements, but their bound timer IDs (timer-0, progress-0) stayed in place. We rewrote the post-drop handler to call updateKeysTable() and cleanly rebuild the table schema dynamically.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;📈 Roadmap &amp;amp; Documentation&lt;/p&gt;

&lt;p&gt;To support this new multi-board ecosystem, we released two massive community guides:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RTC Mastering Guide: Complete wiring layouts for connecting a external high-accuracy DS3231 hardware clock via I2C to handle air-gapped Offline TOTP generation without NTP sync.

Porting Guide: Step-by-step documentation for custom platformio.ini setups, defining custom deep sleep wake pins, and tweaking display geometry for any third-party ESP32/S3 developer board.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;🔗 Explore the Project&lt;/p&gt;

&lt;p&gt;The project remains 100% open-source, trustless, and cloud-free.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 GitHub Repository: https://github.com/makepkg/SecureGen

💻 Flash directly from your browser (Chrome/Edge): https://makepkg.github.io/SecureGen/flash

🛠️ Hackster Project: https://www.hackster.io/makepkg/securegen-open-source-totp-authenticator-password-manager-c350d6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Have you ever built a decoy system or worked with the ESP32-S3's native USB HID? Let's talk about firmware security and physical threat models in the comments below!&lt;/p&gt;

</description>
      <category>password</category>
      <category>authenticator</category>
      <category>securegen</category>
      <category>lilygo</category>
    </item>
    <item>
      <title>Ultimate Upgrade of old HP THIN Clinet T630 with Pico Commander</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Mon, 08 Jun 2026 14:21:43 +0000</pubDate>
      <link>https://dev.to/makepkg/ultimate-upgrade-of-old-hp-thin-clinet-t630-with-pico-commander-1eh0</link>
      <guid>https://dev.to/makepkg/ultimate-upgrade-of-old-hp-thin-clinet-t630-with-pico-commander-1eh0</guid>
      <description>&lt;p&gt;🚀 Turn Any Cheap Thin Client (like HP T630) into an Advanced Headless Server with a Modular Hardware UI&lt;/p&gt;

&lt;p&gt;If you love the r/homelab ethos, you probably know that old enterprise thin clients—like the HP Thin Client T630, Dell Wyse, or Lenovo ThinkCentre Tiny—make incredible, low-power, budget-friendly headless servers. They run Docker, home automation, and lightweight media servers perfectly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lh6of0yc1i5wey9sd70.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lh6of0yc1i5wey9sd70.jpg" alt=" " width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But they have one massive downside compared to enterprise servers: No physical management interface. There is no iLO, no IPMI, and no screen. If your network drops, a Docker container hangs, or you need a safe emergency shutdown, your only choices are blind power-cycling or dragging out a monitor and keyboard.&lt;/p&gt;

&lt;p&gt;To fix this, I built Pico Commander—not just as a macro pad, but as a flexible, modular hardware firmware kit designed to embed right into your micro-PCs or custom monolith chassis to act as an independent physical system manager.&lt;br&gt;
🛠 What is Pico Commander?&lt;/p&gt;

&lt;p&gt;At its core, Pico Commander is an open-source subsystem running on a Raspberry Pi Pico (RP2040) and driven by CircuitPython. It communicates with your host system via native USB HID emulation (acting as a driverless keyboard).&lt;/p&gt;

&lt;p&gt;Because it’s a modular kit, you can easily mount the components inside an empty drive bay, cut holes into a thin client's plastic shell, or build a monolithic custom chassis.&lt;br&gt;
The Hardware Stack:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Brain: Raspberry Pi Pico (RP2040)

The Display: SSD1306 OLED (128x32 or 128x64) for multi-level hierarchical menu navigation.

The Input: KY-040 Rotary Encoder (scroll to browse, click to execute, long-press to go back).

The Secret Weapon: Twin Hall Effect Sensors acting as air-gapped, magnetic hardware panic buttons.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ou0638e7wkkb14uka61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ou0638e7wkkb14uka61.png" alt=" " width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧠 The Magic: Visual Automation Pipelines, No Hardcoding&lt;/p&gt;

&lt;p&gt;Most DIY hardware controllers require you to rewrite and re-flash C++ or Python code every time you want to change a button's function. That is highly inefficient for server management.&lt;/p&gt;

&lt;p&gt;Pico Commander solves this with a decoupled architecture. The firmware reads its entire behavior from a single config.json file. To make managing this file effortless, the repository includes an offline browser-based visual HTML configuration tool (editor.html).&lt;/p&gt;

&lt;p&gt;Instead of simple key combinations, you can visually drag-and-drop multi-step automation pipelines:&lt;br&gt;
JSON&lt;/p&gt;

&lt;p&gt;"scenarios": {&lt;br&gt;
  "docker_container_cycle": [&lt;br&gt;
    {"action": "type", "value": "docker-compose down"},&lt;br&gt;
    {"action": "key", "combo": "enter"},&lt;br&gt;
    {"action": "wait", "ms": 2500},&lt;br&gt;
    {"action": "type", "value": "docker-compose up -d"},&lt;br&gt;
    {"action": "key", "combo": "enter"}&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;You map these scenarios to submenus (e.g., Apps -&amp;gt; Nginx -&amp;gt; Restart), save the file, drop it onto the Pico's flash drive, and the system dynamically updates.&lt;br&gt;
🚨 The Air-Gapped Hardware Panic Trigger&lt;/p&gt;

&lt;p&gt;One of the coolest ways this modular kit upgrades a machine like the HP T630 is through Hall effect sensors.&lt;/p&gt;

&lt;p&gt;Imagine your server handles sensitive data or acts as a local security node. By hiding a tiny Hall sensor inside the chassis casing, you create a completely invisible, physical override.&lt;/p&gt;

&lt;p&gt;When you swipe a magnet near that specific spot on the case, Pico Commander triggers a high-priority emergency script—bypassing standard menu navigation and cooldown protections—to instantly trigger a safe system shutdown, isolate network interfaces, or run a data wipe pipeline.&lt;br&gt;
💻 Project Architecture: Build It Your Way&lt;/p&gt;

&lt;p&gt;The project is designed to be highly extensible. If you want to integrate it into your own system manager monolith, the Python backend is neatly split into functional modules:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;boot.py — Configures USB HID modes and read/write states.

trigger_bus.py — The core engine execution pipeline that handles delays and script sequences.

display.py &amp;amp; screensaver.py — Manages UI layouts, menu scroll animations, and OLED auto-sleep rendering (Matrix, Starfield effects).

passive.py — Monitors background hardware states (Hall sensors, physical boot buttons).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Because it uses generic I2C and GPIO definitions, you can adapt the pins in config.json to match whatever space limitations you have inside your server case.&lt;br&gt;
🚀 Get Started (Open Source)&lt;/p&gt;

&lt;p&gt;The project is completely free, open-source, and ready for you to fork. If you have a few spare parts lying around on your workbench, you can have a working prototype wired up on a breadboard in under 15 minutes.&lt;/p&gt;

&lt;p&gt;👉 Check out the full source code, wiring schematics, and the offline web tool on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/makepkg/pico-admin-hid" rel="noopener noreferrer"&gt;https://github.com/makepkg/pico-admin-hid&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How would you integrate a physical UI into your headless homelab? Let me know your ideas for custom hardware triggers in the comments below!&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>homelab</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>SecureGen v2.0 + v2.1 — What We Built Over the Last Few Months</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Sat, 21 Mar 2026 17:25:20 +0000</pubDate>
      <link>https://dev.to/makepkg/securegen-v20-v21-what-we-built-over-the-last-few-months-2m7k</link>
      <guid>https://dev.to/makepkg/securegen-v20-v21-what-we-built-over-the-last-few-months-2m7k</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Two major releases dropped since the last post. Here's what changed and why it was technically interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  v2.0.0 — The Security Rewrite
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AES-256-GCM Transport Encryption
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  PIN-Encrypted Device Key
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistent PIN Lockout
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure Memory Wipe Before Deep Sleep
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg7wf2a6023ffyu71f6z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg7wf2a6023ffyu71f6z.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  HOTP Support
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5csbxsoumvdfykkqwaae.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5csbxsoumvdfykkqwaae.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  QR Code Import / Export
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;decrypt_export.html&lt;/code&gt; tool lets you decrypt, inspect, and edit key files without the device connected.&lt;/p&gt;




&lt;h2&gt;
  
  
  v2.1.0 — Stability, Hardware, and UX
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2xdnvnlrxm7ea01ylch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2xdnvnlrxm7ea01ylch.png" alt=" " width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hes8z5hvvhzlkxjiwqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hes8z5hvvhzlkxjiwqp.png" alt=" " width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  DS3231 RTC Module Support
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Light Sleep Crash Fix
&lt;/h3&gt;

&lt;p&gt;This one was subtle. On battery power, &lt;code&gt;esp_light_sleep_start()&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Boot Mode System
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;"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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation Guard
&lt;/h3&gt;

&lt;p&gt;The web cabinet fires ~4 encrypted requests during initialization (keys, PIN settings, theme, battery). Switching tabs before these complete broke the UI state.&lt;/p&gt;

&lt;p&gt;Fix: async IIFE runs all init requests sequentially, sets &lt;code&gt;appInitialized = true&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy52p5nmrzl5e3sh64n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy52p5nmrzl5e3sh64n1.png" alt=" " width="800" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Multilingual Interfac
&lt;/h3&gt;

&lt;p&gt;Web cabinet now supports English, Russian, German, Chinese (Simplified), and Spanish. Language selection persists via localStorage. All UI strings go through &lt;code&gt;tr()&lt;/code&gt; — adding a new language is one object in the i18n map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Fixes Worth Noting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;HOTP display corruption.&lt;/strong&gt; When switching from a TOTP key to an HOTP key, the old progress bar rendered on top of "HOTP - Static Code" text. Root cause: &lt;code&gt;drawLayout()&lt;/code&gt; set &lt;code&gt;lastTimeRemaining = -999&lt;/code&gt; as a sentinel, but &lt;code&gt;updateTOTPCode(-1)&lt;/code&gt; saw &lt;code&gt;-1 != -999&lt;/code&gt; as true only once — after that &lt;code&gt;-1 == -1&lt;/code&gt; skipped the redraw. Fix: &lt;code&gt;eraseLoaderArea()&lt;/code&gt; also resets &lt;code&gt;lastTimeRemaining = -999&lt;/code&gt;, forcing a full redraw after the loader completes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HOTP notification showed "TOTP code copied".&lt;/strong&gt; The copy handler checked &lt;code&gt;keysData[index].type === 'hotp'&lt;/code&gt; — but the actual field is &lt;code&gt;keysData[index].t === 'H'&lt;/code&gt;. One character difference, wrong notification for every HOTP copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password reorder endpoint.&lt;/strong&gt; The &lt;code&gt;/api/passwords/reorder&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drag-and-drop key reorder mixed up timer/progress elements.&lt;/strong&gt; DOM rows were physically moved but element IDs (&lt;code&gt;timer-0&lt;/code&gt;, &lt;code&gt;progress-0&lt;/code&gt;) stayed bound to old positions. Fix: call &lt;code&gt;updateKeysTable(keysData)&lt;/code&gt; after every drag-drop to rebuild the table with correct IDs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ECDH P-256 → X25519 migration (~400ms → ~80ms key exchange)&lt;/li&gt;
&lt;li&gt;Port to T-Display S3 (PSRAM, USB HID, larger screen)&lt;/li&gt;
&lt;li&gt;Flash encryption and secure boot via sdkconfig&lt;/li&gt;
&lt;li&gt;ATECC608 secure element support&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Flash from browser (Chrome/Edge):&lt;/strong&gt; &lt;a href="https://makepkg.github.io/SecureGen/flash" rel="noopener noreferrer"&gt;https://makepkg.github.io/SecureGen/flash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/makepkg/SecureGen" rel="noopener noreferrer"&gt;https://github.com/makepkg/SecureGen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hackster.io:&lt;/strong&gt; &lt;a href="https://www.hackster.io/makepkg/securegen-open-source-totp-authenticator-password-manager-c350d6" rel="noopener noreferrer"&gt;https://www.hackster.io/makepkg/securegen-open-source-totp-authenticator-password-manager-c350d6&lt;/a&gt;&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>security</category>
      <category>opensource</category>
      <category>iot</category>
    </item>
    <item>
      <title>Building an 8-Layer Security Architecture for a $15 Hardware Device</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Sun, 15 Feb 2026 20:29:05 +0000</pubDate>
      <link>https://dev.to/makepkg/building-an-8-layer-security-architecture-for-a-15-hardware-device-3p8k</link>
      <guid>https://dev.to/makepkg/building-an-8-layer-security-architecture-for-a-15-hardware-device-3p8k</guid>
      <description>&lt;h1&gt;
  
  
  Building an 8-Layer Security Architecture for a $15 Hardware Device
&lt;/h1&gt;

&lt;p&gt;When you're building a hardware password manager, security isn't optional—it's the entire point.&lt;/p&gt;

&lt;p&gt;But how do you secure a $15 ESP32 device that stores sensitive data? The answer: &lt;strong&gt;defense in depth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll break down the 8-layer security architecture I built for &lt;a href="https://github.com/makepkg/SecureGen" rel="noopener noreferrer"&gt;SecureGen&lt;/a&gt;, a hardware TOTP authenticator and password manager. Each layer defends against different attack types, ensuring that breaching one layer doesn't compromise the system.&lt;/p&gt;

&lt;p&gt;![8 Security Layers Architecture](&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgh16v08hkjvovb3d3iv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgh16v08hkjvovb3d3iv.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why 8 Layers?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single-layer security is a single point of failure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your only protection is encryption and the key gets leaked, game over. If your only protection is authentication and credentials get phished, you're done.&lt;/p&gt;

&lt;p&gt;Defense in depth means attackers need to bypass &lt;strong&gt;multiple independent layers&lt;/strong&gt;. Each layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Targets different attack vectors&lt;/li&gt;
&lt;li&gt;Uses different techniques&lt;/li&gt;
&lt;li&gt;Fails independently&lt;/li&gt;
&lt;li&gt;Slows down attackers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down each layer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Layer 1: ECDH Key Exchange 🔐
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Passwords and TOTP secrets need to be encrypted. But if you hardcode encryption keys, anyone with the source code can decrypt your data.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Generate unique encryption keys dynamically using &lt;strong&gt;Elliptic Curve Diffie-Hellman (ECDH)&lt;/strong&gt; with the P-256 curve.&lt;/p&gt;
&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate ephemeral key pair&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_ecdh_context&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_ecdh_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MBEDTLS_ECP_DP_SECP256R1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// P-256&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_ecdh_gen_public&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;olen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                        &lt;span class="n"&gt;mbedtls_ctr_drbg_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctr_drbg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Compute shared secret&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_ecdh_calc_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;olen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;mbedtls_ctr_drbg_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ctr_drbg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Derive encryption key&lt;/span&gt;
&lt;span class="n"&gt;mbedtls_sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encryption_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Man-in-the-middle (MITM) attacks&lt;/p&gt;

&lt;p&gt;Even if someone intercepts the traffic during key exchange, they can't compute the shared secret without the private keys. And since keys are ephemeral (generated per session), they can't be reused.&lt;/p&gt;
&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before ECDH:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Attacker intercepts traffic]
→ Attacker sees: Encrypted data with static key
→ Attacker bruteforces key offline
→ Game over
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After ECDH:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Attacker intercepts traffic]
→ Attacker sees: Public keys (useless without private)
→ Attacker can't compute shared secret
→ Encrypted data remains secure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 2: Session Encryption 🔒
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Bluetooth (BLE) has built-in encryption, but it's not enough for sensitive data like passwords.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Double encryption&lt;/strong&gt;: AES-128 at the BLE layer + AES-256 at the application layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BLE layer (automatic)&lt;/span&gt;
&lt;span class="n"&gt;esp_ble_gap_set_security_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP_BLE_SM_AUTHEN_REQ_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;auth_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;auth_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_LE_AUTH_REQ_SC_MITM_BOND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// AES-128&lt;/span&gt;

&lt;span class="c1"&gt;// Application layer (manual)&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;encrypt_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mbedtls_aes_context&lt;/span&gt; &lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;mbedtls_aes_setkey_enc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// AES-256&lt;/span&gt;

    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;esp_fill_random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Random IV per encryption&lt;/span&gt;

    &lt;span class="n"&gt;mbedtls_aes_crypt_cbc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MBEDTLS_AES_ENCRYPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                          &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Eavesdropping and packet sniffing&lt;/p&gt;

&lt;p&gt;Even if an attacker breaks BLE encryption (unlikely but possible), they still face AES-256 application-layer encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Single encryption:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[BLE vulnerability discovered]
→ Attacker breaks BLE encryption
→ Passwords visible in plaintext
→ Total compromise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Double encryption:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[BLE vulnerability discovered]
→ Attacker breaks BLE encryption
→ Attacker sees: More encrypted data (AES-256)
→ Passwords still secure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 3: Dynamic API Endpoints 🎲
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Static API endpoints are easy targets for automated scanners. Tools like &lt;code&gt;dirb&lt;/code&gt;, &lt;code&gt;gobuster&lt;/code&gt;, and &lt;code&gt;nikto&lt;/code&gt; can discover them in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Obfuscate endpoint URLs using SHA-256 and change them on every device boot.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate dynamic endpoint&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;generate_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;hash_input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;snprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash_input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"%s-%u"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;mbedtls_sha256&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;hash_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash_input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"%02x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Real endpoint on device&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;boot_seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esp_random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;totp_endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"totp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;boot_seed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Result: /a7f3c9e8b2d4f1a6c8e5d7f9b3a1c5e8d2f4a6b8c0e2d4f6a8b0c2e4f6a8b0c2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Automated API discovery and vulnerability scanning&lt;/p&gt;

&lt;p&gt;Scanners expect endpoints like &lt;code&gt;/api/totp&lt;/code&gt; or &lt;code&gt;/passwords&lt;/code&gt;. When they get &lt;code&gt;/a7f3c9e8b2d4f1a6...&lt;/code&gt;, they don't know what it does without manual analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (static endpoints):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gobuster &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; http://device.local &lt;span class="nt"&gt;-w&lt;/span&gt; wordlist.txt
Found: /api/totp
Found: /api/passwords
Found: /api/admin

&lt;span class="o"&gt;[&lt;/span&gt;Automated exploit tools target these]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (dynamic endpoints):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gobuster &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; http://device.local &lt;span class="nt"&gt;-w&lt;/span&gt; wordlist.txt
No matches found

&lt;span class="o"&gt;[&lt;/span&gt;Attacker needs to reverse-engineer endpoint generation]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 4: Header Obfuscation 🎭
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;HTTP headers reveal your tech stack. Headers like &lt;code&gt;Server: nginx/1.18.0&lt;/code&gt; or &lt;code&gt;X-Powered-By: Express 4.17.1&lt;/code&gt; tell attackers exactly which vulnerabilities to target.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Dynamically map and obfuscate all HTTP headers. Hide metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Header mapping (changes per session)&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;init_header_obfuscation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;send_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AsyncWebServerResponse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;beginResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove revealing headers&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header_map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"Unknown"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add fake misleading headers&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-AspNetMvc-Version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"5.2.7"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Not using ASP.NET&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Powered-By"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PHP/7.4.3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// Not using PHP&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Tech stack fingerprinting and targeted exploits&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ESP-AsyncWebServer/1.2.3&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;X-ESP32-Chip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ESP32-D0WDQ6&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;Attacker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;knows:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ESP&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;library&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;Searches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;CVEs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;those&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;X-k8f3a2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;X-9e4c7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unknown&lt;/span&gt;
&lt;span class="na"&gt;X-AspNetMvc-Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5.2.7&lt;/span&gt;
&lt;span class="na"&gt;X-Powered-By&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PHP/7.4.3&lt;/span&gt;

[Attacker sees: Conflicting information]
[Wastes time targeting wrong stack]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 5: Anti-Fingerprinting 👻
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Even without obvious headers, devices can be fingerprinted by response patterns, timing, and behaviors.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Inject fake headers, randomize response patterns, and make the device appear different on each request.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;add_fingerprint_confusion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerResponse&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Random fake headers&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fake_frameworks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Django/3.2.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Rails/6.1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Laravel/8.40.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="s"&gt;"Express/4.17.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Spring/5.3.8"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Framework"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;fake_frameworks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;esp_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Randomize response time (within reason)&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 10-60ms&lt;/span&gt;

    &lt;span class="c1"&gt;// Vary content encoding&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Encoding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Change protocol hints&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Vary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Accept-Encoding, User-Agent, Accept-Language"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Device fingerprinting and traffic analysis&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1: Response in 45ms, Server: ESP32
Request 2: Response in 44ms, Server: ESP32
Request 3: Response in 46ms, Server: ESP32

[Pattern: ESP32 device, consistent timing]
[Attacker: "I know what this is"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1: Response in 23ms, Framework: Django
Request 2: Response in 51ms, Framework: Rails  
Request 3: Response in 18ms, Framework: Laravel

[Pattern: Inconsistent, conflicting data]
[Attacker: "What the hell is this thing?"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 6: Honey Pot 🍯
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;You want to know if someone is trying to break in. Passive defenses are invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Create fake "vulnerable" endpoints that look tempting but are actually traps. Log all access attempts.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Honey pot endpoints&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;log_intrusion_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remoteIP&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
                          &lt;span class="s"&gt;"/admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Unauthorized access attempt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Return realistic fake data&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fake_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;R"({
        "users": [
            {"username": "admin", "role": "superuser"},
            {"username": "root", "role": "admin"}
        ],
        "version": "2.4.1",
        "debug": true
    })"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// Check for SQL injection attempts&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OR 1=1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log_intrusion_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remoteIP&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                              &lt;span class="s"&gt;"/api/users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SQL injection attempt detected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Empty response&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Path traversal trap&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/../../etc/passwd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;log_intrusion_attempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remoteIP&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                          &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Path traversal attempt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fake passwd file&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;fake_passwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"root:x:0:0:root:/root:/bin/bash&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                         &lt;span class="s"&gt;"admin:x:1000:1000:Admin:/home/admin:/bin/bash&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_passwd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Brute force attacks and automated exploitation&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Attacker scans device]
Attacker: "/admin endpoint found! Jackpot!"
[Accesses /admin]

[Device logs]
IP: 192.168.1.100
Endpoint: /admin
Time: 2026-02-15 14:23:11
User-Agent: python-requests/2.28.1
Action: Rate limited + alerted via web interface

[Attacker wastes time on fake data]
[You get early warning + attacker's IP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 7: Method Tunneling 🔀
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Many automated scanners look for specific HTTP methods (GET, POST, DELETE) on endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Tunnel all requests through POST with an encrypted method field. Mask the real HTTP method.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client-side (JavaScript)&lt;/span&gt;
&lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apiRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;encrypted_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;method:&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Always POST&lt;/span&gt;
        &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;encrypted_method&lt;/span&gt;  &lt;span class="c1"&gt;// Real method hidden here&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Server-side (ESP32)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="n"&gt;AsyncWebServerRequest&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;encrypted_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Method"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;real_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encrypted_method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handle_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handle_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;handle_put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Pattern-based automated attacks&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://device.local/api/passwords/1
Password deleted

&lt;span class="o"&gt;[&lt;/span&gt;Automated scanner detects DELETE is enabled]
&lt;span class="o"&gt;[&lt;/span&gt;Tries to delete everything]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://device.local/api/passwords/1
405 Method Not Allowed

&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://device.local/api/passwords/1
Missing X-Method header

&lt;span class="o"&gt;[&lt;/span&gt;Scanner confused: POST required but doesn&lt;span class="s1"&gt;'t work normally]
[Manual analysis needed]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Layer 8: Timing Attack Protection ⏱️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Attackers can infer information by measuring response times. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong password: 5ms response&lt;/li&gt;
&lt;li&gt;Correct password but wrong PIN: 50ms response&lt;/li&gt;
&lt;li&gt;Correct everything: 100ms response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leaks information without ever succeeding.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Add random delays to all security-critical operations. Make timing unpredictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;verify_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Start timer&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Actual verification&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;password_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constant_time_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored_password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;pin_valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constant_time_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored_pin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password_valid&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;pin_valid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculate elapsed time&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Target response time: 100-150ms&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add delay to reach target (if needed)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Constant-time string comparison (prevents timing leaks)&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;constant_time_compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;len_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Always compare full length (even if lengths differ)&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;max_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;len_a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;len_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;len_a&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;len_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;len_a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;len_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 0 if lengths match&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;char_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;char_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;char_a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;char_b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this prevents:&lt;/strong&gt; Timing side-channel attacks and password length inference&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (no timing protection):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Attacker script
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;passwords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ab&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abcd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pwd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;passwords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output:
# a: 0.005s      ← Wrong immediately
# ab: 0.005s     ← Wrong immediately  
# abc: 0.007s    ← Took slightly longer! (closer match)
# abcd: 0.005s   ← Back to fast
&lt;/span&gt;
&lt;span class="c1"&gt;# Conclusion: Real password starts with "abc"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (with timing protection):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Attacker script (same)
# Output:
# a: 0.123s
# ab: 0.147s
# abc: 0.108s
# abcd: 0.135s
&lt;/span&gt;
&lt;span class="c1"&gt;# Conclusion: ??? (no pattern)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's how all 8 layers work in a real scenario:&lt;/p&gt;

&lt;h3&gt;
  
  
  Attack Scenario: Automated Exploitation Attempt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Attacker scans device
   → Layer 3: Dynamic endpoints confuse scanner
   → Layer 4: Fake headers mislead fingerprinting

2. Attacker finds /admin endpoint (honey pot)
   → Layer 6: Access logged, IP recorded
   → Fake data returned, attacker wastes time

3. Attacker tries SQL injection
   → Layer 6: Honey pot detects pattern, logs attempt
   → Layer 5: Randomized responses confuse automation

4. Attacker intercepts BLE traffic
   → Layer 1: ECDH-generated keys are useless without private key
   → Layer 2: Double encryption still protects data

5. Attacker attempts timing attack on auth
   → Layer 8: Random delays hide password validation timing
   → No information leaked

6. Attacker tries brute force via web API
   → Layer 7: Method tunneling breaks automated tools
   → Layer 8: Consistent random timing prevents optimization

Result: Attack fails, you have logs of attempt, attacker wasted hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Layering isn't redundancy—it's strategy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each layer should target &lt;strong&gt;different&lt;/strong&gt; attack vectors. Having 8 encryption layers doesn't help if they all use the same algorithm and key.&lt;/p&gt;

&lt;p&gt;Good layering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 1: Key exchange (MITM protection)&lt;/li&gt;
&lt;li&gt;Layer 2: Encryption (eavesdropping protection)&lt;/li&gt;
&lt;li&gt;Layer 6: Honey pot (intrusion detection)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad layering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Layer 1: AES-256&lt;/li&gt;
&lt;li&gt;Layer 2: AES-256 again&lt;/li&gt;
&lt;li&gt;Layer 3: AES-256 again (just slower)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Security is about time, not impossibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;No system is unbreakable. But security can make attacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Take too long (months/years)&lt;/li&gt;
&lt;li&gt;Cost too much (hardware, time, expertise)&lt;/li&gt;
&lt;li&gt;Be too risky (early detection, logging)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a $15 DIY device storing personal passwords, being harder to crack than a corporate server is enough deterrent.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Observability matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The honey pot layer isn't about stopping attacks—it's about &lt;strong&gt;knowing&lt;/strong&gt; they're happening. Early warning is invaluable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Open source security works&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Making the code public doesn't weaken security. The architecture still requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Physical access to the device&lt;/li&gt;
&lt;li&gt;Extracting hardware-unique keys&lt;/li&gt;
&lt;li&gt;Bypassing multiple independent layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Security through obscurity" is not security. &lt;strong&gt;Security through architecture&lt;/strong&gt; is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation Complexity
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Code Complexity&lt;/th&gt;
&lt;th&gt;Performance Impact&lt;/th&gt;
&lt;th&gt;Maintenance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ECDH&lt;/td&gt;
&lt;td&gt;Medium (mbedTLS)&lt;/td&gt;
&lt;td&gt;Low (once per session)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session Encryption&lt;/td&gt;
&lt;td&gt;Low (built-in)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic Endpoints&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Header Obfuscation&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anti-Fingerprinting&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low (~50ms)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Honey Pot&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method Tunneling&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timing Protection&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium (~100ms)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total added overhead:&lt;/strong&gt; ~150ms per critical operation&lt;br&gt;
&lt;strong&gt;Memory overhead:&lt;/strong&gt; ~15KB for security code&lt;br&gt;
&lt;strong&gt;Development time:&lt;/strong&gt; ~2 weeks (with testing)&lt;/p&gt;


&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Current architecture is strong, but there's always room for improvement:&lt;/p&gt;
&lt;h3&gt;
  
  
  Planned additions:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware secure element&lt;/strong&gt; (ATECC608A) for key storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted SD card backup&lt;/strong&gt; with versioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-device sync&lt;/strong&gt; via encrypted mesh protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;U2F/FIDO2 support&lt;/strong&gt; for hardware security keys&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Testing improvements:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Automated penetration testing suite&lt;/li&gt;
&lt;li&gt;Fuzzing for API endpoints&lt;/li&gt;
&lt;li&gt;Load testing for timing attack resistance&lt;/li&gt;
&lt;li&gt;Third-party security audit&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building secure embedded systems doesn't require enterprise budgets or closed-source magic. It requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Understanding threat models&lt;/strong&gt; (what attacks are likely?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layered defenses&lt;/strong&gt; (multiple independent protections)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing and validation&lt;/strong&gt; (verify it actually works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open, auditable code&lt;/strong&gt; (trust through transparency)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 8-layer architecture in SecureGen demonstrates that even a $15 ESP32 device can implement defense-in-depth security rivaling commercial products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The full source code is available on GitHub:&lt;/strong&gt; &lt;a href="https://github.com/makepkg/SecureGen" rel="noopener noreferrer"&gt;github.com/makepkg/SecureGen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every security decision, every line of code, every trade-off is documented and auditable. Because &lt;strong&gt;verifiable security&lt;/strong&gt; is the only real security.&lt;/p&gt;


&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/makepkg/SecureGen" rel="noopener noreferrer"&gt;SecureGen GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/makepkg/building-a-hardware-totp-authenticator-on-esp32-the-memory-management-nightmare-45ko"&gt;Part 1: Memory Management on ESP32&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/makepkg/implementing-ble-security-on-esp32-le-secure-connections-the-hard-way-2kb9"&gt;Part 2: BLE Security Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/index.html" rel="noopener noreferrer"&gt;ESP32 Security Features (Espressif)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-embedded-application-security/" rel="noopener noreferrer"&gt;OWASP Embedded Application Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;Questions? Comments?&lt;/strong&gt; Drop them below! I'm happy to discuss specific layers, trade-offs, or alternative approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building something similar?&lt;/strong&gt; I'd love to hear about your security architecture!&lt;/p&gt;
&lt;h1&gt;
  
  
  esp32 #security #embedded #architecture #defenseinдепth #encryption #opensource
&lt;/h1&gt;
&lt;h2&gt;
  
  
  📸 Images to Include
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. &lt;strong&gt;Cover Image&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Your 8-layers infographic (already have)&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;Code Comparison Images&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78qd2knsnibv8hccoo9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78qd2knsnibv8hccoo9s.png" alt=" " width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create these in VS Code with dark theme, screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE:
GET /api/totp
GET /api/passwords
DELETE /api/passwords/1

AFTER:
POST /a7f3c9e8b2d4f1a6...
POST /9e4f1b8c3a7d2f5e...
POST /c3f7a9e1b5d8f2a4...

BEFORE:
Server: ESP-AsyncWebServer/1.2.3
X-ESP32-Chip: ESP32-D0WDQ6
Content-Type: application/json

AFTER:
X-k8f3a2: application/json
X-AspNetMvc-Version: 5.2.7
X-Powered-By: PHP/7.4.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsusplg7niqa1dpw6v30o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsusplg7niqa1dpw6v30o.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Timing Attack Graph&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simple graph showing response times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without protection: clear pattern&lt;/li&gt;
&lt;li&gt;With protection: random scatter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple graph showing response times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without protection: clear pattern&lt;/li&gt;
&lt;li&gt;With protection: random scatter&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>riscv</category>
      <category>c</category>
      <category>esp32</category>
      <category>lilygo</category>
    </item>
    <item>
      <title>Implementing BLE Security on ESP32: LE Secure Connections the Hard Way</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Sat, 14 Feb 2026 16:42:03 +0000</pubDate>
      <link>https://dev.to/makepkg/implementing-ble-security-on-esp32-le-secure-connections-the-hard-way-2kb9</link>
      <guid>https://dev.to/makepkg/implementing-ble-security-on-esp32-le-secure-connections-the-hard-way-2kb9</guid>
      <description>&lt;h1&gt;
  
  
  Implementing BLE Security on ESP32: LE Secure Connections the Hard Way
&lt;/h1&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/makepkg/building-a-hardware-totp-authenticator-on-esp32-the-memory-management-nightmare-45ko"&gt;previous post&lt;/a&gt;, I talked about memory management challenges when building SecureGen. Today, let's dive into something equally painful: implementing proper BLE security.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Passwords Over Bluetooth
&lt;/h2&gt;

&lt;p&gt;My device needs to send passwords via BLE to any computer. But standard Bluetooth? &lt;strong&gt;Not secure enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone intercepts the pairing process, they can decrypt everything. For a password manager, that's catastrophic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter LE Secure Connections
&lt;/h2&gt;

&lt;p&gt;Bluetooth 4.2+ introduced &lt;strong&gt;LE Secure Connections&lt;/strong&gt; - basically, proper encryption with MITM (Man-in-the-Middle) protection.&lt;/p&gt;

&lt;p&gt;But getting it working on ESP32? That's another story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Levels on ESP32
&lt;/h3&gt;

&lt;p&gt;The ESP32 BLE stack has several security modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD - No security&lt;/span&gt;
&lt;span class="n"&gt;esp_ble_gap_set_security_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP_BLE_SM_AUTHEN_REQ_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;auth_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;auth_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_LE_AUTH_NO_BOND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// BETTER - Encryption but no MITM protection&lt;/span&gt;
&lt;span class="n"&gt;auth_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_LE_AUTH_BOND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// BEST - LE Secure Connections with MITM&lt;/span&gt;
&lt;span class="n"&gt;auth_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_LE_AUTH_REQ_SC_MITM_BOND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went with the last one. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SC&lt;/strong&gt; (Secure Connections) = Uses P-256 elliptic curve&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MITM&lt;/strong&gt; = Prevents man-in-the-middle attacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BOND&lt;/strong&gt; = Remembers paired devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Pairing Dance
&lt;/h2&gt;

&lt;p&gt;With MITM protection, pairing requires user verification. ESP32 supports several methods:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Just Works&lt;/strong&gt; - No verification (useless for security)&lt;br&gt;
&lt;strong&gt;2. Passkey Entry&lt;/strong&gt; - User types 6-digit code&lt;br&gt;
&lt;strong&gt;3. Numeric Comparison&lt;/strong&gt; - User confirms matching codes&lt;/p&gt;

&lt;p&gt;Relevant files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/ble_keyboard.cpp&lt;/code&gt; - BLE HID implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/ble_security.cpp&lt;/code&gt; - Security configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/keyboard_layouts.h&lt;/code&gt; - Layout mappings
I chose &lt;strong&gt;Numeric Comparison&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;gap_event_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_gap_ble_cb_event_t&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                       &lt;span class="n"&gt;esp_ble_gap_cb_param_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ESP_GAP_BLE_NC_REQ_EVT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// 6-digit PIN appears on both screens&lt;/span&gt;
            &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;passkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ble_security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_notif&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Display on device screen&lt;/span&gt;
            &lt;span class="n"&gt;display_pairing_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passkey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// User must confirm match on both devices&lt;/span&gt;
            &lt;span class="n"&gt;esp_ble_confirm_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ble_security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ble_req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bd_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When pairing, a 6-digit code appears on both the ESP32 screen and the computer. User verifies they match. If they don't match → someone's intercepting the connection.&lt;/p&gt;
&lt;h2&gt;
  
  
  iOS vs Android: The Nightmare
&lt;/h2&gt;

&lt;p&gt;This is where things got painful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&gt; Works perfectly with default settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&gt; Refuses to pair. Just... refuses.&lt;/p&gt;

&lt;p&gt;After &lt;strong&gt;days&lt;/strong&gt; of debugging, I found the issue: &lt;strong&gt;iOS enforces stricter bonding requirements.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix: Adaptive Bonding
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configure_bonding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;is_ios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;key_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Maximum encryption key size&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;init_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_ENC_KEY_MASK&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_ID_KEY_MASK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;rsp_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_ENC_KEY_MASK&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_ID_KEY_MASK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_ios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// iOS requires these additional keys&lt;/span&gt;
        &lt;span class="n"&gt;init_key&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_CSR_KEY_MASK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;rsp_key&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;ESP_BLE_CSR_KEY_MASK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;esp_ble_gap_set_security_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP_BLE_SM_MAX_KEY_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;key_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;esp_ble_gap_set_security_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP_BLE_SM_SET_INIT_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;init_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;esp_ble_gap_set_security_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP_BLE_SM_SET_RSP_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;rsp_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;strong&gt;CSR (Connection Signature Resolving) key&lt;/strong&gt; is what iOS wants. Without it, pairing fails silently with no error messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detecting iOS vs Android
&lt;/h2&gt;

&lt;p&gt;But how do you know if the connecting device is iOS?&lt;/p&gt;

&lt;p&gt;Turns out, you can check during the pairing process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;gap_event_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_gap_ble_cb_event_t&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                       &lt;span class="n"&gt;esp_ble_gap_cb_param_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ESP_GAP_BLE_AUTH_CMPL_EVT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ble_security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth_cmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Check device properties&lt;/span&gt;
                &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;is_ios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_device_capabilities&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ble_security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth_cmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bd_addr&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Store for future connections&lt;/span&gt;
                &lt;span class="n"&gt;save_device_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ble_security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth_cmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bd_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                &lt;span class="n"&gt;is_ios&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I check the device's BLE capabilities during the first pairing and store it. Subsequent connections use the stored info.&lt;/p&gt;

&lt;h2&gt;
  
  
  BLE HID Keyboard Setup
&lt;/h2&gt;

&lt;p&gt;Now that we have secure pairing, let's send passwords.&lt;/p&gt;

&lt;p&gt;BLE HID (Human Interface Device) lets the ESP32 pretend to be a keyboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// HID Report Descriptor for keyboard&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;hid_keyboard_report_map&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mh"&gt;0x05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Page (Generic Desktop)&lt;/span&gt;
    &lt;span class="mh"&gt;0x09&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage (Keyboard)&lt;/span&gt;
    &lt;span class="mh"&gt;0xA1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Collection (Application)&lt;/span&gt;

    &lt;span class="c1"&gt;// Modifier keys (Ctrl, Shift, Alt, etc.)&lt;/span&gt;
    &lt;span class="mh"&gt;0x05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Page (Key Codes)&lt;/span&gt;
    &lt;span class="mh"&gt;0x19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xE0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Minimum (Left Ctrl)&lt;/span&gt;
    &lt;span class="mh"&gt;0x29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xE7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Maximum (Right GUI)&lt;/span&gt;
    &lt;span class="mh"&gt;0x15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Logical Minimum (0)&lt;/span&gt;
    &lt;span class="mh"&gt;0x25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Logical Maximum (1)&lt;/span&gt;
    &lt;span class="mh"&gt;0x75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Report Size (1)&lt;/span&gt;
    &lt;span class="mh"&gt;0x95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Report Count (8)&lt;/span&gt;
    &lt;span class="mh"&gt;0x81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Input (Data, Variable, Absolute)&lt;/span&gt;

    &lt;span class="c1"&gt;// Regular keys&lt;/span&gt;
    &lt;span class="mh"&gt;0x95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Report Count (6)&lt;/span&gt;
    &lt;span class="mh"&gt;0x75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Report Size (8)&lt;/span&gt;
    &lt;span class="mh"&gt;0x15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Logical Minimum (0)&lt;/span&gt;
    &lt;span class="mh"&gt;0x26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Logical Maximum (255)&lt;/span&gt;
    &lt;span class="mh"&gt;0x05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Page (Key Codes)&lt;/span&gt;
    &lt;span class="mh"&gt;0x19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Minimum (0)&lt;/span&gt;
    &lt;span class="mh"&gt;0x2A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Usage Maximum (255)&lt;/span&gt;
    &lt;span class="mh"&gt;0x81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Input (Data, Array)&lt;/span&gt;

    &lt;span class="mh"&gt;0xC0&lt;/span&gt;  &lt;span class="c1"&gt;// End Collection&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This descriptor tells the OS: "I'm a keyboard with modifier keys and 6 simultaneous key presses."&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Layout Hell
&lt;/h2&gt;

&lt;p&gt;Here's a fun problem: &lt;strong&gt;Different keyboard layouts handle special characters differently.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@ symbol:
US layout:   Shift + 2
UK layout:   Shift + '
German:      AltGr + Q
French:      AltGr + à
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My solution: &lt;strong&gt;configurable layout mapping&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;keycode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;KeyMapping&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;KeyMapping&lt;/span&gt; &lt;span class="n"&gt;us_layout&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x1F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SHIFT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// @ = Shift+2&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'#'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SHIFT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// # = Shift+3&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'$'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SHIFT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// $ = Shift+4&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;KeyMapping&lt;/span&gt; &lt;span class="n"&gt;uk_layout&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SHIFT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// @ = Shift+'&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sc"&gt;'#'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// # = just 3&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;type_character&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KeyMapping&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;KeyMapping&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;send_key_press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;keycode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users select their keyboard layout in the web interface. The device translates characters to the correct key combinations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security: It's Not Just Encryption
&lt;/h2&gt;

&lt;p&gt;Even with encrypted BLE, you need additional protections:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. PIN Protection Before Transmission
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;send_password_via_ble&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Require PIN on device screen&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;verify_user_pin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;display_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PIN required"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Show which password will be sent&lt;/span&gt;
    &lt;span class="n"&gt;display_confirm_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Wait for button confirmation (both buttons)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;wait_for_button_confirmation&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Only now send via BLE&lt;/span&gt;
    &lt;span class="n"&gt;ble_keyboard_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Bonding Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store bonded devices&lt;/span&gt;
&lt;span class="cp"&gt;#define MAX_BONDED_DEVICES 5
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;manage_bonding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;bonded_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;esp_ble_get_bond_device_num&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bonded_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_BONDED_DEVICES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Remove oldest bonded device&lt;/span&gt;
        &lt;span class="n"&gt;esp_ble_bond_dev_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esp_ble_bond_dev_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
                                             &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bonded_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;esp_ble_get_bond_device_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bonded_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;esp_ble_remove_bond_device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;bd_addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Timeout Protection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ble_transmission_task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Connect&lt;/span&gt;
    &lt;span class="n"&gt;ble_connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Send password&lt;/span&gt;
    &lt;span class="n"&gt;ble_keyboard_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// CRITICAL: Disconnect after transmission&lt;/span&gt;
    &lt;span class="n"&gt;vTaskDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdMS_TO_TICKS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// Wait for typing to complete&lt;/span&gt;
    &lt;span class="n"&gt;ble_disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Disable BLE completely after use&lt;/span&gt;
    &lt;span class="n"&gt;ble_deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BLE only active during password transmission. Rest of the time, it's off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory Considerations
&lt;/h2&gt;

&lt;p&gt;Remember from my &lt;a href="https://dev.to%D0%A1%D0%A1%D0%AB%D0%9B%D0%9A%D0%90"&gt;first post&lt;/a&gt;: BLE stack uses ~70KB RAM.&lt;/p&gt;

&lt;p&gt;When sending passwords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;safe_ble_transmission&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check heap before enabling BLE&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFreeHeap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;80000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;display_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Low memory"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Disable WiFi first&lt;/span&gt;
    &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIFI_OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Now safe to enable BLE&lt;/span&gt;
    &lt;span class="n"&gt;init_ble_keyboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;send_password&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;deinit_ble_keyboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing BLE Security
&lt;/h2&gt;

&lt;p&gt;How do you verify it's actually secure?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Wireshark BLE Capture:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use an Ubertooth or nRF52840 to capture BLE packets. With proper LE Secure Connections, packets should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypted with AES-128&lt;/li&gt;
&lt;li&gt;Indecipherable without the pairing key&lt;/li&gt;
&lt;li&gt;Protected by message integrity checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Try MITM Attack:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;btlejack&lt;/code&gt; or similar tools to attempt interception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;btlejack &lt;span class="nt"&gt;-f&lt;/span&gt; 0x9c68 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0x1fffffffff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If implemented correctly, the attack should fail because the numeric comparison prevents MITM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. iOS Security Check:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On iOS, check Settings → Bluetooth → Device Info. Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Encrypted" badge&lt;/li&gt;
&lt;li&gt;"Security: High"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;iOS is picky&lt;/strong&gt; - Android will pair with almost anything, iOS won't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numeric Comparison is worth it&lt;/strong&gt; - Slight UX friction for massive security gain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disconnect aggressively&lt;/strong&gt; - BLE should be off when not transmitting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test on real devices&lt;/strong&gt; - Emulators don't catch bonding issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard layouts matter&lt;/strong&gt; - International users exist!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In the next post, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generating hardware encryption keys&lt;/strong&gt; from ESP32 chip parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key derivation&lt;/strong&gt; with PBKDF2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why flash wear leveling matters&lt;/strong&gt; for security devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Full implementation: &lt;a href="https://github.com/makepkg/SecureGen" rel="noopener noreferrer"&gt;SecureGen GitHub&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Questions? Comments?&lt;/strong&gt; Drop them below! I'm still learning and would love to hear how others solved similar problems.&lt;/p&gt;

&lt;h1&gt;
  
  
  esp32 #bluetooth #security #embedded #ble #encryption
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>cybersecurity</category>
      <category>iot</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Hardware TOTP Authenticator on ESP32: The Memory Management Nightmare</title>
      <dc:creator>makepkg</dc:creator>
      <pubDate>Wed, 11 Feb 2026 19:05:18 +0000</pubDate>
      <link>https://dev.to/makepkg/building-a-hardware-totp-authenticator-on-esp32-the-memory-management-nightmare-45ko</link>
      <guid>https://dev.to/makepkg/building-a-hardware-totp-authenticator-on-esp32-the-memory-management-nightmare-45ko</guid>
      <description>&lt;h1&gt;
  
  
  Building a Hardware TOTP Authenticator on ESP32: The Memory Management Nightmare
&lt;/h1&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Problem: BLE + WiFi = Crash
&lt;/h2&gt;

&lt;p&gt;My device needed to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate TOTP codes&lt;/strong&gt; (requires accurate time via NTP/WiFi)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type passwords via Bluetooth&lt;/strong&gt; (requires BLE HID keyboard)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds simple, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Here's what I learned the hard way:
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This will crash after a few minutes:&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;bad_approach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;BLEDevice&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SecureGen"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// 💥 Heap exhausted&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Solution: Strict Mode Separation
&lt;/h2&gt;

&lt;p&gt;Instead of trying to run everything at once, I separated the device into distinct modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TOTP Mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WiFi ON for NTP sync&lt;/li&gt;
&lt;li&gt;Get accurate time&lt;/li&gt;
&lt;li&gt;WiFi OFF immediately after sync&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;millis()&lt;/code&gt; for countdown between syncs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Password Manager Mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WiFi completely disabled&lt;/li&gt;
&lt;li&gt;Only BLE active&lt;/li&gt;
&lt;li&gt;Pure offline operation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;BLE Transmission:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WiFi always OFF before connecting&lt;/li&gt;
&lt;li&gt;BLE connects, types password, disconnects&lt;/li&gt;
&lt;li&gt;No coexistence needed
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;safe_approach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TOTP_MODE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sync_time_via_ntp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Force disconnect&lt;/span&gt;
        &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIFI_OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;need_ble_transmission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ensure_wifi_off&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Double check&lt;/span&gt;
        &lt;span class="n"&gt;init_ble_keyboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;send_password&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;deinit_ble_keyboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring Heap Memory
&lt;/h2&gt;

&lt;p&gt;I added aggressive heap monitoring to catch memory leaks early:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;check_heap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Free heap: %d bytes&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ESP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFreeHeap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Min free heap: %d bytes&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ESP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMinFreeHeap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ESP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getFreeHeap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Critical! Take action&lt;/span&gt;
        &lt;span class="n"&gt;emergency_cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;getMinFreeHeap()&lt;/code&gt; function was crucial - it shows the lowest point your heap reached, helping you spot fragmentation issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Memory Optimization Tricks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Stack Size Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BLE callbacks run in their own task. Default stack size wasn't enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Increase BLE task stack&lt;/span&gt;
&lt;span class="n"&gt;xTaskCreatePinnedToCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ble_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"BLE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Increased from default 4096&lt;/span&gt;
    &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. String Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid &lt;code&gt;String&lt;/code&gt; class on ESP32 - it fragments heap. Use &lt;code&gt;char[]&lt;/code&gt; arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad (heap fragmentation):&lt;/span&gt;
&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Good (stack allocation):&lt;/span&gt;
&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;snprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Hello %s!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. WiFi Events Cleanup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WiFi events can leak memory if not handled properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// true = erase credentials&lt;/span&gt;
&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIFI_OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Let it settle&lt;/span&gt;
&lt;span class="n"&gt;esp_wifi_stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;esp_wifi_deinit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;After these optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable operation for days without reboot&lt;/li&gt;
&lt;li&gt;Min free heap stays above 50KB (safe zone)&lt;/li&gt;
&lt;li&gt;No more random crashes&lt;/li&gt;
&lt;li&gt;Clean mode transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ESP32 is powerful, but not unlimited&lt;/strong&gt; - you're still working with embedded constraints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure everything&lt;/strong&gt; - heap monitoring saved me countless hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mode separation works&lt;/strong&gt; - don't try to do everything simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WiFi/BLE coexistence is hard&lt;/strong&gt; - avoid it if you can&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;The full source code is available on GitHub: &lt;a href="https://github.com/Unix-like-SoN/SecureGen" rel="noopener noreferrer"&gt;SecureGen&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TOTP authenticator (RFC 6238)&lt;/li&gt;
&lt;li&gt;Encrypted password manager (AES-256)&lt;/li&gt;
&lt;li&gt;BLE HID keyboard&lt;/li&gt;
&lt;li&gt;LE Secure Connections with MITM protection&lt;/li&gt;
&lt;li&gt;Web management interface&lt;/li&gt;
&lt;li&gt;Battery powered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building anything with ESP32 + BLE + WiFi, I hope this saves you some debugging time!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

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




&lt;p&gt;&lt;strong&gt;Questions? Comments?&lt;/strong&gt; Drop them below! I'm still learning and would love to hear how others solved similar problems.&lt;/p&gt;

&lt;h1&gt;
  
  
  esp32 #iot #embedded #security #bluetooth #opensource
&lt;/h1&gt;

</description>
      <category>esp32</category>
      <category>lilygo</category>
      <category>iot</category>
      <category>security</category>
    </item>
  </channel>
</rss>
