A multi-password HID keyboard emulator for Arduino Pro Micro Leonardo (ATmega32U4) that triggers different passwords via USB keyboard emulation using button press sequences.
This project implements a secure, sequence-based password manager on microcontroller hardware. Different combinations of short and long button presses unlock and transmit distinct passwords through USB keyboard emulation, protected by a two-stage encryption system.
- Sequence-Based Activation: Multiple password combinations triggered by button press patterns
- Short press (< 500ms) =
0| Long press (≥ 500ms) =1
- Short press (< 500ms) =
- Two-Stage Encryption Architecture:
- Stage 1 (Build-Time): AES-128-CBC with per-password random IV (firmware storage)
- Stage 2 (First-Boot): ChaCha20 per-device re-encryption using HKDF-SHA256 derived keys (EEPROM storage)
- Persistent Brute-Force Protection: Survives power cycles and device resets
- Memory Security: Multi-pass buffer clearing 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: Passwords never appear in source code or version control
| 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.
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
- Python 3.6+ with
pycryptodomexlibrary - arduino-cli (for building and flashing)
- SparkFun AVR board support (installed via arduino-cli)
1. Install Python Dependencies (Debian/Ubuntu/Kali):
sudo apt install python3-pycryptodomexOr via pip:
pip3 install pycryptodomex --user2. 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-HidKey1. 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
# Password combinations
COMBINATION_COUNT=3
COMBINATION_0_SEQUENCE="0,0,1,0"
COMBINATION_0_PASSWORD="admin123"
COMBINATION_1_SEQUENCE="1,0,0"
COMBINATION_1_PASSWORD="user456"
COMBINATION_2_SEQUENCE="0,1,1,0,1"
COMBINATION_2_PASSWORD="password789"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
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
Default Port: /dev/ttyACM0
Custom Port Example:
./build.sh /dev/ttyACM1🔁 Optional: Reset EEPROM before uploading
If you need to force the device to re-run the Stage-2 re-encryption (for example after fixing padding or updating encryption logic), the build script provides a helper flag --reset-eeprom (short -r). This compiles and uploads a small helper sketch that clears the re-encryption flag in EEPROM, waits briefly, and then uploads the main sketch.
Examples:
# Reset EEPROM and upload (default port /dev/ttyACM0)
./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 ensures stage-2 data is rebuilt on next boot.
- Use with caution if you rely on existing device-specific stage-2 data.
Upload Troubleshooting: If upload fails, enter bootloader mode:
- Press the reset button on Pro Micro twice quickly
- Run
./build.shimmediately (within 8 seconds)
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 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 (stage-2 encryption initialized) |
This project implements a two-stage encryption system to minimize attack surface while providing device-unique protection for each deployed instance.
Executed during the build process to protect passwords before firmware deployment:
- Passwords encrypted with AES-128-CBC (PKCS#7 padding) using the master key from
.env - A cryptographically random 16-byte IV generated per password and prepended to ciphertext
- Encrypted data marked with flag
0x01and embedded in firmware (embedded_passwords.h) - Master key stored in PROGMEM (flash memory)
Executed on device first power-on to create device-specific encryption:
- Device generates unique 16-byte Device ID (stored in EEPROM) using improved entropy sampling
- Device-specific 32-byte key derived via HKDF-SHA256 from
(Master Key || Device ID) - Stage-1 entries are:
- Decrypted using AES-CBC with stored IV and master key
- Re-encrypted using ChaCha20 with cryptographically random 12-byte nonce
- Marked with flag
0x02and stored in EEPROM
- Derived key kept only in RAM and cleared after re-encryption
| Aspect | Benefit |
|---|---|
| Device-Unique Protection | Each device holds independent stage-2 ciphertexts even with identical firmware |
| Cryptographic Strength | CBC prevents block-pattern leakage; ChaCha20 avoids XOR-based weaknesses |
| Key Derivation | HKDF-SHA256 replaces insecure XOR operations with standard KDF |
| Persistent Storage | Plaintext only briefly in RAM during processing, immediately cleared |
- Passwords stored encrypted in flash (stage-1) and EEPROM (stage-2)
- Plaintext only decrypted into RAM during keyboard transmission
- RAM buffers cleared via 3-pass overwrite (0xFF → 0xAA → 0x00)
- AES and ChaCha20 contexts zeroed after use
- Plaintext passwords never in source code or version control
- Failed attempt counter stored in EEPROM
- Survives power cycles and device resets
- Threshold: Maximum 5 failed attempts
- Lockout duration: 30 seconds
- Counter only resets on successful password entry
- No bypass via simple reset
- ✅ Firmware extraction alone (stage-2 requires EEPROM)
- ✅ USB replay attacks via keyboard sniffing
- ✅ Timing attacks on sequence matching
- ✅ Brute-force bypass via device reset
- ✅ RAM recovery from powered-off device
- ❌ Combined flash + EEPROM physical extraction
- ❌ Sophisticated hardware attacks (power analysis, fault injection)
- ❌ Physical coercion
This device relies on:
- Physical Security: Secure storage/location
- Knowledge Factor: Memorized button sequences
- Possession Factor: Device ownership
- No secure boot mechanism
- No flash/EEPROM read protection
- No trusted execution environment
- Side-channel attacks (power analysis, timing) possible with specialized equipment
Recommendations:
- Use only in physically secure environments
- Treat a stolen device as fully compromised
- Do not use for critical high-security applications
- Consider device as two-factor authentication complement (possession + sequence knowledge)
- For critical applications: implement additional tamper detection or destruction mechanisms
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)
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)
Solutions by Platform:
- Linux: Usually
/dev/ttyACM0or/dev/ttyACM1 - 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/nullSolution:
cp .env.example .env
# Edit .env with your settings
nano .envESP-ProMicro-HidKey/
├── ESP-ProMicro-HidKey.ino # Main Arduino sketch
├── aes.h # AES-128-CBC implementation
├── chacha20.h # ChaCha20 stream cipher
├── sha256.h # SHA-256 hash function
├── embedded_passwords.h # Auto-generated encrypted passwords
├── embedded_passwords_eeprom.h # Auto-generated stage-2 encryption data
├── generate_password_header.py # Password encryption generator
├── build.sh # Build and flash automation script
├── build_config.h # Compile-time configuration
├── .env # Your password configuration (gitignored)
├── .env.example # Example configuration template
├── .gitignore # Git ignore rules
├── README.md # This file
├── SECURITY_UPGRADE.md # Security implementation details
└── LICENSE # Project license
| File | Purpose |
|---|---|
ESP-ProMicro-HidKey.ino |
Main application logic, sequence matching, LED control |
aes.h |
Stage-1 AES-128-CBC encryption/decryption |
chacha20.h |
Stage-2 ChaCha20 stream cipher |
sha256.h |
HKDF-SHA256 key derivation |
generate_password_header.py |
Reads .env, encrypts passwords, generates headers |
build.sh |
Orchestrates build process, password generation, upload |
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 sequenceChange hardware pins in ESP-ProMicro-HidKey.ino:
#define LED_PIN 10 // LED output pin
#define BUTTON_PIN 9 // Button input pinWithout recompilation:
- Update
.env: incrementCOMBINATION_COUNT - Add new entries:
COMBINATION_3_SEQUENCE="1,1,0" COMBINATION_3_PASSWORD="newpassword123"
- Run
./build.shto regenerate headers and upload
- Flash: ~8-10KB (sketch + stage-1 encrypted passwords)
- SRAM: ~400 bytes (runtime buffers, depends on password count)
- EEPROM: ~500 bytes (device ID + stage-2 encrypted passwords + brute-force counter)
- ATmega32U4 Specs: 32KB flash, 2.5KB SRAM, 1KB EEPROM
Stage 1 (Flash, included in firmware):
[Flag: 0x01] [IV: 16 bytes] [AES-CBC ciphertext: variable length]
Stage 2 (EEPROM, device-specific):
[Flag: 0x02] [Nonce: 12 bytes] [ChaCha20 ciphertext: variable length]
Runtime Processing:
- Only active password decrypted into SRAM
- Plaintext transmitted to USB
- Buffer immediately cleared via 3-pass overwrite
Uses constant-time bitwise XOR accumulation to prevent timing attacks that could leak partial sequence matches through execution time measurements.
HKDF-SHA256 derives device-specific encryption key:
PRK = HMAC-SHA256(salt=0x00x00..., IKM = MasterKey || DeviceID)
OKM = HMAC-SHA256(PRK, info="ESP-ProMicro-HidKey" || 0x01, length=32)
Result: 32-byte device-unique key (kept in RAM, cleared after use)
- Edit
ESP-ProMicro-HidKey.inofor behavior changes - Recompile and upload:
./build.sh - No
.envchanges required for logic modifications
No code changes needed:
- Update
.envwith new credentials - Run
./build.shto regenerate encryption and upload - Device re-encrypts stage-1 passwords on next boot
The build.sh script orchestrates:
- Validates
.envconfiguration - Runs
generate_password_header.pyto encrypt stage-1 passwords - Compiles Arduino sketch with
arduino-cli - Uploads firmware to connected device
- Device performs stage-2 re-encryption on first boot
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 password with AES-128-CBC
- Marks entries with stage-1 flag (0x01)
- Outputs C header file with embedded encrypted data
- Runs automatically during build
| 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 |
- 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.)
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
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
[Specify your project license here]
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.
For more information about cryptographic implementations and security practices:
- NIST SP 800-38A: Block Cipher Modes (AES-CBC)
- ChaCha20 and Poly1305 (IETF RFC 7539)
- HKDF: A Simpler Approach to Key Derivation (RFC 5869)
See SECURITY_UPGRADE.md for detailed encryption architecture documentation.
