مولّد UUID v1
يولد UUID v1 زمنية مع طابع زمني مضمن
…
تنسيق
ما هو UUID v1؟
UUID v1 هو إصدار UUID الأصلي، موحَّد في RFC 4122 (2005). يولّد معرّفات فريدة بجمع طابع زمني عالي الدقة مع عنوان MAC للمضيف المولِّد، بالإضافة إلى تسلسل ساعة قصير لمعالجة الدقة دون الطابع الزمني.
لأن الطابع الزمني مضمَّن، تكون قيم UUID v1 من المضيف ذاته رتيبة تصاعدياً بمرور الوقت — مما يجعلها مرتّبة بطبيعتها. صُمِّم هذا للأنظمة الموزّعة حيث يستطيع كل عقدة توليد UUID باستقلالية دون تنسيق.
اليوم، UUID v1 استُبدل إلى حدٍّ بعيد بـ UUID v7 (قابل للترتيب، لا تسريب MAC) وUUID v4 (عشوائي كلياً، خاص). لا يزال مستخدماً في أنظمة مثل Apache Cassandra وقواعد البيانات الموزّعة القديمة.
تشريح UUID v1
سلسلة UUID v1 مثل 550e8400-e29b-11d4-a716-446655440000 ترمّز ستة حقول متمايزة:
| الحقل | الحجم | الوصف |
|---|---|---|
| time_low | 32 bits | الحقل المنخفض 32 بت للطابع الزمني الغريغوري 60 بت (بفترات 100 نانوثانية منذ 15 أكتوبر 1582) |
| time_mid | 16 bits | الحقل الأوسط 16 بت للطابع الزمني 60 بت |
| time_hi_and_version | 16 bits | أعلى 12 بت للطابع الزمني 60 بت بالإضافة إلى رقم الإصدار 4 بت (دائماً <code>1</code>) |
| clock_seq_hi_res | 8 bits | الحقل العلوي 6 بت لتسلسل الساعة مدمجاً مع علامة متغيّر RFC 4122 البالغة 2 بت |
| clock_seq_low | 8 bits | الـ 8 بت الدنيا لتسلسل الساعة |
| node | 48 bits | معرّف العقدة 48 بت — عادةً عنوان MAC لواجهة الشبكة المولِّدة، أو قيمة عشوائية 48 بت إذا لم يتوفّر MAC |
حقل تسلسل الساعة (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 - الناتج عدد 60 بت من
فترات 100 نانوثانيةمنذ 15 أكتوبر 1582 (حقبة التقويم الغريغوري) - اطرح إزاحة الغريغوري-إلى-Unix: 122,192,928,000,000,000 (فترات 100 نانوثانية بين 15 أكتوبر 1582 و1 يناير 1970)
- اقسم على
10,000لتحويل فترات 100 نانوثانية إلى مللي ثانية - استخدم الناتج كـ
طابع زمني Unix بالمللي ثانيةلإنشاء كائن Date - اعرضه كـ
ISO 8601للإخراج المقروء بشرياً
دقة الطابع الزمني 100 نانوثانية — أدق بكثير من دقة المللي ثانية في UUID v7. غير أن معظم أنظمة التشغيل عملياً لا تكشف عن دقة أعلى من المللي ثانية، لذا كثيراً ما تكون البتات الدنيا صفراً أو مُركَّبة.
مخاوف الخصوصية
أبرز عيوب UUID v1 أنه يضمّ عنوان MAC للمضيف المولِّد في حقل العقدة. هذا يعني أن كل UUID v1 يحمل بصمة دائمة وفريدة عالمياً للجهاز الذي ولّده.
يستطيع المهاجم الذي يحصل على UUID v1 معرفة: (1) الوقت التقريبي لتوليد المعرّف، (2) عنوان MAC للمضيف المولِّد، و(3) من خلال تحليل عدة UUID، معدّل توليد المعرّفات.
لهذا السبب، لا ينبغي أبداً استخدام UUID v1 كمعرّف مواجه للعموم (مثلاً في عناوين URL أو استجابات API) ما لم تكن مرتاحاً للكشف عن هذه المعلومات. يلاحظ RFC 4122 نفسه أن النظام قد يستخدم قيمة عشوائية 48 بت بدلاً من عنوان MAC، لكن كثيراً من التطبيقات لا تفعل ذلك.
متى يظل UUID v1 مناسباً
UUID v1 مقابل UUID v7
UUID v7 هو الخلف الحديث لـ UUID v1 في المعرّفات المرتّبة زمنياً. إليك مقارنة مباشرة:
| الجانب | UUID v1 | UUID v7 |
|---|---|---|
| الأصل / القاعدة الزمنية | الحقبة الغريغورية (15 أكتوبر 1582) | حقبة Unix (1 يناير 1970) |
| الدقة | 100 نانوثانية | ملّي ثانية واحدة |
| معرّف العقدة | عنوان MAC (يكشف هوية المضيف) | عشوائي (خاص) |
| الخصوصية | يكشف عنوان MAC والطابع الزمني للتوليد | لا معلومات مضيف مضمَّنة |
| أداء فهرس قاعدة البيانات | جيد — تسلسلي لكل مضيف | ممتاز — k-sortable عبر جميع المولِّدات |
| المعيار | 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))
}