How Random Is a UUID? Entropy, v4, and v7
· by Andergrove Software
A version-4 UUID contains 122 bits of randomness. A version-7 UUID contains 74, because a timestamp has eaten the rest. Those two numbers explain almost everything about where each version belongs, and yet most of us generate thousands of UUIDs a week without ever meeting them.
This post is about that bit budget: how v4 and v7 are actually built, what jobs they end up doing, and what "random" and "entropy" mean — because they are not the same word wearing different hats.
The 128-bit budget
A UUID is 128 bits, printed as 32 hex digits in five groups: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. Six of those bits go on bureaucracy. The M digit holds the four-bit version, and the top two bits of N hold the variant, which says "these are RFC rules, read the rest accordingly." That leaves 122 bits for the version to spend however it likes.
It is also the one place a UUID reliably tells the truth about itself. Digit 13 is a 4, you have a v4; a 7, you have a v7. Everything else is version-specific filling.
v4: spend all of it on randomness
v4 spends the entire 122-bit allowance on random bits, and that is the whole design. No clock, no MAC address, no coordination, no central authority. Two machines that have never heard of each other can each mint a million IDs and safely assume no overlap.
That property is why v4 turns up everywhere:
- Idempotency keys. Generated client-side before a payment request, so a retry after a dropped connection does not charge the customer twice.
- Correlation and trace IDs. Stamped on a request at the edge and carried through every service and log line it touches.
- Object and file names. S3 keys, upload filenames, anything where two concurrent writers must not pick the same name.
- Resource identifiers handed out by systems like Kubernetes, where objects are created by many actors at once.
The one place v4 quietly disappoints is the database primary key, and that is where v7 came in.
v7: half a clock, half a coin
v7 divides the same 122 bits differently: the leading 48 hold a Unix millisecond timestamp, and the remaining 74 are random. (Formally: unix_ts_ms 48 bits, version 4, rand_a 12, variant 2, rand_b 62 — implementations may spend part of rand_a on a counter so IDs minted in the same millisecond still come out in order.)
Because the significant bits increase with time, v7s sort roughly in creation order. New rows land at the right-hand edge of the index instead of scattered through it, which is the entire reason the version exists; the index behaviour is covered in UUID v4 vs. v7. So v7 shows up as database primary keys, as event and message IDs in append-only streams, and anywhere chronological sorting for free is worth having.
The catch is printed on the label: a v7 publishes its own creation time to the millisecond. For a row ID, fine. For a password-reset token, you have just handed an attacker 48 bits of the answer.
Support is now boringly normal. PostgreSQL 18 ships a built-in uuidv7(), .NET has Guid.CreateVersion7(), and most language ecosystems have a small library. Browsers are the holdout — crypto.randomUUID() still gives you v4 only, which is what our UUID generator produces. For the rest of the family tree, including the name-based versions, see the UUID versions explained.
Randomness and entropy are not the same thing
Here is the distinction that trips people up, myself included for longer than I would like to admit.
Randomness is a property of a process. A coin flip is random; the number 7 is not. You cannot inspect a value and decide whether it was randomly produced — 00000000-0000-4000-8000-000000000000 is exactly as likely to fall out of a good generator as any other v4. It just looks like somebody's test fixture escaped.
Entropy is the measurement. It quantifies, in bits, how much you do not know about a value before you see it. One fair coin flip is one bit. Sixteen fair flips are sixteen bits, or 65,536 equally likely outcomes.
The standard measure is Shannon entropy, the average surprise per outcome: H = −Σ p log₂ p. For a fair coin that is 1 bit. For a coin biased 90/10 it is 0.469 bits — still positive, but you already know a lot about how it lands.
Security work usually wants a stricter measure: min-entropy, which considers only the single most likely outcome, H∞ = −log₂(max p). That 90/10 coin has a min-entropy of 0.152 bits, because an attacker who always guesses "heads" wins nine times in ten. Averages are comforting; the worst case is what actually gets guessed. It is the same reason password strength depends on how a cracker attacks rather than on how impressive the password looks — see how long it takes to crack a password.
Now point that at UUIDs. A v4 from a proper cryptographic generator carries 122 bits of entropy. A v4 from Math.random() carries at most as much as the generator's internal state: V8 uses xorshift128+, whose state can be recovered from a handful of outputs, after which every future value is predictable. The two UUIDs are visually indistinguishable. Both survive any eyeball test you can devise. One of them is a secret and the other is a formality.
Randomness, in short, is not a look.
How entropy actually gets measured
Two different jobs, routinely confused.
Estimating the entropy of a source. This is what NIST SP 800-90B exists for. It defines min-entropy estimators applied to raw output from a noise source — ring oscillators, thermal noise, timing jitter — plus continuous health tests that run in production: the Repetition Count Test, which trips when a value repeats implausibly often, and the Adaptive Proportion Test, which trips when one value starts dominating a window. That is how a CPU's RDRAND or a TPM justifies the number it claims, and the estimate is deliberately pessimistic.
Testing an output stream statistically. Suites like Dieharder, PractRand, the NIST SP 800-22 battery and TestU01's BigCrush throw hundreds of tests at a stream looking for structure: bit correlations, gaps, birthday spacings, poor matrix ranks. The poor-man's version is compression — a megabyte of good random output should not shrink, and if gzip finds 20% to remove, something has gone badly wrong upstream.
The essential caveat: these tests can only ever falsify randomness. They find patterns, and finding none proves nothing at all. The digits of π sail through BigCrush, and π is not a secret. A counter encrypted with AES passes everything while being perfectly predictable to anyone holding the key. Statistical tests catch broken generators; they cannot certify a good one. For that you have to know where the bits came from.
What the bits actually buy you
Collision odds follow the birthday bound: with n bits of randomness you expect a first collision after roughly 2^(n/2) values.
For v4's 122 bits that is about 2.7 quintillion UUIDs before the odds reach even. The usual framing is that you could generate 103 trillion of them and still face only about a one-in-a-billion chance of a single duplicate. Which is why "will my UUIDs collide" is not a real engineering concern, while "is my generator actually random" very much is. Every real-world UUID collision I have seen traced back to a bad seed, a forked process sharing RNG state, or a fleet of containers booting from one image — never to the arithmetic.
For v7's 74 random bits the sums are per-millisecond, since IDs from different milliseconds cannot collide at all. You would need roughly 160 billion v7s inside the same millisecond for even odds. If you are producing those, the ID scheme is not your most pressing problem.
The short version
- Default to v4 for anything unpredictable or externally visible: tokens, idempotency keys, correlation IDs, object names.
- Use v7 for primary keys and anything append-ordered, and accept that it discloses its own creation time.
- Never build one from
Math.random().crypto.randomUUID(),crypto.getRandomValues(),secretsin Python,SecureRandomin Java — the right call costs nothing. - Do not truncate a UUID to make it prettier. Chopping a v4 to 64 bits drops the collision threshold from quintillions to around 4 billion, a number real systems reach.
- Remember that a UUID looks identical whether it holds 122 bits of entropy or none. Only the source knows.