UUID v1生成器
Generate time-based UUID v1 with embedded timestamp
…
格式化
什么是 UUID v1?
UUID v1 是原始的 UUID 版本,在 RFC 4122(2005 年)中标准化。它通过将高精度时间戳与生成主机的 MAC 地址以及用于处理亚时间戳分辨率的短时钟序列组合来生成唯一标识符。
由于嵌入了时间戳,来自同一主机的 UUID v1 值随时间单调递增——使它们自然有序。这是为分布式系统设计的,其中每个节点可以独立生成 UUID 而无需协调。
今天 UUID v1 在很大程度上被 UUID v7(可排序、无 MAC 泄露)和 UUID v4(完全随机、私密)所取代。它仍然在 Apache Cassandra 和遗留分布式数据库等系统中使用。
UUID v1 的结构
像 550e8400-e29b-11d4-a716-446655440000 这样的 UUID v1 字符串编码了六个不同的字段:
| 字段 | 大小 | 描述 |
|---|---|---|
| time_low | 32 bits | 60 位格里高利时间戳的 32 位低字段(自 1582 年 10 月 15 日以来的 100 纳秒间隔) |
| time_mid | 16 bits | 60 位时间戳的中间 16 位字段 |
| time_hi_and_version | 16 bits | 60 位时间戳的最高 12 位加 4 位版本号(始终为 <code>1</code>) |
| clock_seq_hi_res | 8 bits | 6 位时钟序列高字段与 2 位 RFC 4122 变体标记组合 |
| clock_seq_low | 8 bits | 时钟序列的低 8 位 |
| node | 48 bits | 48 位节点标识符——通常是生成网络接口的 MAC 地址,如果没有 MAC 可用则为随机 48 位值 |
时钟序列字段(clock_seq_hi_res + clock_seq_low)是一个 14 位计数器。每当系统时钟向后移动(例如 NTP 调整)或系统在未持久化最后已知时间戳的情况下重启时,它就会递增。这防止了在时钟不单调前进时生成重复的 UUID。
解码 UUID v1 时间戳
60 位时间戳分散在 UUID 的三个字段中。要重建生成时间:
- 提取
time_low(字节 0–3)、time_mid(字节 4–5)和time_hi(字节 6–7,减去版本半字节) - 重新组装:
(time_hi << 48) | (time_mid << 32) | time_low - 结果是自 1582 年 10 月 15 日(格里高利历纪元)以来
100 纳秒间隔的 60 位计数 - 减去格里高利到 Unix 的偏移量:122,192,928,000,000,000(1582 年 10 月 15 日至 1970 年 1 月 1 日之间的 100 纳秒间隔)
- 除以
10,000将 100 纳秒间隔转换为毫秒 - 将结果用作
Unix 毫秒时间戳来构造 Date 对象 - 格式化为
ISO 8601以获得人类可读的输出
时间戳精度为 100 纳秒——远比 UUID v7 的毫秒精度更细。但是,在实践中大多数操作系统不提供亚毫秒时钟分辨率,所以较低的位通常为零或合成值。
隐私问题
UUID v1 最显著的缺点是它在节点字段中嵌入了生成主机的 MAC 地址。这意味着每个 UUID v1 都携带着生成它的机器的永久、全球唯一的指纹。
获得 UUID v1 的攻击者可以确定:(1) ID 生成的大致时间,(2) 生成主机的 MAC 地址,以及 (3) 通过分析多个 UUID,ID 的生成速率。
因此,UUID v1 不应作为面向公众的标识符使用(例如在 URL 或 API 响应中),除非你愿意公开这些信息。RFC 4122 本身指出,系统可以使用随机 48 位值代替 MAC 地址,但许多实现并不这样做。
UUID v1 仍然适用的场景
UUID v1 vs UUID v7
UUID v7 是时间排序标识符的 UUID v1 的现代继任者。以下是直接对比:
| 方面 | UUID v1 | UUID v7 |
|---|---|---|
| 纪元 / 时间基准 | 格里高利纪元(1582 年 10 月 15 日) | Unix 纪元(1970 年 1 月 1 日) |
| 精度 | 100 纳秒 | 1 毫秒 |
| 节点标识符 | MAC 地址(泄露主机身份) | 随机(私密) |
| 隐私 | 泄露 MAC 地址和生成时间戳 | 不嵌入主机信息 |
| 数据库索引性能 | 良好——每台主机顺序排列 | 极佳——跨所有生成器 k 可排序 |
| 标准 | RFC 4122(2005 年) | RFC 9562(2024 年) |
对于新项目,UUID v7 是 UUID v1 的推荐替代品。它提供类似的时间排序保证,而不会有嵌入主机 MAC 地址的隐私影响。
代码示例
UUID v1 生成在浏览器或 Node.js 中原生不可用。使用 uuid npm 包:
// Generate a UUID v1 using the Web Crypto API
function generateUuidV1() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
const gregorianOffset = 122192928000000000n
const t = ms * 10000n + gregorianOffset
const tLow = Number(t & 0xFFFFFFFFn)
const tMid = Number((t >> 32n) & 0xFFFFn)
const tHiVer = Number((t >> 48n) & 0x0FFFn) | 0x1000 // version 1
const clockSeq = (buf[8] & 0x3F) | 0x80 // variant 10xxxxxx
const clockSeqLow = buf[9]
const hex = (n, pad) => n.toString(16).padStart(pad, '0')
const node = [...buf.slice(10)].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex(tLow,8)}-${hex(tMid,4)}-${hex(tHiVer,4)}-${hex(clockSeq,2)}${hex(clockSeqLow,2)}-${node}`
}
// Extract the embedded timestamp from a UUID v1
function extractTimestamp(uuid) {
const parts = uuid.split('-')
const tHex = parts[2].slice(1) + parts[1] + parts[0]
const t = BigInt('0x' + tHex)
const ms = (t - 122192928000000000n) / 10000n
return new Date(Number(ms))
}
const id = generateUuidV1()
console.log(id) // e.g. "1eb5e8b0-6b4d-11ee-9c45-a1f2b3c4d5e6"
console.log(extractTimestamp(id)) // e.g. 2023-10-15T12:34:56.789Zimport uuid from datetime import datetime, timezone # Generate UUID v1 (uses MAC address by default) uid = uuid.uuid1() print(uid) # Extract embedded timestamp # uuid.time is 100-ns intervals since Oct 15, 1582 GREGORIAN_OFFSET = 122192928000000000 # 100-ns intervals ts_100ns = uid.time ts_ms = (ts_100ns - GREGORIAN_OFFSET) // 10000 dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc) print(dt.isoformat()) # e.g. "2023-10-15T12:34:56.789000+00:00"
package main
import (
"fmt"
"time"
"github.com/google/uuid" // go get github.com/google/uuid
)
func main() {
id, _ := uuid.NewUUID() // UUID v1
fmt.Println(id)
// Extract timestamp from UUID v1
// uuid.Time is 100-ns ticks since Oct 15, 1582
t := id.Time()
sec := int64(t)/1e7 - 12219292800 // convert to Unix seconds
nsec := (int64(t) % 1e7) * 100
ts := time.Unix(sec, nsec).UTC()
fmt.Println(ts.Format(time.RFC3339Nano))
}