Building a Hardware True Random Number Generator with RP2040 for Under $5
Learn how to construct a low-cost, hardware-based True Random Number Generator (TRNG) using an RP2040 microcontroller. This project, named EMPHarvest, utilizes electromagnetic noise, microsecond timer jitter, and photon detection to generate high-quality entropy, suitable for cryptographic and statistical applications, all for under $5.
The article details how to build a low-cost, hardware-based True Random Number Generator (TRNG) using an RP2040 microcontroller. This project, named EMPHarvest, leverages readily available components to create a robust source of randomness for under $5. The generated entropy has demonstrated strong statistical properties, passing FIPS 140-2 tests with 1998 successes out of 1999, along with several Diehard tests (birthday and rank32x32), and partially passing operm5 with a 5MB sample. It achieves an average output rate of 18 KiB/s.
The core principle involves harvesting entropy from multiple independent physical sources:
- Two wires to capture electromagnetic (EM) noise.
- A diode to detect photon noise.
- The microcontroller's internal jitter.
Requirements:
- An RP2040-based board (e.g., Raspberry Pi Pico)
- Any copper wire (approx. 10-20cm)
- An LED diode (any color, transparent or red works best)
The project repository, containing instructions and tools, is available at: https://github.com/tcsenpai/EMPHarvest
How It Works
EMPHarvest combines multiple independent physical entropy sources:
Electromagnetic Antennas (E)
Two copper wires (10-20cm) connected to ADC pins function as broadband antennas. They capture ambient electromagnetic radiation from sources like WiFi (2.4GHz), Bluetooth, cellular networks, the 50/60Hz hum from power lines, harmonics from switching power supplies, and thermal noise from nearby electronics.
The ADC samples voltage on these wires at 12-bit resolution. While higher bits (8-11) reflect the quasi-DC average voltage, the lower bits (0-3) contain the crucial noise – fluctuations too fast and small to be coherent signals. This represents pure chaos, a sum of thousands of uncorrelated sources. XORing two independent noisy sources helps reduce bias.
Microsecond Timer Jitter (M)
The internal microsecond timer, though appearing deterministic, exhibits slight imperfections. Hardware interrupts, DMA transfers, clock domain crossings, and oscillator instability introduce jitter at the microsecond level. While not a high-entropy source on its own, it's a free and uncorrelated component, with each micros() call influenced by the system's exact state.
Photon Detection via LED (P)
An LED is a P-N junction. When forward-biased, it emits photons. However, it also works in reverse: when photons strike the junction, they generate electron-hole pairs through the photoelectric effect. By reading the digital state of the LED pin, the circuit captures variations based on the amount of light hitting the junction at that microsecond. Ambient light fluctuations, reflections, and imperceptible 50/60Hz flicker from artificial lighting all contribute to randomness.
Mixing and Whitening
Raw entropy from all sources is XOR-mixed. XORing independent bits tends to normalize the distribution towards a 50/50 ratio, even if individual sources are biased.
The mixed entropy then feeds into SHA-256 for whitening. SHA-256, a cryptographic hash function, diffuses every input bit across all output bits. A single bit change in the input typically flips approximately 50% of the output bits unpredictably. A 4:1 ratio is used (128 bytes of raw entropy producing 32 bytes of output) to ensure full entropy in the output, even if the raw entropy per byte is low.
Hardware Requirements
- Any RP2040-based board (e.g., Raspberry Pi Pico, Waveshare RP2040 Zero)
- Two pieces of wire (10-20cm copper wire or jumper wires)
- One LED (any color; transparent or red works best)
- USB cable
Wiring
Antenna 1 (10-20cm wire) ──── GP26 (ADC0)
Antenna 2 (10-20cm wire) ──── GP27 (ADC1)
LED Anode (long leg) ──── GP2
LED Cathode (short leg) ──── GP3
No resistors are needed. Antenna pins must be floating (no pull-up/pull-down). The LED is used as a sensor, not an emitter, and will not visibly light up.
Software Setup
Arduino IDE
- Add RP2040 board support: Go to
File > Preferences > Additional Boards Manager URLsand add:https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json - Install the board package: Go to
Tools > Board > Boards Manager, search for "Raspberry Pi Pico/RP2040" by Earle F. Philhower III, and install it. - Install SHA256 library: Go to
Sketch > Include Library > Manage Libraries, search for "Crypto" by Rhys Weatherley, and install it. - Select your board: Go to
Tools > Board > Raspberry Pi RP2040 Boardsand select your board. - Upload the firmware: Hold the
BOOTbutton on your board, connect USB, releaseBOOT, then clickUpload.
Python Client
Install the pyserial library:
pip install pyserial
Usage
Finding Your Serial Port
The device will appear as a serial port:
- Linux:
/dev/ttyACM0,/dev/ttyACM1, etc. - macOS:
/dev/cu.usbmodem* - Windows:
COM3,COM4, etc.
On Linux, you may need to add your user to the dialout group:
sudo usermod -aG dialout $USER
Log out and back in for this to take effect.
Serial Port Configuration
On Linux, if data streaming issues occur, configure the port:
stty -F /dev/ttyACM0 115200 raw -echo -hupcl
Command Line Interface
# Generate 32 random bytes (hex)
./trng.py random 32
# Generate raw bytes to file
./trng.py raw 1000000 > random.bin
# Generate a random integer (64-bit default)
./trng.py int
# Generate a 256-bit integer
./trng.py int 256
# Generate a float between 0 and 1
./trng.py float
# Generate a number in a range
./trng.py range 1 100
# Roll a dice
./trng.py dice 6
./trng.py dice 20
# Flip a coin
./trng.py coin
# Continuous stream
./trng.py stream
Specifying the Port:
./trng.py --port /dev/ttyACM1 random 32
./trng.py -p COM3 random 32
Python Library
from trng import TRNG
trng = TRNG("/dev/ttyACM0")
# Get random bytes
data = trng.read(32)
# Get hex string
hex_string = trng.read_hex(32)
# Get random integer
number = trng.read_int(128)
# Get float [0, 1)
f = trng.read_float()
# Get number in range
n = trng.read_range(1, 100)
trng.close()
Testing
Prerequisites
# Debian/Ubuntu
sudo apt install rng-tools dieharder
# Fedora
sudo dnf install rng-tools dieharder
Run Tests
Navigate to the scripts directory in the repository.
# Default 5MB test
./run_tests.sh
# Custom size (in bytes)
./run_tests.sh 20000000
Expected Results
- FIPS 140-2: Should pass over 99% of tests.
- Dieharder: Most tests pass. Some tests (e.g.,
operm5,rank_32x32) may show "WEAK" with small sample sizes; larger samples (20MB+) generally improve results.
Performance
- Throughput: Approximately 18 KB/s
- Time to generate 1MB: Approximately 57 seconds
- Time to generate 5MB: Approximately 5 minutes
Troubleshooting
Serial Port Permission Denied
sudo chmod 666 /dev/ttyACM0
Or, for a permanent solution:
sudo usermod -aG dialout $USER
Log out and back in for this change to take effect.
Device Stops Streaming
Reset the serial port configuration:
stty -F /dev/ttyACM0 115200 raw -echo -hupcl
Alternatively, kill any processes currently holding the port:
sudo fuser -k /dev/ttyACM0
Board Not Detected
- Try a different USB cable (some are power-only).
- Try a different USB port (avoid hubs).
- Hold the
BOOTbutton, connect USB, then releaseBOOTto enter bootloader mode.
The Physics
Electromagnetic Noise
The antennas act as receivers for all electromagnetic radiation in their environment. Maxwell's equations describe how changing electric and magnetic fields propagate as waves. Every electronic device, power line, and radio transmitter contributes to the electromagnetic "soup" that the antennas sample.
The ADC converts this analog chaos into digital values. At 12-bit resolution, the least significant bits represent voltage changes of less than 1 millivolt – small enough that thermal noise within the ADC itself contributes significantly to randomness.
Photon Detection
When a photon with sufficient energy strikes the LED's semiconductor junction, it can excite an electron from the valence band to the conduction band, creating an electron-hole pair. This phenomenon is known as the photoelectric effect, explained by Einstein in 1905.
The rate of photon arrival follows Poisson statistics, which is inherently random. Even in seemingly constant lighting conditions, the exact number of photons hitting the junction within any given microsecond interval varies unpredictably.
Timing Jitter
The RP2040 operates at 125MHz from a crystal oscillator. However, no oscillator is perfect; thermal noise causes tiny frequency variations. Additionally, the precise timing of code execution depends on factors such as cache hits, bus arbitration, and interrupt servicing. These combined effects accumulate into microsecond-level unpredictability.
Why SHA-256
Even after mixing multiple entropy sources, subtle correlations might remain – patterns too weak for human detection but discernible by statistical tests. SHA-256 performs 64 rounds of bit mixing, with each round utilizing different constants and rotation amounts.
Its avalanche effect ensures that even slightly similar inputs produce completely different outputs. Two inputs differing by just one bit will result in outputs that differ by approximately 50% of their bits, with no predictable pattern.
System Integration (Linux)
You can configure your Linux system to automatically feed entropy from the TRNG device to the kernel's random pool using rngd. This requires installing a udev rule and a systemd service.
Finding Your Device's Vendor and Product ID
Important: The Vendor and Product IDs vary depending on your specific RP2040 board. The provided files typically use 2e8a:0003 (Raspberry Pi Pico), but your board may differ.
To find your device's IDs:
-
Connect your TRNG device via USB.
-
Run one of these commands:
# Method 1: lsusb lsusb | grep -i "pico\|rp2040\|raspberry" # Method 2: udevadm (more detailed) # First find your device (usually /dev/ttyACM0) udevadm info -a -n /dev/ttyACM0 | grep -E "idVendor|idProduct" # Method 3: Check dmesg after plugging in dmesg | tail -20Example output from
lsusb:Bus 001 Device 005: ID 2e8a:0003 Raspberry Pi Pico ^^^^:^^^^ Vendor:ProductCommon RP2040 board IDs:
| Board | Vendor ID | Product ID |
|---|---|---|
| Raspberry Pi Pico | 2e8a | 0003 |
| Waveshare RP2040-Zero | 2e8a | 0003 |
| Adafruit Feather RP2040 | 239a | 80f4 |
| Seeed XIAO RP2040 | 2e8a | 000a |
Installing the udev Rule
The udev rule creates a /dev/trng symlink when your device is plugged in and automatically starts the entropy service.
- Copy the rule file:
sudo cp udev_rules/99-trng.rules /etc/udev/rules.d/ - Edit the rule if your device has different IDs:
Replacesudo nano /etc/udev/rules.d/99-trng.rulesidVendorandidProductvalues with your device's IDs:ACTION=="add", SUBSYSTEM=="tty", ATTRS{idVendor}=="YOUR_VENDOR_ID", ATTRS{idProduct}=="YOUR_PRODUCT_ID", SYMLINK+="trng", TAG+="systemd", ENV{SYSTEMD_WANTS}="trng-entropy.service", RUN+="/bin/stty -F /dev/%k 115200 raw -echo" - Reload udev rules:
sudo udevadm control --reload-rules sudo udevadm trigger - Verify the symlink (after plugging in the device):
ls -la /dev/trng
Installing the Systemd Service
The service uses rngd to feed entropy from /dev/trng to the kernel's random pool.
- Install
rng-toolsif not already installed:# Debian/Ubuntu sudo apt install rng-tools # Fedora sudo dnf install rng-tools # Arch sudo pacman -S rng-tools - Copy the service file:
sudo cp service/trng-entropy.service /etc/systemd/system/ - Reload systemd:
sudo systemctl daemon-reload
The service starts automatically when the device is plugged in (via the udev rule). To check its status:
sudo systemctl status trng-entropy.service
To manually start/stop:
sudo systemctl start trng-entropy.service
sudo systemctl stop trng-entropy.service
Verifying the Setup
- Check that the device is recognized:
ls -la /dev/trng # Should show: /dev/trng -> ttyACM0 (or similar) - Check that the service is running:
sudo systemctl status trng-entropy.service
3. Monitor entropy being added:
```bash
# Watch the kernel entropy pool
watch cat /proc/sys/kernel/random/entropy_avail
# Check rngd activity
journalctl -u trng-entropy.service -f
```
## Uninstalling
To remove the system integration:
1. Stop and disable the service:
```bash
sudo systemctl stop trng-entropy.service
sudo systemctl disable trng-entropy.service
```
2. Remove files:
```bash
sudo rm /etc/systemd/system/trng-entropy.service
sudo rm /etc/udev/rules.d/99-trng.rules
```
3. Reload configurations:
```bash
sudo systemctl daemon-reload
sudo udevadm control --reload-rules
```
## Security Considerations
EMPHarvest is suitable for:
* Cryptographic key generation
* Nonces and initialization vectors
* Monte Carlo simulations
* Gaming and gambling applications
* Seeding pseudorandom number generators
For high-security applications, consider:
* Running the full `dieharder` test suite
* NIST SP 800-90B entropy assessment
* Combining with other entropy sources
## License
This project is released under the MIT License.