UUID v1 چیست؟
UUID v1 نسخه اصلی UUID است که در RFC 4122 (2005) استانداردسازی شده است. این نسخه شناسههای یکتا را با ترکیب یک تایماستمپ با دقت بالا با آدرس MAC میزبان تولیدکننده، به علاوه یک دنباله ساعت کوتاه برای مدیریت تداخلهای زمانی تولید میکند.
از آنجا که تایماستمپ جاسازی شده است، مقادیر UUID v1 از همان میزبان در طول زمان به صورت یکنواخت افزایش مییابند — که آنها را به طور طبیعی مرتب میکند. این برای سیستمهای توزیعشده طراحی شده بود که هر گره میتوانست UUIDها را به صورت مستقل و بدون هماهنگی تولید کند.
امروزه UUID v1 عمدتاً توسط UUID v7 (قابل مرتبسازی، بدون افشای MAC) و UUID v4 (کاملاً تصادفی، خصوصی) جایگزین شده است. این نسخه هنوز در سیستمهایی مانند Apache Cassandra و پایگاههای داده توزیعشده قدیمی استفاده میشود.
RFC 9562 — Universally Unique IDentifiers (UUID) →
ساختار UUID v1
یک رشته UUID v1 مانند 550e8400-e29b-11d4-a716-446655440000 شش فیلد مجزا را کدگذاری میکند:
| فیلد | اندازه | توضیحات |
|---|---|---|
| time_low | 32 bits | فیلد پایین ۳۲ بیتی تایماستمپ گرگوری ۶۰ بیتی (بازههای ۱۰۰ نانوثانیهای از ۱۵ اکتبر ۱۵۸۲) |
| time_mid | 16 bits | فیلد میانی ۱۶ بیتی تایماستمپ ۶۰ بیتی |
| time_hi_and_version | 16 bits | ۱۲ بیت بالایی تایماستمپ ۶۰ بیتی به علاوه شماره نسخه ۴ بیتی (همیشه <code>1</code>) |
| clock_seq_hi_res | 8 bits | فیلد بالایی دنباله ساعت ۶ بیتی ترکیبشده با نشانگر نوع RFC 4122 دو بیتی |
| clock_seq_low | 8 bits | ۸ بیت پایینی دنباله ساعت |
| node | 48 bits | شناسه گره ۴۸ بیتی — معمولاً آدرس MAC رابط شبکه تولیدکننده، یا یک مقدار تصادفی ۴۸ بیتی در صورت عدم دسترسی به MAC |
فیلد دنباله ساعت (clock_seq_hi_res + clock_seq_low) یک شمارنده ۱۴ بیتی است. هرگاه ساعت سیستم به عقب برگردد (مثلاً تنظیم NTP) یا سیستم بدون ذخیره آخرین تایماستمپ شناختهشده راهاندازی مجدد شود، افزایش مییابد. این از تولید UUIDهای تکراری در صورت عدم پیشروی یکنواخت ساعت جلوگیری میکند.
رمزگشایی تایماستمپ UUID v1
تایماستمپ ۶۰ بیتی در سه فیلد UUID پراکنده شده است. برای بازسازی زمان تولید:
- استخراج
time_low(بایتهای ۰–۳)،time_mid(بایتهای ۴–۵) وtime_hi(بایتهای ۶–۷ منهای nibble نسخه) - بازسازی:
(time_hi << 48) | (time_mid << 32) | time_low - نتیجه یک شمارش ۶۰ بیتی از
بازههای ۱۰۰ نانوثانیهایاز ۱۵ اکتبر ۱۵۸۲ (مبدأ تقویم گرگوری) است - تفریق افست گرگوری-به-یونیکس: ۱۲۲٬۱۹۲٬۹۲۸٬۰۰۰٬۰۰۰٬۰۰۰ (بازههای ۱۰۰ نانوثانیهای بین ۱۵ اکتبر ۱۵۸۲ و ۱ ژانویه ۱۹۷۰)
- تقسیم بر
۱۰٬۰۰۰برای تبدیل بازههای ۱۰۰ نانوثانیهای به میلیثانیه - استفاده از نتیجه به عنوان
تایماستمپ میلیثانیه یونیکسبرای ساخت یک شیء Date - قالببندی به صورت
ISO 8601برای خروجی قابل خواندن توسط انسان
دقت تایماستمپ ۱۰۰ نانوثانیه است — بسیار ظریفتر از دقت میلیثانیه UUID v7. با این حال، در عمل اکثر سیستمعاملها وضوح ساعت زیر-میلیثانیه را در دسترس نمیگذارند، بنابراین بیتهای پایینتر اغلب صفر یا مصنوعی هستند.
نگرانیهای حریم خصوصی
مهمترین عیب UUID v1 این است که آدرس MAC میزبان تولیدکننده را در فیلد گره جاسازی میکند. این بدان معناست که هر UUID v1 یک اثرانگشت دائمی و منحصر به فرد جهانی از ماشینی که آن را تولید کرده است حمل میکند.
مهاجمی که یک UUID v1 را به دست آورد میتواند زمان تقریبی تولید آن و آدرس MAC میزبان تولیدکننده را بازیابی کند.
به همین دلیل، UUID v1 هرگز نباید به عنوان شناسه رو به عموم (مثلاً در URLها یا پاسخهای API) استفاده شود، مگر اینکه با افشای این اطلاعات موافق باشید. RFC 4122 خود اشاره میکند که یک سیستم ممکن است از یک مقدار تصادفی ۴۸ بیتی به جای آدرس MAC استفاده کند، اما بسیاری از پیادهسازیها این کار را نمیکنند.
زمانی که UUID v1 هنوز مناسب است
UUID v1 در مقابل UUID v7
UUID v7 جانشین مدرن UUID v1 برای شناسههای مرتبشده بر اساس زمان است. در اینجا یک مقایسه مستقیم آورده شده است:
| جنبه | UUID v1 | UUID v7 |
|---|---|---|
| مبدأ / پایه زمانی | مبدأ گرگوری (۱۵ اکتبر ۱۵۸۲) | مبدأ یونیکس (۱ ژانویه ۱۹۷۰) |
| دقت | ۱۰۰ نانوثانیه | ۱ میلیثانیه |
| شناسه گره | آدرس MAC (هویت میزبان را افشا میکند) | تصادفی (خصوصی) |
| حریم خصوصی | آدرس MAC و تایماستمپ تولید را افشا میکند | هیچ اطلاعات میزبانی جاسازی نشده است |
| عملکرد ایندکس پایگاه داده | خوب — ترتیبی به ازای هر میزبان | عالی — k-sortable در تمام تولیدکنندهها |
| استاندارد | RFC 4122 (2005) | RFC 9562 (2024) |
برای پروژههای جدید، UUID v7 جایگزین مناسب UUID v1 است. ترتیب مرتبسازی جهانی بهتری ارائه میدهد — k-sortable در تمام تولیدکنندهها، نه فقط ترتیبی به ازای هر میزبان — بدون افشای آدرس MAC.
مثالهای کد
UUID v1 در APIهای داخلی مرورگر موجود نیست، اما میتوان آن را با استفاده از crypto.getRandomValues() و Date.now() ساخت. ماژول uuid پایتون آن را مستقیماً دارد؛ Go به پکیج github.com/google/uuid نیاز دارد:
// 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))
}