Digizap

Your digital pocket knife.

Your IP: 216.73.217.62

Knowledge base

UUID, ULID and ObjectId: what the bits of an id hold

An id of 36 characters looks like a random row of hex, but a UUID is a form: RFC 9562 assigns six of the 128 bits to version and variant, and at the time forms a clock stands in as well. With UUID and ULID you read those fields bit by bit in your own browser — version, variant, time, clock-seq, node address, machine id, counter and the random fields — and refuses everything that is no form, with the reason attached.

Besides UUID (versions 1 through 7, nil and max) the tool reads ULID: 26 characters of Crockford base32, of which the first ten carry a 48-bit millisecond clock and the remaining sixteen are random. Two ULIDs in one paste answer the monotone question with that: which id stands after which one, and whether the time or the randomness decided it.

This article reads what those bits mean and where they stop having meaning: why random bits prove no randomness, why the time in an id is not the time of the event, and why a UUIDv1 gives away more about your machine than you intended. The tool’s generators build ids too: UUID v4 and v7, ULID and ObjectId, from your browser CSPRNG.

1. One hundred twenty-eight bits with six bits of appointment

A UUID is 8-4-4-4-12 hex characters: 128 bits of which RFC 9562 sets six in stone. The top four bits of byte 6 carry the version nibble, the top two bits of byte 8 the variant — and that variant says which family the id belongs to: 10xx is RFC 9562 itself, 0xxx the old NCS, 110x Microsoft, 111x reserved. The remaining 122 bits are exactly what the version puts in them: at v4 and v7 random, at v1 and v6 time and a node address, at v3 and v5 the hash of a name. Outside those six bits a UUID carries no appointment: no checksum, no origin, no ownership.

Two ids have their own name instead of a version: the nil UUID, every bit zero, and the max UUID, every bit one — both formally named in RFC 9562 and therefore no version error. A version nibble RFC 9562 does not know (8, 9, A through F) is readable as a form but stays without meaning: the anatomy reads it as unknown, not as an error and not as something it invents.

2. The time in an id — and whose clock it is

UUIDv1 times in 60 bits: 100-nanoseconds since 15 October 1582, the Gregorian rebase, spread over time_low, time_mid and time_hi — and the tool computes that clock back to the Unix millisecond. UUIDv6 is the same clock, reordered so that sorting on hex follows time; v7 keeps the Gregorian ring out and puts plain Unix time in milliseconds in the top 48 bits. ULID does the latter in base32: 48 bits, with a ceiling lying in the year +10889 — a ULID needing a timestamp character beyond that does not exist. An ObjectId finally carries only seconds, in its first eight hex characters.

Where the time stands, stands the time of the id — not of the event. A row created tomorrow may already have received its id today or been carried in from an earlier file; the bits cannot tell those apart. And the time forms are not anonymous: v1 and v6 carry a clock-seq and the node address with them, in practice the MAC address of the machine that built the id — personal or company-bound, and not linkable to a name from the id itself.

3. Crockford base32 and the monotone question

A ULID is 26 characters of Crockford base32: 32 letters of alphabet without the four that are confused with others — I, L, O and U — and therefore forbidden in an id. A pasted ULID with an I is no id and is not half-read: the anatomy names the position and the character the encoder did mean, because Crockford itself fixes those suggestions (I and L to 1, O to 0, U to V). Lowercase is simply the same id: the reading uppercases before it judges.

Because the ten timestamp characters stand in front and base32 follows ordinary character sorting, the lexicographic order of ULIDs is their time order — as long as the ids were not built in the same millisecond. The tool answers that question per pair: the second id stands after, before, or is identical to the first, and at an equal millisecond the card adds that the entropy characters decided the order. Over three or more ids, or over how a database sorts, the tool stays silent — that is not what a handful of bits carry.

4. What no id says about itself

Random fields prove no randomness. A UUIDv4, ULID or ObjectId has random bits — 122, 80 or 64 of them — but whether those came from a good source cannot be read from the id itself: an id from a predictable source looks identical. An id that a bug built twice carries no trace of that bug. That is why the tool’s generators build from crypto.getRandomValues and say so; the origin of ids you copy out of a system we leave unclaimed.

Start at the measurement side: UUID and ULID for the anatomy and the generators, Security headers for one place where such a random id truly must be irreplaceable (the CSP nonce, which may not be predictable), and Reading mail headers for ids in the wild: Message-IDs and selector fingerprints, where the same question — what does this id say, and what not — returns across the whole header.

Measure it yourself: UUID and ULID

Paste uuids, ulids and objectids and read their version, variant, time and random fields bit by bit; build one yourself with your browser CSPRNG, with the non-claims attached.

Read on