UUID v4 generator

Random version 4 UUIDs to RFC 9562, generated from crypto.getRandomValues() in your browser and never sent anywhere.

G

Where the 122 bits come from

A version 4 UUID is 128 bits of which 122 are random. The other six are not free: four of them are the version field and two are the variant field, and both are overwritten after the random draw rather than sampled. That is the whole algorithm. Take sixteen random bytes, clear the top nibble of byte 6 and set it to 0100, clear the top two bits of byte 8 and set them to 10, then render the result as hexadecimal.

The variant costs two bits rather than four because only the two most significant bits of that nibble are fixed. The remaining two stay random, which is why the character at that position is always one of 8, 9, a or b and never anything else. The version nibble costs all four bits, so the character before the third hyphen is always the digit 4.

Everything else in the value is uniform output from the platform CSPRNG. There is no timestamp, no counter, no machine identifier and nothing derived from the process. Two generators running on two machines that have never heard of each other produce values that do not collide, and that property is the entire reason the format exists.

The two positions that are not random, counting characters from 1 in the 36-character form
PositionByteFieldFixed valueRenders as
156, high nibblever0100Always the digit 4
208, top two bitsvar10One of 8, 9, a, b

A validating regular expression that does not pin those two positions will accept 32 arbitrary hex digits as a UUID. If you are checking inbound identifiers, check the version and variant, not just the shape.

The collision numbers, worked out

The figure you see quoted is usually derived from 2 to the power of 128, which is wrong by a factor of 64. The correct keyspace is 2 to the power of 122, which is 5.317e36. Using the birthday bound on that number gives the table below.

The honest reading is that the mathematics stops being the interesting part almost immediately. You would have to generate 103 trillion version 4 UUIDs before the chance of a single collision anywhere in the set reached one in a billion, and 2.71e18 before it reached a coin flip. Nothing you are building will approach either.

What can actually collide is a broken source. A generator built on Math.random() has at most 128 bits of PRNG state and, in one engine, only 2 to the power of 32 distinct streams. A container image that snapshots a seeded state and then starts fifty copies produces fifty identical sequences. Both of those failure modes are real and neither of them shows up in the birthday calculation, which is why the source of the bytes matters more than the width of the value.

Probability that a batch of n version 4 UUIDs contains at least one duplicate
UUIDs generatedCollision probabilityRead as
1 billion9.4e-20About 1 in 1.1e19
1 trillion9.4e-14About 1 in 1.1e13
103 trillion1.0e-91 in a billion
1 quadrillion9.4e-8About 1 in 10.6 million
2.71e180.50A coin flip

crypto.randomUUID() produces exactly this

The native browser and Node function returns a version 4 UUID and nothing else. There is no argument to make it produce another version, and there will not be, because the specification defines it as v4. It draws from the same CSPRNG that backs crypto.getRandomValues(), so the output is identical in quality to the code on this page.

The one thing to know about it is that it is only exposed in a secure context. On an https origin or on localhost it is there; on a plain http origin such as a LAN address or an IP-based dev server, crypto.randomUUID is undefined and the call throws. This is the common way it breaks: it works on the developer machine and fails the moment a colleague opens the same page over the network. The availability floor is Chrome 92, Firefox 95, Safari 15.4 and Node 16.7.0, with Safari the binding constraint.

This tool builds on crypto.getRandomValues() rather than delegating to the native function. That is deliberate: getRandomValues is one primitive that also produces version 7, hex, base64 and custom alphabets, so there is a single code path to read and audit instead of two.

When v4 is the right answer, and when it is not

Use version 4 whenever the identifier is going to be visible to someone who should not be able to guess neighbouring values, and whenever it does not need to sort. Public-facing object identifiers, idempotency keys, correlation identifiers on a request, file names in object storage and anything you hand to a third party are all good fits. The value carries nothing about when or where it was made.

Use version 7 instead when the identifier is a database primary key on an engine that clusters or orders by it. Random keys land at unpredictable points in a B-tree, which splits pages and enlarges the index; time-ordered keys do not. The tradeoff is that a v7 value discloses its creation time to the millisecond, so it belongs on internal keys rather than on public URLs. The UUID v7 generator covers that in detail.

Do not reach for version 1 as a compromise. It embeds a timestamp and historically a MAC address, so it leaks more than v7 does while giving up the sortability that v6 and v7 were designed to provide. And do not use a name-based version 3 or 5 UUID anywhere unguessability is required: those are deterministic hashes of public input and contain zero unpredictable bits.

Doing this in your own code

JavaScript
// The one-liner. Version 4 only, and it needs a secure context.
crypto.randomUUID();

// The same thing written out, so you can see where the six fixed bits go.
function uuidv4() {
  const b = crypto.getRandomValues(new Uint8Array(16));
  b[6] = (b[6] & 0x0f) | 0x40;   // version nibble -> 4
  b[8] = (b[8] & 0x3f) | 0x80;   // variant -> 10xx
  const h = [...b].map((v) => v.toString(16).padStart(2, "0")).join("");
  return `${h.slice(0,8)}-${h.slice(8,12)}-${h.slice(12,16)}-${h.slice(16,20)}-${h.slice(20)}`;
}

crypto.randomUUID() is undefined on a plain http:// origin that is not localhost. That is the usual cause of it working on your machine and failing on a LAN dev server.

Python
import uuid

u = uuid.uuid4()
str(u)        # '919108f7-52d1-4320-9bac-f847db4148a8'
u.hex         # 32 hex digits, no hyphens
u.bytes       # 16 bytes, big-endian
u.version     # 4
u.variant     # 'specified in RFC 4122'

uuid.uuid4() reads from os.urandom, so it is cryptographically secure. uuid.uuid1() is not a substitute.

Go
import "github.com/google/uuid"

u := uuid.New()             // v4, panics if the entropy source fails
u, err := uuid.NewRandom()  // v4, returns the error instead

// Parse and check before trusting an inbound value.
p, err := uuid.Parse(s)
if err == nil && p.Version() == 4 { /* ... */ }
Java 17+
import java.util.UUID;

UUID u = UUID.randomUUID();   // v4, backed by SecureRandom
u.version();                  // 4
u.variant();                  // 2

// UUID.fromString is lenient, and still is. It rejects input longer
// than 36 characters, but it only requires four hyphens: a short form
// such as "1-1-1-1-1" parses happily into 00000001-0001-0001-0001-...
// Validate untrusted input yourself rather than relying on the parser
// to reject a malformed value.
PostgreSQL
-- Built in since PostgreSQL 13, no extension needed.
SELECT gen_random_uuid();

CREATE TABLE api_key (
  id       uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  created  timestamptz NOT NULL DEFAULT now()
);

-- Store as uuid (16 bytes), never as varchar(36).
Shell
uuidgen --random          # util-linux, explicitly v4
cat /proc/sys/kernel/random/uuid   # Linux kernel, always v4
python3 -c "import uuid; print(uuid.uuid4())"

# --random exists only on util-linux. The macOS uuidgen documents exactly
# one option, -hdr, and its man page names no UUID version at all, so if
# the version has to be guaranteed on macOS, use the Python line. macOS
# also prints the value in uppercase.

Questions

Why does every UUID I generate have a 4 in the same place?

That character is the version field, and version 4 is the random version. It is set after the random bytes are drawn rather than sampled, along with the two variant bits in the fourth group. Six of the 128 bits are spent this way, which leaves 122 random ones.

Is a v4 UUID strong enough to use as a token?

For a bearer token, 122 bits from a CSPRNG is more than sufficient; 128 bits is the usual target and this is close enough that the difference is not the weak link. What matters is that the bytes come from a cryptographic source. A v4 UUID produced by Math.random() has nothing like 122 bits behind it, and you cannot tell the two apart by looking at the output.

Can I make crypto.randomUUID() give me a v7 UUID?

No. The function is specified to return version 4 and takes no arguments. Version 7 has no native API in any JavaScript runtime yet, so it has to be built on crypto.getRandomValues().

Are uppercase UUIDs valid?

Yes. RFC 4122 required lowercase output; RFC 9562 relaxed that and permits upper, lower or mixed case, while still requiring parsers to accept any of them. Lowercase remains the sensible default, and Windows tooling conventionally uses uppercase. They compare equal only if you normalise the case before comparing as text.

Should I store UUIDs as text?

No. A uuid column is 16 bytes and compares as an integer. A varchar(36) column is 37 bytes, compares as text under a collation, and doubles the size of every index that includes it. On MySQL, BINARY(16) with UUID_TO_BIN is the equivalent.

Do you check the values before showing them?

Yes. Every value emitted here has its version nibble and variant bits verified after generation. The reference implementation was checked against the test vector in RFC 9562 Appendix A.3, and a run of 200,000 values carried the correct version and variant on every one.

Related generators