# 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 `--interval` for loop rate control, `--out` for writing raw bytes to a FIFO, mutual exclusion of `--loop`/`--single`, and bit-multiple validation. ### Health checks - Added `health.py` with **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-health` disables the checks for debugging. ### Cleanup - Removed `entropy-rng-cam.py` and `entropy-rng-opencv.py`, which were superseded by `entropy.py` and carried the same unused-entropy bug. - Removed the empty 0-byte `file`. - Added `requirements.txt` with 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 `Dockerfile` for 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-Key` header, controlled by `ENTROPY_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.md` to match the new pipeline and commands. --- ## How It Works 1. **Capture frame** — fetch a single MJPEG frame from the camera via OpenCV. 2. **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. 3. **Extract entropy** — the frame is SHA-256 hashed to a 32-byte digest. 4. **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. 5. **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: ```bash sudo apt-get install libgl1 libglib2.0-0 ``` ### Install Python dependencies ```bash 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 `rngd` installed (`rng-tools` package). - `sudo` privileges to run `rngd`. ### For container deployment - Docker (or any OCI-compatible runtime) for local containers. - Kubernetes for orchestrated deployment (optional). --- ## Local Usage ### Single number ```bash python3 entropy.py --bit 256 ``` ### Loop, writing raw bytes to a FIFO for `rngd` ```bash 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 ```bash 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 ```bash 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 `nodeSelector` or a labeled node), or expose the camera via a `Service`. - Store `ENTROPY_API_KEY` in a `Secret` and mount as an env var. - Use an `Ingress` or `Service` for 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`: ```bash 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 ```bash 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 `ent` or `rngtest` on a captured sample.