In Ruby, is SecureRandom.uuid or SecureRandom.uuid_v7 a sound password-reset token, and what does each one actually contain?
answer
- uuid is an alias of uuid_v4
- v4: 122 random bits
- v7: millisecond timestamp first
- v7: 74 random bits by default
- identifiers vs secrets
basics
~20 sSecureRandom.uuid (version 4) carries 122 random bits and is acceptable, though urlsafe_base64(32) is stronger and carries more bits per character. SecureRandom.uuid_v7 starts with a millisecond timestamp and has only 74 random bits, so it leaks creation time and suits identifiers, not secrets.
solid answer
~40 s`SecureRandom.uuid`, also available as `uuid_v4`, fills all but the version and variant bits from the secure generator, so it has 122 random bits in 36 characters. That is guess-resistant enough for a reset token, but `SecureRandom.urlsafe_base64(32)` gives 256 bits in 43 characters and makes the intent obvious. `SecureRandom.uuid_v7` is built for database keys: the first 48 bits are the Unix time in milliseconds, so values sort by creation time, and only 74 bits are random, or 62 with `extra_timestamp_bits: 12`. It reveals when the token was issued and is easier to guess than a v4 value. Use v7 for primary keys and ordering, v4 when you need an opaque identifier, and a dedicated token method for secrets.
code
ruby · 7 linesrequire "securerandom"
SecureRandom.uuid # => "2d931510-d99f-494a-8c67-87feb05e1594" (version 4)
SecureRandom.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" (time-ordered)
# 122 random bits in v4; 74 in v7; 256 here:
SecureRandom.urlsafe_base64(32)go deeper
Recall that SecureRandom.uuid is a random version-4 UUID and that uuid_v7 starts with a timestamp.
Explain the bit layouts, 122 versus 74 random bits, and why v7's ordering helps indexes but hurts secrecy.
Keep identifiers and secrets separate in schema design, and pick uuid_v7 for keys while issuing tokens with a dedicated method.
Set the identifier strategy for the platform: which ids are public, which are time-ordered, and which values are secrets with their own storage rules.
## What each method returns Both methods come from `Random::Formatter`, which `SecureRandom` extends, and both return the canonical 36-character UUID text form, `8-4-4-4-12` hex digits. They differ in where the bits come from. | Method | Layout | Random bits | Sortable? | |---|---|---|---| | `SecureRandom.uuid` / `uuid_v4` | version and variant bits, the rest random | 122 | no | | `SecureRandom.uuid_v7` | 48-bit millisecond Unix timestamp, then random | 74 | yes, by creation time | | `SecureRandom.uuid_v7(extra_timestamp_bits: 12)` | timestamp plus 12 bits of sub-millisecond time | 62 | yes, finer ordering | `extra_timestamp_bits:` accepts 0 to 12; anything else raises `ArgumentError`. Each extra timestamp bit is taken from the random part. ## Why v7 exists Version 7 UUIDs are designed for **database keys**. Because consecutive values start with increasing timestamps, new rows land near each other in an index instead of at random positions, which improves insert locality. The library's documentation also notes what v7 does not promise: values created within the same millisecond are not guaranteed to be in order, and a clock rolled back breaks monotonicity. Those properties are good for identifiers and bad for secrets: - **It leaks time.** Anyone holding the token can read when it was issued to the millisecond. - **It has fewer secret bits.** 74 random bits, or 62 with extra timestamp precision, rather than 122, and the timestamp part is easy to guess from when the email was sent. - **It is structured.** Tokens issued close together share their prefix, which gives an attacker a head start. ## Is v4 good enough for a reset token? A version-4 UUID from `SecureRandom` carries 122 bits from the operating system's secure generator, far beyond what online guessing can reach, so it is **acceptable**. The reasons to prefer a dedicated token method are practical: 1. **Intent.** `SecureRandom.urlsafe_base64(32)` reads as "secret"; a UUID reads as "identifier", and identifiers get logged, shown in admin screens and put in URLs that are shared. 2. **Density.** 256 bits in 43 characters versus 122 bits in 36. 3. **Consistency.** One token helper across the codebase, instead of a mix of UUIDs and hex strings, makes review simpler. ## Common confusions - **`uuid` is version 4.** `uuid_v4` is an alias of `uuid`, and only `uuid_v7` produces time-ordered values. - **A UUID column is not a secret column.** If a table's primary key is a v4 UUID, it is still an identifier that appears in URLs; do not reuse it as a reset token. - **`Random.uuid` and `Random.new.uuid` exist too** once the formatter is loaded, filled from the non-cryptographic generator. Only `SecureRandom` gives secure bits. - **Uniqueness is not secrecy.** A value can be globally unique and still guessable; a v7 UUID is unique by construction and partly predictable by design. ## Choosing, in one line each - Primary keys and time-ordered ids: `SecureRandom.uuid_v7`. - Opaque public identifiers: `SecureRandom.uuid`. - Reset tokens, API keys, session secrets: `SecureRandom.urlsafe_base64(32)` or `SecureRandom.hex(32)`, stored as a digest. ## How the bits are laid out - **Version 4.** The method takes 16 random bytes, overwrites the high nibble of byte 6 with `4` (the version) and the top two bits of byte 8 with the variant, and prints the result in `8-4-4-4-12` groups. Everything else is random. - **Version 7.** The method reads the realtime clock in milliseconds, writes those 48 bits as the first 12 hex digits, then fills the rest from 10 random bytes with the version nibble `7` and the variant bits set. That layout is why the creation time is readable by anyone holding the value: the first 12 hex digits, taken as one base-16 number, are the Unix time in milliseconds. `Integer(uuid.delete("-")[0, 12], 16)` recovers it, and `Time.at(ms / 1000.0)` turns it into a date. For a record id that is often harmless; for a reset token it tells an attacker exactly when to expect the next one.
- What does uuid_v7(extra_timestamp_bits: 12) trade away?It adds about 244 nanoseconds of timestamp precision so values created in the same millisecond still sort, and takes those 12 bits from the random part, leaving 62 random bits. Values outside 0..12 raise `ArgumentError`.
- Can a v7 UUID still be a good public identifier for records?Yes, for identifiers where revealing creation time is acceptable: it is unique, index-friendly and still hard to enumerate. Keep secrets such as reset tokens and API keys separate, generated with a token method.
saying these in an interview costs you the question
- SecureRandom.uuid produces a version 7, time-ordered UUID by default.
- A v7 UUID is as unguessable as a v4 UUID because both are from SecureRandom.
- A unique value is automatically a secure token.
- Using the record's UUID primary key as its reset token is fine.