Replace README.md with a single comprehensive doc. Add a "What Changed" section mapping each improvement to its commit (DRBG fix, health checks, cleanup, HTTP service, auth, tests, docs). Expand Requirements into software, system libraries, hardware, kernel-RNG, and container subsections. Keep the existing usage, HTTP service, container, remote consumption, and tests sections. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Entropy-RNG: Camera-Based Random Number Generator
This project uses a live video feed from a network camera (e.g. an Axis M1013) to generate cryptographically secure random numbers. The camera is pointed at a high-contrast, ever-changing scene (e.g. ceiling and lamps) so the pixel data carries genuine entropy. The output can be used locally (feeding the kernel RNG via rngd) or served over HTTP so remote systems can pull entropy to seed their own pools.
What Changed
This section records the rewrite from the original scripts to the current pipeline. Each bullet maps to a commit in git log.
Core RNG fix (the important one)
The original code computed a SHA-256 of the camera frame into a variable named entropy and then discarded it, drawing instead from the OS CSPRNG via secrets.randbits(). The camera contributed nothing — the output was just relabeled /dev/urandom.
- Replaced with a NIST SP 800-90A HMAC-DRBG (SHA-256) seeded from the frame hash. The camera now genuinely contributes entropy.
- The DRBG is reseeded every frame, so output changes even when camera noise is modest.
- Added
--intervalfor loop rate control,--outfor writing raw bytes to a FIFO, mutual exclusion of--loop/--single, and bit-multiple validation.
Health checks
- Added
health.pywith FIPS 140-2 continuous tests (monobit, runs, long-run) on the raw JPEG bytes before hashing. - A frozen, all-identical, or degraded frame is rejected and never seeds the DRBG.
- Runs thresholds are doubled because we count 0-runs and 1-runs together.
--no-healthdisables the checks for debugging.
Cleanup
- Removed
entropy-rng-cam.pyandentropy-rng-opencv.py, which were superseded byentropy.pyand carried the same unused-entropy bug. - Removed the empty 0-byte
file. - Added
requirements.txtwith pinned dependencies.
HTTP service + container deployment
- Added
api.py— a FastAPI app that runs the camera producer in a background thread and serves the latest entropy blob over HTTP. - Added
Dockerfilefor container builds, with a healthcheck against/healthz. - All config is via environment variables for containers.
Auth (simple by design)
- Single shared secret via
X-API-Keyheader, controlled byENTROPY_API_KEY. - Empty key means no auth, so you can start open and lock down by setting one env var.
- The API layer is where HMAC-signed blobs or mTLS plug in later — no architecture change needed.
Tests
- Added
test_entropy.py— 16 tests covering the DRBG, health checks, a bit-balance smoke test, and API auth gating. All pass without a camera (the producer is stubbed).
Docs
- Rewrote this README and
notes.mdto match the new pipeline and commands.
How It Works
- Capture frame — fetch a single MJPEG frame from the camera via OpenCV.
- Health check — the raw JPEG bytes are run through FIPS 140-2 continuous tests (monobit, runs, long-run). A frozen or degraded frame is rejected before use.
- Extract entropy — the frame is SHA-256 hashed to a 32-byte digest.
- DRBG — the digest seeds a NIST SP 800-90A HMAC-DRBG (SHA-256), which generates the output bytes. The camera genuinely contributes entropy; the output is not just relabeled OS randomness.
- Emit — hex to stdout, or raw bytes to a file/FIFO (
--out).
Requirements
Software
- Python 3.10+
- Python packages:
opencv-python,fastapi,uvicorn,pytest
System libraries (Debian/Ubuntu)
OpenCV needs runtime libraries that are not pip-installed:
sudo apt-get install libgl1 libglib2.0-0
Install Python dependencies
pip install -r requirements.txt
Hardware
- An IP webcam serving an MJPEG stream over HTTP (e.g. Axis M1013 at
http://192.168.0.200/mjpg/video.mjpg). - The camera must be pointed at a dynamic, high-contrast scene. A static frame will fail health checks and be rejected.
For the kernel-RNG flow
- Linux with
rngdinstalled (rng-toolspackage). sudoprivileges to runrngd.
For container deployment
- Docker (or any OCI-compatible runtime) for local containers.
- Kubernetes for orchestrated deployment (optional).
Local Usage
Single number
python3 entropy.py --bit 256
Loop, writing raw bytes to a FIFO for rngd
mkfifo /tmp/entropy.fifo
python3 entropy.py --loop --bit 512 --out /tmp/entropy.fifo &
sudo rngd -f -W 90% -x rdrand -x jitter -x pkcs11 -x rtlsdr \
-O namedpipe:path:/tmp/entropy.fifo -O namedpipe:timeout:2
Flags
| Flag | Default | Description |
|---|---|---|
--url |
http://192.168.0.200/mjpg/video.mjpg |
Camera MJPEG stream URL |
--bit |
256 |
Bit length of each random number (multiple of 8) |
--single N |
1 |
Generate N numbers and exit |
--loop |
off | Run forever |
--interval |
1.0 |
Seconds between iterations in --loop |
--out PATH |
stdout | Append raw bytes to a file/FIFO instead of printing hex |
--no-health |
off | Disable FIPS 140-2 health checks |
--loop and --single are mutually exclusive.
HTTP Service (for remote systems)
api.py runs the camera producer in a background thread and serves the latest entropy blob over HTTP, so other systems can pull entropy to seed their own pools.
Run locally
uvicorn api:app --host 0.0.0.0 --port 8000
Endpoints
| Method | Path | Auth | Returns |
|---|---|---|---|
| GET | / |
none | service info |
| GET | /healthz |
none | {status: ok} (liveness probe) |
| GET | /entropy |
X-API-Key if configured |
{hex, bits, ts} |
Configuration (env vars)
| Var | Default | Description |
|---|---|---|
ENTROPY_CAMERA_URL |
http://192.168.0.200/mjpg/video.mjpg |
Camera MJPEG URL |
ENTROPY_BITS |
256 |
Bits per blob |
ENTROPY_INTERVAL |
1.0 |
Seconds between frames |
ENTROPY_NO_HEALTH |
0 |
1 disables health checks |
ENTROPY_API_KEY |
empty | Shared secret for /entropy; empty = no auth |
Container Deployment
Build and run
docker build -t entropy-rng .
docker run -d -p 8000:8000 \
-e ENTROPY_CAMERA_URL=http://192.168.0.200/mjpg/video.mjpg \
-e ENTROPY_API_KEY=changeme \
entropy-rng
The container has a healthcheck against /healthz. The camera must be reachable from the container's network.
Kubernetes notes
- Replicas: 1. Multiple pods hitting the same camera produce correlated output. Pin to a single replica.
- Camera reachability is the real constraint. Schedule the pod on a node that can reach the camera (use
nodeSelectoror a labeled node), or expose the camera via aService. - Store
ENTROPY_API_KEYin aSecretand mount as an env var. - Use an
IngressorServicefor TLS termination in front of uvicorn.
Consuming Remotely (seed another system's pool)
A remote Linux box can pull a blob and feed its kernel RNG via rngd:
curl -s -H "X-API-Key: changeme" https://entropy-svc.internal/entropy \
| python3 -c "import sys,json; print(json.load(sys.stdin)['hex'])" \
| xxd -r -p > /tmp/entropy.fifo
sudo rngd -f -W 90% -x rdrand -x jitter -x pkcs11 -x rtlsdr \
-O namedpipe:path:/tmp/entropy.fifo -O namedpipe:timeout:2
Tests
pytest -q
Covers the HMAC-DRBG (determinism, reseed, length, reseed limit), health checks (random passes, frozen/all-ones/short/long-run rejected), a bit-balance smoke test, and the API endpoints including auth gating. Tests stub the camera, so they run without hardware.
Notes
- Point the camera at a dynamic scene. A static frame will fail health checks and be rejected.
- The DRBG is reseeded from each frame, so output changes even if the camera noise is modest.
- Auth is a single shared secret for now. When you need per-client keys, rotate to HMAC-signed blobs or mTLS — the API layer is where that plugs in.
- The 256-bits-per-frame entropy credit is conservative. If you want a defensible number for your specific camera, run
entorrngteston a captured sample.