Random number generation is difficult, and by definition, computers are deterministic. Anything "random" is derived from real values using an algorithm, and as such, the security of these numbers depends on seeding them with data an attacker can't predict. A computer needs data to base the creation of something "random" off of, and if you can predict the original data it uses, it's not random anymore. Modern operating systems include well-vetted entropy collectors and cryptographically secure pseudorandom number generators, otherwise known as CSPRNGs; for most use cases, that's enough.
However, adding extra unpredictable data from the physical world is a way to take it one step further. It's why you've probably heard of unique solutions like Cloudflare’s wall of lava lamps, which the company photographs continuously to create a stream of high‑entropy bytes for its TLS key generation. The reasoning for it is simple: dynamic natural scenes produce highly variable pixel data, even low-level sensor noise adds unpredictability, and periodic snapshots can be hashed and mixed into an entropy pool. Inspired by that idea, I built my own alternative using Frigate, an open‑source NVR, and an inexpensive Wi‑Fi camera.
To do this the right way, you would need to pull raw image data from your camera, and also have a source of entropy right in front of it. I live in Ireland, a very windy country, and my outdoor CCTV camera is pointed at a group of trees that are blowing and moving a lot, which is my source of entropy. If you have a camera connected to a Linux computer, you can grab raw data using a tool like fswebcam. This project is a proof of concept rather than a true security solution, and should be treated as such. This is not a drop-in replacement for your OS RNG.
Why entropy matters, and how Frigate fits in
It's all about randomness
In everyday language, entropy is a measure of disorder. In cryptography, entropy is better thought of as unpredictability. The more entropy your random source has, the harder it is for an attacker to detect patterns, and the stronger your keys, IVs, nonces, and one‑time pads will be. Most operating systems already maintain a kernel entropy pool, and you can even see this on Linux by running the following command:
head -c 32 /dev/urandom | hexdump -C
Note that /dev/urandom, when read during early boot time, can return data before the system entropy pool has been initailized. As a result, getRandom or /dev/random can be used instead, though /dev/urandom is the better choice.
With that said, sometimes you need application‑specific randomness. One example would be a hardware wallet that always remains deterministic across power cycles. Plus, real-world data will always trump whatever your system generates as entropy, and that's where a dedicated entropy feeder can help. There are already other examples of entropy generators out there that you can use, like random.org's true randomness based on atmospheric noise.
If you've ever heard of the concept of RNG manipulation, common in many games and frequently a component of speedrun attempts in many titles, the same concept applies to any level of randomness on a computer. While RNG manipulation may usually be harmless in a game, that isn't so much the case when dealing with actual security. Having said that, games (especially older titles) would have used simpler, more predictable algorithms for their "random" events, and RNG manipulation is a common tactic in games like Pokémon to catch shiny Pokémon or encounter Pokémon with better statistics.
What I've created is, essentially, a middle ground between something over-the-top like Cloudflare's wall of lava lamps and not having any entropy at all. I already have Frigate configured with a couple of cameras, so it's fairly easy to make use of it and its API to generate my own keys if I need them. Most people don't ever need to roll this level of secure cryptographic key generation, but it's an exercise to demonstrate how it can be done and use it as a tool to explain why it's necessary in some contexts along the way.
As for why we use Frigate, the answer is that it has a great API that can be accessed by anyone with authorization. It's easy to use, it's a commonly deployed piece of software, and the killer API feature for this project is its ability to grab a snapshot of the latest frame captured by a specified camera. It's located at:
https://(IP):5000/api/(camera_name)/latest.jpg
The beauty of this is that every frame will be different, even if, to the naked eye, the image is unchanged. Lighting, movement, and other aspects of the photo make it so that the data won't likely ever truly be the same, and that matters for entropic reasons. A stream of input bytes should never be exactly the same as another, so we can combine that with our own system's calculated entropy through a key derivation function and build a unique "seed" for our cryptographic generation. With that said, while I do use JPG for this project, JPG is inadvisable for a true production-ready implementation. It has a data block that's predictable and stays quite similar to the header, and compression will also introduce areas of the image that are very similar to the same area in other images. You're better off using raw frames or, at the very least, lossless formats like PNG and hashing the uncompressed pixel data.
As a result, this project is a proof of concept and should be treated as such.
Bringing it all together
Fetching from Frigate and calculating hashes
To build this project completely, you'll need a Frigate instance with a camera connected to it, and you'll need to have Python with the cryptography and requests libraries installed. We'll make a Python function to make a request to our image source and then return it as a stream of bytes. This data stream can then be hashed with our system's own pseudorandom generation or used by itself for seed generation. Here is the Python code that derives those values:
# Build 512-bit seed from frame and 256 bits OS entropy
seed = hashlib.sha512(frame + os.urandom(32)).digest()
# Fresh HKDF object each iteration
aes_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"frigate-rng",
backend=default_backend(),
).derive(seed)
# HMAC-SHA256 demo
h = hmac.HMAC(aes_key, hashes.SHA256(), backend=default_backend())
h.update(b"hello xda")
tag = h.finalize()
# Printable 64-char token
token = token_from_seed(seed)
def token_from_seed(seed: bytes, length: int = 64, symbols: bool = False) -> str:
alphabet = string.ascii_letters + string.digits
if symbols:
alphabet += string.punctuation
# HKDF just to expand/compress the seed into exactly
At this point, we have everything we need: a camera snapshot that changes every second and a bit of system entropy. We combine those two byte‑streams into a single 512‑bit seed and immediately derive three artefacts:
- An AES-256 key: 32-bytes long, suitable for schemes such as AES-GCM or AES-SIV
- HMAC-SHA256 tag: Message authentication with a fresh key
- A 64-character length token comprised of uppercase, lowercase, and number characters that can be used for passwords or applications requiring a token for local storage
However, just because it should be random doesn't mean that it actually is, so I put the algorithm to the test. I sent thousands and thousands of requests to my Frigate server, checking if an image was unique. If it was, I pulled it, hashed it with the pseudorandom key generated by the system, and then saved the outgoing seed value to an output file. A more appropriate solution for this would be to actively hash each image and check for collisions, but for this usage, it works just fine. I used two of my cameras in Frigate for this to speed things up. By doing this repeatedly, we can generate a lot of different results, and then test the final concatenation of every seed using a tool such as Dieharder.
Dieharder is a free tool that's built to test random number generators, but the idea is that it's supposed to take in gigabytes of data and identify if there are any patterns. When you give it a limited sample size, it will "rewind" the file a number of times, and this will significantly impact the test results. I didn't give it anywhere close to the amount of data it would typically need for a true test, yet the results were still pretty decent. For contrast, some of its largest tests (like rgb_lagged_sum) require a minimum of 13.5 GB of data. For a quick sanity check, however, the earlier tests are quite useful and give us an indication of whether we're on the right track. I collected roughly 11 MB of data, and still saw some very promising results.
Passing most of these early, low-volume tests doesn't prove the stream is perfect, but it's a good first signal before you commit hours to collecting multi-gigabyte samples for the full battery. Any good result here would indicate (to be clear, not confirm) that our algorithm is working, though this is a preliminary sanity check and not a guarantee. For context, given that we are using a known random value that passes Dieharder's tests (/dev/urandom on my Linux VM) for hashing, it increases the probability of passing these tests. Including /dev/urandom in the mix means that Dieharder is validating the kernel's pseudo-random number generator, plus whatever extra entropy is added by the camera.
I then took things a bit further; I removed /dev/urandom from the equation entirely, where I hashed the entire frame by itself, cutting out the OS-level entropy, and calculated a 256-bit AES key from it. I collected 22MB of seeds (which took several hours), and the results were really impressive. Given that, again, a much larger file is needed for a true test of many of these algorithms, these pass values are a pretty good indication that we're on the right track for cryptograhically secure number generation.
Can this be used for cryptograhically secure key generation? Realistically, you're better off just using /dev/urandom. The amount of work required to ensure the veracity of this algorithm and its safety isn't worth it for the miniscule amount of benefit it would give. However, the deployment of tools like these isn't some crazy, unrealistic thing to do. As we've already seen Cloudflare has similar, and there's a reason for that. In the right context, it can technically be more secure, and that's what matters the most when it comes to an enterprise environment.
It's worth keeping mind that I took a number of shortcuts in this project as well, like using JPG instead of PNG, and not using raw inputs. Yet, even then, the results were impressive. It's totally viable with a bit more work, meaning you too could theoretically make your own wall of lava lamps. Just like Cloudflare.
