مولّد CUID2
يولد معرفات CUID2 آمنة من الجيل التالي
CUID2s المولّدة
ما هو CUID2؟
CUID2 هو الجيل الثاني من معرّف CUID، أُعيدت كتابته من الصفر بتركيز على الأمان التشفيري والخصوصية.
على عكس CUID v1 الذي يُضمّن الطابع الزمني بشكل مرئي، يُخفي CUID2 جميع مكوناته داخل هاش SHA3، مما يُنتج سلاسل لا يمكن التنبؤ بها عملياً.
تبدأ المعرّفات ببادئة حرفية قابلة للتخصيص (افتراضياً c) تليها سلسلة مرمَّزة بـ base36 من هاش SHA3.
لماذا CUID2؟
جاء CUID2 استجابةً لمخاوف أمنية محددة في CUID v1:
- الطابع الزمني المرئي: كان CUID v1 يكشف وقت الإنشاء في بداية كل معرّف.
- القابلية للتنبؤ: مع معرفة بنية CUID v1، كان بالإمكان استنتاج جزء من الحالة الداخلية.
- نقص الأمان التشفيري: لم يكن CUID v1 يستخدم مولد أرقام عشوائية تشفيرياً آمناً بشكل صريح.
يعالج CUID2 هذه المخاوف باستخدام SHA3 وCSPRNG وإخفاء الطابع الزمني.
مبادئ التصميم
CUID2 مقابل CUID v1
مقارنة الخصائص الرئيسية بين الجيلين:
| الخاصية | CUID2 | CUID v1 |
|---|---|---|
| الأمان | تشفيري (SHA3+CSPRNG) | غير تشفيري |
| القابلية للتنبؤ | منخفضة جداً | منخفضة (طابع زمني مكشوف) |
| الطول | قابل للتخصيص (24 افتراضي) | 25 حرفاً ثابتة |
| البادئة | قابلة للتخصيص | ثابتة 'c' |
| التوزيع | موزع بالتساوي | متحيز نحو الطابع الزمني |
| الحالة | نشط ومُوصى به | قديم |
CUID2 مقابل UUID v4
مقارنة مع معيار UUID الشائع:
| الخاصية | CUID2 | UUID v4 |
|---|---|---|
| الطول الافتراضي | 24 حرفاً | 36 حرفاً |
| آمن للعناوين | نعم | نعم (مع الشُرَط) |
| طول مخصص | نعم | لا |
| قابل للترتيب | لا | لا |
| الإنتروبيا | عالية (SHA3) | عالية (CSPRNG) |
| مجموعة الأحرف | حروف وأرقام صغيرة | ست عشري + شُرَط |
CUID2 يوفر بادئات أبجدية مضمونة وطولاً مرناً مع أمان مكافئ لـ UUID v4.
من يستخدم CUID2؟
تبنّى CUID2 مجتمعات وأطر عمل بارزة:
- Prisma — يدعم CUID2 كنوع معرّف في حقل
@idعبر@default(cuid()). - مجتمع Drizzle ORM يتبنّى CUID2 للمعرّفات الافتراضية.
- مشاريع SvelteKit وRemix تستخدمه لمعرّفات الجلسات.
- خوادم API عالية الحجم تحتاج إلى معرّفات يمكن تتبعها في السجلات.
- التطبيقات التي تتزامن عبر عدة قواعد بيانات أو مناطق.
أمثلة برمجية
التثبيت والاستخدام: pnpm add @paralleldrive/cuid2
import { createId } from '@paralleldrive/cuid2'
// Generate a single CUID2 (default length: 24)
const id = createId()
console.log(id) // e.g. "tz4a98xxat96iws9zmbrgj3a"
// Custom length
import { init } from '@paralleldrive/cuid2'
const createShortId = init({ length: 16 })
const shortId = createShortId()
console.log(shortId) // e.g. "tz4a98xxat96iws9"التكوين المتقدم:
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}التكامل مع قاعدة البيانات:
async function generateCuid2(length = 24) {
const alphabet = 'abcdefghijklmnopqrstuvwxyz'
// Random prefix letter
const firstByte = crypto.getRandomValues(new Uint8Array(1))[0]
const firstChar = alphabet[firstByte % 26]
// Random 32-byte salt
const salt = crypto.getRandomValues(new Uint8Array(32))
const saltHex = [...salt].map(b => b.toString(16).padStart(2, '0')).join('')
// SHA-512 of timestamp + salt
const input = Date.now().toString(36) + saltHex
const hashBuffer = await crypto.subtle.digest(
'SHA-512',
new TextEncoder().encode(input)
)
// Encode hash bytes as base-36 string
const bytes = new Uint8Array(hashBuffer)
let hash = ''
for (let i = 0; i < bytes.length; i += 8) {
let chunk = 0n
for (let j = 0; j < 8 && i + j < bytes.length; j++) {
chunk = (chunk << 8n) | BigInt(bytes[i + j])
}
hash += chunk.toString(36)
}
return (firstChar + hash).slice(0, length)
}
// Usage
const id = await generateCuid2()
console.log(id) // e.g. "m7k3r9p2nxq8zt5a6cwj4bvd"