May 2026
The Futility of Lava Lamps: What Random Really Means
Cloudflare brags about using lava lamps to “help with internet encryption”. They have this impressive wall of lava lamps, one hundred of them, standing witnesses of their commitment to security, dutifully generating entropy to make the internet a safer place.
It’s not just one wall of lava lamps. They have double pendulums, wave motion (my personal favourite), mesmerising mobiles… different setups, same core principle: unpredictability before your very eyes, at a non-trivial cost. You can see how serious Cloudflare is about your safety.
That’s all marketing. Security theatre. Cloudflare stops shy of openly lying, but the impression they give that lava lamps significantly contributes to their security is false. They don’t. They’re worse in fact than more mundane alternatives, and Cloudflare almost certainly knows it.
To understand why though, we need to know what randomness actually is.
Probability is in the mind
Take a look at this program:
int getRandomNumber()
{
return 4; // chosen by fair dice roll.
// guaranteed to be random.
}Does this function return a random number? Most people would look at you funny if you asked them with a straight face: the thing returns 4 every time, fair dice roll or no, that’s obviously not random at all.
Now what if the only thing we knew about this function was the following:
// Return a constant number, chosen by fair dice roll.
int getRandomNumber();We call it once, and it returns 4. Was that random? Well, there are basically two ways to look at it:
If you see randomness as a property of the thing itself, you’d say of course not, that’s the same function that always return 4, you think I’m going to get fooled by your obvious gaslighting?
If you see randomness as a property of your knowledge of the thing, you’d say of course it was, my probability was uniformly distributed between integers from 1 to 6, there was no way I could have known it was 4, you think I’m stupid?
Which is the correct perspective is a matter of philosophy of science that should have been settled over a century ago, yet somehow the wrong one still permeates papers written today. I won’t get into that. What I want to stress, is which perspective is useful, for encryption.
One-time pad
Let’s say you’re playing Russian roulette.
There’s a 6-shot revolver on the table, with one bullet already inside. The opponent in front of you, and people watching you and shouting bets about when the gun will fire. To avoid an untimely death, you’ve decided to cheat: you have an accomplice who can know where the bullet is. They’ll just shout the number, and you’ll know exactly when to quit.
Problem is, your opponent has very good spies. He knows when you’re cheating, and how. Once he hears your accomplice, he too will know when to stop. To avoid that, you devised a strategy:
- Before the game, you get alone with your accomplice and throw a die.
- Your accomplice does their magic to learn where the bullet is.
- Your accomplice adds the position of the bullet and the die throw, subtracts 6 if the sum is 7 or more, then shouts the result.
- You subtract the die throw to whatever you hear, and add 6 if the result is zero or less. That’s where the bullet is.
When you threw the die this morning, the result was 4. Now the game begins, and your accomplice shouts “Three!”. You subtract 4 in your head, that’s -1. Add 6, the bullet is in the fifth chamber. The question is, can your opponent guess?
To answer that rigorously, we need some probability theory. Your opponent starts with the following prior information (note that i is an integer between 1 and 6):
- P(Ci), his prior probability that the bullet is in the chamber i.
- P(Di), his prior probability that the die rolled on an i.
- P(Si), his prior probability that your accomplice will shout i.
There’s exactly one bullet, so the sum of all P(Ci) is 1. The die is fair, so P(Di) is 1/6 for all i. P(Si) is the sum of all P(Cj ∧ Dk), such that i = j + k or i = j + k - 6. Since the die and the gun are independent, P(Cj ∧ Dk) = P(Cj) × P(Dk) = P(Cj) ÷ 6, and the sum is 1/6.
From there we can compute P(Ci|S3), the posterior probability of your opponent that the ith chamber is loaded, knowing that your accomplice has shouted 3. This is given by the Bayes formula:
P(Ci|S3) = P(Ci) × P(S3|Ci) ÷ P(S3)
P(Ci|S3) = P(Ci) × 1/6 ÷ 1/6
P(Ci|S3) = P(Ci)
Conclusion: the prior and posterior probabilities of your opponent are identical, he has learned absolutely nothing. The one-time pad works.
Reusing the one-time pad
You won. Your opponent gave up after the gun harmlessly clicked 4 times. With only two chambers left he didn’t like his odds. Instead he’s asking for a rematch.
You can’t throw the die again, so you make do with your first throw. Your accomplice shouts “Four!”: the bullet is in the last chamber. The die is still as random now as it was then, right? So your opponent has no way to know, right? Riight?
Wrong of course, but it’s not clear why at a first glance: our die roll was fair, the result was random. But that’s the wrong way to look at it: we don’t care about the intrinsic properties of the die roll. Not directly. What matters is who knows how much about the result.
And the first game revealed a lot: with the first four chambers of the gun shown empty, your opponent knows the die could only have landed on a 3 or 4. Which is not nearly as random as not knowing anything. And at the very least, he now knows the bullet is either in the very first chamber, or the very last one. Heck, if he knows about some bias in the barrel, he might make an even better guess.
On this game your opponent goes first. The odds still aren’t great, but he’s desperate enough to risk it anyway. The gun clicks. He sighs in relief: he knows he’ll win.
It’s called “one-time pad” for a reason: it works only once. Next time, try to prepare several die throws in advance. Or quit. In this game, it’s not clear how cheating affects life expectancy.
Encrypting bits
On the internet, instead of rolling gun barrels, we sends bits and bytes. Let’s say you want to send one bit. Like a yes or no, go or no-go, pass or fail. You can encrypt that with a coin flip: heads, the message is unchanged. Tails, yes and no are flipped.
Just like with the dice, someones who sees the encrypted message without knowing which side the coin landed on, will not be able to decrypt the message: with a 50% chance of yes and no being flipped, both are equally likely whether they heard “yes” or “no”.
And just like with the dice, a coin flip is only good for one yes or no. If you try to encrypt two with a single coin flip, observing the encrypted message will reveal information about the original message:
- “Yes yes” means either “yes yes” (heads) or “no no” (tails).
- “No no” means either “no no” (heads) or “yes yes” (tails).
- “Yes no” means either “yes no” (heads) or “no yes” (tails).
- “No yes” means either “no yes” (heads) or “yes no” (tails).
From 4 possibilities, we’re down to only 2. The same is true for arbitrarily many bits: if you try to encrypt n bits with a single coin flip, where you do different things to the message depending on whether you got heads or tails, then seeing the encrypted message will reduce the space of possible messages from 2n to just 2.
The gist of it is, to properly encrypt n bits of information, you need an n bit key. Because no matter how you cut it, once someone sees the encrypted text and properly applies the Bayes formula, the amount of uncertainty about the original message is reduced to no more than n bits. Messages longer than that will get partially decrypted. Messages longer than that, about which the observer already knew n bits of information or more, will most likely get totally decrypted.
From there, we can see why Cloudflare love their lava lamps: the amount of traffic they handle is huge, and using a one-time pad on all of it requires an astronomical amount of random numbers.
Except…
No one uses one-time pads any more. We have authenticated encryption now. From a 256-bit key we can encrypt gigabytes upon gigabytes of data, in direct contradiction of information theory.
How?
In theory, when you encrypt a message with a 256-bit key, publishing the encrypted message instantly reveals everything except 256-bits of information. But in practice, if you correctly use a proper stream cipher like ChaCha20 or AES-256-CTR, a passive observer will have to try about 2255 combinations before they stumble upon the original plaintext, which to our current knowledge of physics and available resources on this planet, is flat out impossible.
Put another way, there are deterministic processes out there, that can extract an unlimited amount of information from just a couple dozen bytes.
I mean, not actually: anyone who knows the key can predict the entire stream, and observing it will not teach them anything they didn’t already know. To them, the stream itself contains no information at all. Likewise, any God can observe the stream and guess the key until they find the one that generates it. Our puny Earth-bound civilisation though? Even with all the resources devoted to Bitcoin and all language models combined, without the key the stream will stay perfectly unpredictable. Pure randomness, a bottomless well of “entropy”.
To encrypt the entirety of their internet traffic for the foreseeable future, Cloudflare needs no more than 256 bits.
Random numbers in practice
The procedure to derive random numbers for Cloudflare from a single 256 bit master key is actually quite simple. Start with a master server or hardware security module with the master key. Use that to generate a stream of local keys, to be securely distributed across the entire company. Each server, or maybe each CPU core, has a 256 bits local key and a counter they can use to generate as many random bytes as they need.
There’s just one problem: if a local key leaks, every message the affected machine ever encrypted, as well as any message they will encrypt, is compromised. If the master key leaks, well… Cloudflare getting sued to oblivion will be a minor detail compared to the rest of the fallout.
That’s no good. We need a way to (i) minimise the chances of a key ever leaking, and (ii), the consequences if it ever does. A nice procedure for that is fast key erasure:
- Put a 32 byte random seed at the start of a 512 byte buffer.
- Generate 512 bytes with it, overwriting the buffer.
- Output those bytes on demand, except the first 32.
- Don’t forget to wipe those bytes from the buffer right away.
- When you run out, go back to step (2).
The “erasure” part of this procedure comes from the fact that step 2 erases the key. The buffer can still leak, which would compromise all future messages, but at least the past ones are safe. And of course, you can re-seed and start over whenever you suspect such a leak.
A much bigger thing to watch out for, is making sure you never duplicate your buffer. Don’t copy it, don’t instantiate two streams from the same seed, make sure to properly split your stream in two whenever you fork your process… Otherwise you end up with two or more identical streams, with all manner of catastrophic consequences.
One last thing to be mindful of, is the internal block size of the underlying stream cipher. ChaCha20’s 512 bit blocks are large enough never to worry about. AES’s 128 bit blocks are still large enough, but there are realistic attacks whose chance of success are much better than naive brute force would be — to the point they would break AES-128, though AES-256 remains safe. As for smaller block sizes, they’re just broken here.
My advice: just use either ChaCha20 (my favourite), or AES-256.
But what if my CSPRNG is broken?
To begin with, it’s not. I know it sounds like a cop out, but modern stream ciphers are extremely secure. We have every reason to think they are very unlikely to be broken in the foreseeable future. Not just the academic literature, but also the use by various governments, most notably the USA. Simply put, don’t worry.
And if it is broken… you’re just screwed, sorry. See, both your encryption and your CSPRNG relies on a cipher. If either is broken, then so is your whole system.
It won’t happen of course, but let’s assume for a moment the chances of AES-256 or ChaCha20 being broken in the near future are significant. To maximise your chances of success, you can do a number of things:
Use the same cipher for both your CSPRNG and your encryption. That way you remove a choice from your attacker: instead of having to break AES or ChaCha, they have to break the one you’ve chosen specifically.
Increase the number of rounds. AES-256 currently uses 14, and ChaCha20… 20. For both, more rounds means being harder to attack. Not because it takes more resources to brute force (though it does), but because it stops us from doing better than brute force. ChaCha7 for instance has attacks that work better than exhaustive search, but ChaCha8 currently does not. But we still use 20 rounds, to make extra sure we won’t have any problem even if all of a sudden someone finds an attack that works on 12 rounds.
Prefer ChaCha20 over AES. It has a bigger security margin, and is easier to implement in constant time (crucial to avoid some side channel attacks) on a wider range of architectures than AES. (Really though, AES is fine.)
Don’t use several systems in parallel, it’s a trap. Yes, it can be done, but it makes the whole system quite a bit more complex, and that complexity is much more likely to introduce a critical vulnerability, than for a mathematical attack to emerge for any of its components.
And before you go on objecting that a physical true RNG remains better than a could-theoretically-be-broken CSPRNG, understand that your random output often must have no detectable bias to be secure. That means a distribution so uniform you can’t detect a bias even after analysing 264 samples.
Good luck tuning a physical process to such perfection. In practice you’ll need to run the output of your noise source through a cryptographic hash. The increase in security is marginal compared to a stream cipher with fast key erasure, and the performance cost might be significant depending on your needs.
Reach for the true RNG source when it is convenient to do so, not because you think it is more secure than a CSPRNG. Simplicity is far more important than speculations about future breakthroughs that will most probably never materialise.
Starting seed
Okay, we have a way to generate arbitrarily many random numbers from just a few bytes. But we do need those initial bytes. How do I get those?
Simple: digitally record some process that generates more than 256 bits of entropy, hash that record with a 256-bit hash (SHA-256, BLAKE2s…), and voila, you have your master seed. Possible processes include:
- Using CPU jitter (currently used by the Linux kernel).
- Using a hardware random number generator.
- Taking a snap of some random tree.
- Shooting photons through a beam splitter.
- Typing random letters on your keyboard, frantically moving the mouse…
- Throwing dice.
- …
Whatever your mean, once you have your number you can seed everything.
Though, distributing random numbers across sites isn’t really practical. It’s not just complicated, it can be a source of breach. Also, you don’t just need it once, you need it every time you suspect a breach, or make a significant security update. To minimise both the hassle and the risk, it is often best to generate the random seed from the very computer that will use it, instead of taking it from elsewhere.
Thankfully, these days CPU jitter is all you need on a typical server. Or maybe a hardware RNG, they’re no bigger than a USB stick, and could be a better alternative if you fear CPU jitter isn’t unpredictable enough. Anything more involved adds unnecessary complexity, and therefore unnecessary risk.
This is why Cloudflare’s wall of lava lamps is actually worse than useless: you don’t just need to take the photo and hash it, you also need to transmit the hash over the local network, and hope it hasn’t been intercepted. Even if you take all the necessary precautions, the camera and local network increase the attack surface. From the outset, the random number coming from the camera is less trustworthy than a random number coming from the server itself.
You can try to salvage this contraption by hashing the server RNG seed together with the camera’s, such that your seed remains secure even if the camera or local network are compromised. It would work, but that’s still more complex, and riskier, than it needs to be.
Here’s what I think Cloudflare should do, if they’re as committed to security as they’re trying to convey with their marketing: stop using those lava lamps. They can keep them for decoration and marketing, but servers must generate their own random numbers. Simpler, more secure, pure win.
Come to think of it, they may already be doing that.