ESP-ProMicro-HidKey
A multi-secret HID keyboard emulator for Arduino Pro Micro Leonardo (ATmega32U4) that triggers passwords or other text payloads via USB keyboard emulation using button press sequences.
Overview
This project implements a sequence-based secret launcher on microcontroller hardware. Different combinations of short and long button presses unlock and transmit distinct payloads through USB keyboard emulation. Payloads can be simple passwords or multiline text such as ASCII-armored GPG private keys.
Features
- Sequence-Based Activation: Multiple password combinations triggered by button press patterns
- Short press (< 500ms) =
0| Long press (≥ 500ms) =1
- Short press (< 500ms) =
- Encrypted Secret Storage:
- Build-Time Encryption: AES-128-CBC with per-secret random IV
- Runtime Decryption: Entries are decrypted directly from flash immediately before USB typing
- Multiline Secret Support: Supports escaped newlines and file-based import for ASCII-armored GPG private keys
- Brute-Force Counter: Persisted in EEPROM (weak — small PIN space, see Security Architecture)
- Memory Security: Barrier-protected buffer wiping and automatic crypto context zeroing
- LED Feedback: Real-time status indicators for user actions
- Configuration Management: Simple
.envfile configuration (auto-generated if missing) - No Plaintext Storage: Secrets never appear in generated headers as plaintext
Getting Started
Requirements
Hardware
| Component | Quantity | Notes / Purpose | Approx. Price (USD) |
|---|---|---|---|
| ATmega32U4 | 1 | Main microcontroller | $5 - $10 (can be much cheaper in bulk or via AliExpress) |
| LED diode | 1 | Status indicator | $0.10 - $0.50 (bulk packs or leftover components can be cheaper) |
| Resistor 220Ω - 3KΩ | 1 | Current limiting for LED | $0.05 - $0.10 (often <$0.01 in bulk or from leftovers) |
| Push button | 1 | User input | $0.20 - $1 (cheaper via AliExpress or bulk packs) |
| Heat shrink tube | 1 | Insulation and cable protection | $0.10 - $0.50 (bulk rolls are more cost-effective) |
| Wire pieces / cable | For connections between components |
Notes:
- Buying in higher quantities or using leftover/recycled parts can drastically reduce cost.
- AliExpress and similar suppliers usually offer significantly lower prices, especially for microcontrollers and common components.
- Total cost for a single prototype: ~$6 - $13, but can drop below $5 per unit when sourcing smartly.
Wiring
Pro Micro Pin Component
───────────── ─────────────────────────────────
GND Button (first terminal)
Pin 9 Button (second terminal)
Pin 10 LED anode (+) via 220Ω - 3kΩ resistor
GND LED cathode (-)
Note: This is an example image showing an older ATmega Pro Micro board — it may look a little rough. Newer boards may look different.
After soldering, secure exposed contacts with hot glue or UV resin, and fully enclose the board in heat-shrink tubing or a 3D-printed case.
Pin Configuration:
- Button input: Pin 9 (with internal pullup)
- LED output: Pin 10
Software
- Python 3.6+ with
pycryptodomexlibrary - arduino-cli (for building and flashing)
- SparkFun AVR board support (installed via arduino-cli)
Installation
1. Install Python Dependencies (Debian/Ubuntu/Kali):
sudo apt install python3-pycryptodomexOr via pip (pinned):
pip3 install --user -r requirements.txtNote: the import namespace is Cryptodome, provided by pycryptodomex — do
not pip install Cryptodome (that bare name is not the canonical package).
2. Install arduino-cli:
Linux/macOS:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | shOr via package manager:
# Debian/Ubuntu
sudo apt install arduino-cli
# macOS
brew install arduino-cli3. Clone Repository:
git clone https://github.com/arn-c0de/ESP-ProMicro-HidKey
cd ESP-ProMicro-HidKeyConfiguration
Setting Up Your Environment
1. Create Configuration File:
cp .env.example .env
nano .env # or your preferred editor2. Generate AES Master Key:
python3 -c "import secrets; print(secrets.token_hex(16))"
# Output example: a7b3c9d2e8f41a6b5c7e9f2d4a8c1b3e3. Configure .env File:
# AES-128 Master Key (32 hex characters = 16 bytes)
AES_MASTER_KEY=a7b3c9d2e8f41a6b5c7e9f2d4a8c1b3e
# Secret combinations
COMBINATION_COUNT=3
COMBINATION_0_SEQUENCE="0,0,1,0"
COMBINATION_0_TYPE="text"
COMBINATION_0_PASSWORD="admin123"
COMBINATION_1_SEQUENCE="1,0,0"
COMBINATION_1_TYPE="text"
COMBINATION_1_SECRET="user456"
COMBINATION_2_SEQUENCE="0,1,1,0,1"
COMBINATION_2_TYPE="gpg-private-key"
COMBINATION_2_GPG_PRIVATE_KEY_FILE="./secrets/private-message-key.asc"Quoting Notes:
- If a secret contains a literal
"character, wrap the whole value in single quotes. - For very complex secrets, prefer
COMBINATION_n_SECRET_FILE=./secrets/my-secret.txt
Sequence Format:
0= Short button press (< 500ms)1= Long button press (≥ 500ms)- Maximum sequence length: 20 presses
- Separate presses with commas
Example: Sequence "0,0,1,0" represents:
- Short press
- Short press
- Long press
- Short press
Building and Flashing
1. Connect Pro Micro via USB
2. Build and Upload:
./build.sh [port]The build script automatically:
- Generates
embedded_passwords.hfrom.env - Compiles the sketch
- Uploads to the device
If you do not pass a port explicitly, build.sh lists the detected serial ports and lets you choose one interactively.
Custom Port Example:
./build.sh /dev/ttyACM1🔁 Optional: Reset EEPROM before uploading
The --reset-eeprom flag (short -r) clears the device's EEPROM state, including the persistent brute-force counter. This is useful if you want a clean state before flashing.
Examples:
# Reset EEPROM and upload after interactive port selection
./build.sh -r
# Reset EEPROM and upload to specific port
./build.sh /dev/ttyACM1 -rNotes:
- The reset step is optional and safe for normal updates.
- It clears persistent EEPROM state such as the failed-attempt counter.
Upload Troubleshooting: If upload fails, enter bootloader mode:
- Press the reset button on Pro Micro twice quickly
- Run
./build.shimmediately (within 8 seconds)
Basic Operation
After flashing:
- Connect the Pro Micro to your target computer
- Enter the configured button sequence
- The password is automatically typed via USB keyboard emulation
- 3-second timeout resets the sequence if no input is received
LED Status Indicators
| LED Pattern | Meaning |
|---|---|
| 4 quick blinks | Password matched and transmitted successfully |
| Solid 2-second glow | Invalid sequence or no match found |
| 10 rapid blinks | Brute-force lockout activated |
| 2 quick blinks (startup) | Device ready |
Security Architecture
⚠️ Read this before trusting the device with anything valuable. The encryption here is obfuscation / defense-in-depth, not a security boundary. The AES master key is stored in plaintext flash right next to the ciphertext it protects, so anyone who can read the firmware image recovers every secret — including any GPG private key. This has been demonstrated: a flash dump decrypts itself with no.envand no EEPROM. Treat a device that has left your physical control as fully compromised.
How it works (single-stage)
Build-time encryption (Python)
- Secrets are encrypted with AES-128-CBC (PKCS#7 padding) using the master
key from
.env. - A CSPRNG 16-byte IV is generated per secret and prepended to the ciphertext.
- Encrypted data is marked with flag
0x01and embedded inembedded_passwords.h. - The master key is embedded in PROGMEM (flash) in the same image.
Runtime handling
- On a recognized button sequence, the matching entry is read from PROGMEM and decrypted in RAM using the embedded master key.
- The plaintext is typed over USB keyboard emulation into whatever window has focus, then the RAM buffer is wiped.
What the encryption does and does not buy you
| Property | Reality |
|---|---|
Resists casual strings/inspection of the image |
✅ Yes |
| Each secret has a unique IV (no block-pattern leakage) | ✅ Yes |
| Resists an attacker who can dump flash | ❌ No — key is in the dump |
| Authenticates ciphertext (tamper detection) | ❌ No (CBC, no MAC) |
| Key derived from a user secret | ❌ No — key is stored, not derived |
Memory handling
- Plaintext exists in RAM only during typing and is then overwritten with a
secureWipe()helper that uses a compiler memory barrier so the wipe is not optimized away (single pass — SRAM has no remanence). - AES context, key copy, and IV buffers are wiped on every exit path.
- Plaintext secrets are never written to the generated header.
Brute-force protection (weak — do not rely on it)
- A failed-attempt counter is stored in one EEPROM byte (threshold 5, 30 s lockout) and a corrupted counter now fails closed (enters lockout).
- Limitations you must understand: each press encodes only 1 bit, so a 3–5 press sequence is only 8–32 possibilities. The lockout timer is not persisted across reboots, and an attacker can power-cycle or cut power before the recognition timeout to avoid the increment. The counter rate-limits but cannot make a short button PIN cryptographically strong.
Threat model
Reasonably resists
- ✅ Casual inspection of the firmware image (
strings) - ✅ USB keyboard replay of a previously sniffed sequence (it's a press pattern, not a transmitted secret)
- ✅ Recovery of plaintext from RAM after power-off (decrypt-on-demand, wiped after)
Does NOT resist
- ❌ Flash extraction alone — recovers the key and all secrets (the device
ships
tools/reset_eeprom/dump_flash.sh+decode_flash.py, which do exactly this) - ❌ Brute-force of the short button PIN (small keyspace, bypassable lockout)
- ❌ A device plugged into an attacker's host (it will type secrets on a correct sequence, with no host authentication or confirmation prompt)
- ❌ Hardware attacks (power/EM analysis, fault injection, glitching) — the ATmega32U4 has no countermeasures
- ❌ Physical coercion
Recommendations:
- Use only in physically secure environments; treat a stolen device as fully compromised.
- Avoid storing irreplaceable, long-lived secrets (especially GPG private keys) — they cannot be protected on this hardware.
- For real protection, derive the key from a typed passphrase and/or move to
hardware with a secure element. See
VULNERABILITY_ANALYSIS.md. - Consider device as two-factor authentication complement (possession + sequence knowledge)
- For critical applications: implement additional tamper detection or destruction mechanisms
Troubleshooting
Upload Fails
Problem: Sketch does not upload to device
Solutions:
- Manually trigger bootloader: Press reset button twice quickly
- Immediately run
./build.sh(within 8 seconds) - Verify serial port:
ls /dev/ttyACM*orls /dev/ttyUSB* - Grant user permissions:
sudo usermod -a -G dialout $USER(then logout/login)
Compilation Fails
Problem: Build script reports compilation errors
Solutions:
- Verify arduino-cli:
arduino-cli version - Check Python version:
python3 --version(requires 3.6+) - Validate
.envexists:test -f .env || echo "Missing .env" - Check
.envsyntax (no spaces around=, proper quotes)
Port Not Found
Solutions by Platform:
- Linux: Usually
/dev/ttyACM0,/dev/ttyACM1, or/dev/ttyUSB0 - macOS: Use tab completion with
/dev/cu.usbmodem* - Windows: Check Device Manager; typically
COM3orCOM4
List all ports:
# Linux
ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null
# macOS
ls /dev/cu.* 2>/dev/nullConfiguration File Missing
Solution:
cp .env.example .env
# Edit .env with your settings
nano .envProject Structure
ESP-ProMicro-HidKey/
├── ESP-ProMicro-HidKey.ino # Main Arduino sketch
├── aes.h # AES-128-CBC implementation
├── embedded_passwords.h # Auto-generated encrypted secrets (gitignored)
├── generate_password_header.py # Secret encryption generator
├── decode_flash.py # Owner-only flash-dump decryptor (forensics)
├── rotate_aes_master_key.sh # Rotate the AES master key in .env
├── build.sh # Build and flash automation script
├── build_config.h # Compile-time configuration
├── requirements.txt # Pinned Python build dependency
├── tools/reset_eeprom/ # EEPROM reset + flash dump helpers
├── .env # Your secret configuration (gitignored)
├── .env.example # Example configuration template
├── .gitignore # Git ignore rules
├── README.md # This file
├── CODE_REVIEW_2026.md # Review findings + best-practice notes
├── VULNERABILITY_ANALYSIS.md # Threat model and ranked findings
└── LICENSE # Project license
Key Files
| File | Purpose |
|---|---|
ESP-ProMicro-HidKey.ino |
Main application logic, sequence matching, LED control |
aes.h |
AES-128-CBC encryption/decryption |
generate_password_header.py |
Reads .env, encrypts secrets, generates headers |
build.sh |
Orchestrates build process, password generation, upload |
Customization
Timing Configuration
Edit timing constants in ESP-ProMicro-HidKey.ino:
#define LONG_PRESS_MS 500 // Long press threshold (milliseconds)
#define TIMEOUT_MS 3000 // Sequence timeout (milliseconds)
#define MAX_FAILED_ATTEMPTS 5 // Failed attempts before lockout
#define LOCKOUT_MS 30000 // Lockout duration (milliseconds)
#define MAX_SEQUENCE_LENGTH 20 // Maximum button presses per sequencePin Configuration
Change hardware pins in ESP-ProMicro-HidKey.ino:
#define LED_PIN 10 // LED output pin
#define BUTTON_PIN 9 // Button input pinAdding More Passwords
Without recompilation:
- Update
.env: incrementCOMBINATION_COUNT - Add new entries:
COMBINATION_3_SEQUENCE="1,1,0" COMBINATION_3_TYPE="text" COMBINATION_3_PASSWORD="newpassword123"
- Run
./build.shto regenerate headers and upload
Technical Details
Memory Usage
- Flash: ~9-10KB (sketch + encrypted secrets)
- SRAM: ~300 bytes global, plus temporary plaintext buffer during active output
- EEPROM: minimal persistent state for brute-force protection
- ATmega32U4 Specs: 32KB flash, 2.5KB SRAM, 1KB EEPROM
Password Storage Architecture
Encrypted entry in flash:
[Flag: 0x01] [IV: 16 bytes] [AES-CBC ciphertext: variable length]
Runtime Processing:
- Only the active secret is decrypted into SRAM
- Plaintext transmitted to USB
- Buffer immediately cleared via a barrier-protected
secureWipe()
Sequence Matching
Uses constant-time bitwise XOR accumulation to prevent timing attacks that could leak partial sequence matches through execution time measurements.
Multiline Secret Handling
ASCII-armored GPG private keys can be imported from files or from inline .env values using escaped \n. During generation the input is normalized to Unix line endings, and GPG-key entries receive a trailing newline automatically so the typed output preserves the expected armor format.
Development Guide
Modifying Application Logic
- Edit
ESP-ProMicro-HidKey.inofor behavior changes - Recompile and upload:
./build.sh - No
.envchanges required for logic modifications
Changing Passwords
No code changes needed:
- Update
.envwith new credentials or secret files - Run
./build.shto regenerate encryption and upload
Build Process
The build.sh script orchestrates:
- Validates
.envconfiguration - Runs
generate_password_header.pyto encrypt configured secrets - Compiles Arduino sketch with
arduino-cli - Uploads firmware to connected device
Python Generator Script
The generate_password_header.py script:
- Reads and validates
.envconfiguration - Validates sequence format (0/1 only, max 20 presses)
- Generates cryptographically random IVs
- Encrypts each secret with AES-128-CBC
- Supports inline text, escaped multiline content, and file-backed GPG key input
- Marks entries with encryption flag
0x01 - Outputs C header file with embedded encrypted data
- Runs automatically during build
Limitations
| Limitation | Details |
|---|---|
| Sequence Length | Maximum 20 button presses per combination |
| Password Length | Maximum 63 characters (64-byte buffer) |
| Sequence Prefix Matching | If one sequence is a prefix of another (e.g., "0,1" and "0,1,0"), the shorter one triggers first |
| Hardware Security | ATmega32U4 lacks secure boot, flash protection, or trusted execution environment |
| Blocking LED | LED animations block button input during display |
| Button Debouncing | Basic software-only debouncing; no hardware filtering |
| Microcontroller Scope | Designed for ATmega32U4; modifications needed for other platforms |
Known Issues and Roadmap
Potential Improvements
- Non-blocking LED state machine (allows button input during animations)
- Hardware button debouncing circuit
- Multiple button support
- Exponential backoff for brute-force attempts
- Display integration for visual feedback
- EEPROM wear leveling for counter persistence
- Modular code structure (separate compilation units)
- Alternative input methods (capacitive touch, etc.)
Support and Contributions
Reporting Issues
Please provide:
- Device specifications (Arduino Pro Micro variant, USB interface)
- Exact error messages and output
.envconfiguration (without sensitive passwords)- Operating system and relevant version numbers
- Steps to reproduce the issue
Contributing
Contributions are welcome. Please:
- Test thoroughly on actual hardware before submitting
- Follow existing code style and conventions
- Update documentation and comments
- Consider security implications of changes
- Add tests or validation where applicable
License
[Specify your project license here]
Disclaimer
This project is provided as-is for educational and personal use. The authors and contributors assume no responsibility for:
- Security breaches or data exposure
- Device malfunction or hardware damage
- Loss of access to credentials or systems
- Misuse of the device
Use at your own risk. This device is not suitable for protecting critical authentication credentials or high-security applications. Treat a compromised or stolen device as fully compromised.
Security References
For more information about cryptographic implementations and security practices:
- NIST SP 800-38A: Block Cipher Modes (AES-CBC)
See VULNERABILITY_ANALYSIS.md for the threat model and ranked findings, and CODE_REVIEW_2026.md for the review notes.
