CUID2生成器
Generate secure next-generation CUID2 identifiers
已生成的 CUID2
什么是 CUID2?
CUID2(碰撞抵抗唯一 ID,版本 2)是 CUID v1 的下一代继任者,旨在生成短小、密码安全、不透明的 ID,可安全用作数据库、URL 和分布式系统中的主键。
与其前身不同,CUID2 不会泄露任何关于创建时间、主机机器或生成进程的信息。每个 ID 是一个看似随机的字符串,以随机小写字母开头,后跟从 SHA-512 派生的 base-36 哈希。默认长度为 24 个字符,但你可以根据存储限制将其配置为 2 到 32 个字符。
CUID2 被现代数据库工具包广泛推荐。Prisma 将其作为 @default(cuid()) 标量的默认 ID 策略,PlanetScale、Neon 和其他无服务器数据库提供商明确将 CUID2 列为首选 ID 格式,因为它避免了自增整数的顺序扫描漏洞,同时比 UUID 更短更易读。
CUID2 为何取代 CUID v1
CUID v1 于 2012 年由 Eric Elliott 发布,是客户端 ID 生成相对于普通 UUID 的重大改进。然而,安全研究人员发现其设计存在两个根本问题:
- 指纹识别:嵌入每个 CUID v1 值的主机指纹可用于识别生成 ID 的机器或进程,向任何能观察 ID 的人泄露操作元数据。
- 可预测性:由于 CUID v1 包含单调递增计数器和时间戳段,观察到多个 ID 的攻击者可以预测未来 ID 的大致范围,使针对使用 ID 作为唯一授权检查的 API 的枚举攻击成为可能。
- 非密码哈希:CUID v1 使用了简单的非密码哈希步骤,不符合现代安全标准。
原始作者 Eric Elliott 正式弃用 CUID v1 并从头编写了 CUID2 来解决所有这些问题。新算法使用 Web Crypto API(SHA-512)并消除了所有确定性组件,使每个 ID 在统计上独立于其他所有 ID。
CUID2 设计原则
CUID2 vs CUID v1——比较
下表总结了 CUID2 与现已弃用的 CUID v1 之间的主要差异。如果你当前正在使用 CUID v1,强烈建议迁移到 CUID2。
| 属性 | CUID2 | CUID v1 |
|---|---|---|
| 安全性 | 密码(SHA-512) | 非密码(基于指纹) |
| 可预测性 | 不透明——无元数据泄露 | 时间戳 + 指纹在 ID 中可见 |
| 长度 | 可配置(2–32 字符) | 固定 25 字符 |
| 前缀 | 随机字母 a–z | 始终以 "c" 开头 |
| 分布 | 平坦 / 均匀 | 单调递增段 |
| 状态 | 积极维护 | 被原始作者弃用 |
CUID2 vs UUID v4——比较
UUID v4 是随机唯一 ID 的主导标准。CUID2 在不牺牲安全性的情况下提供了相对于 UUID v4 的几个实际优势。
| 属性 | CUID2 | UUID v4 |
|---|---|---|
| 默认长度 | 24 个字符 | 36 个字符(含连字符) |
| URL 安全 | 是——小写 a–z + 0–9 | 需要编码(含连字符) |
| 自定义长度 | 是(2–32) | 否——始终 128 位 / 36 字符 |
| 可排序 | 否(设计如此) | 否(v4 是随机的) |
| 熵源 | SHA-512 + Web Crypto | CSPRNG |
| 字符集 | Base-36(a–z, 0–9) | 十六进制 + 连字符 |
主要权衡是熟悉度:UUID v4 是 IETF 标准(RFC 4122),几乎每个数据库、编程语言和 API 框架都开箱即用地支持它。CUID2 是一个具有增长但非普遍支持的社区标准。当与外部系统的互操作性至关重要时选择 UUID v4;当你控制两端并偏好更短、URL 安全的 ID 时选择 CUID2。
谁在使用 CUID2
CUID2 在现代 JavaScript 和 TypeScript 生态系统中得到了快速采用:
- Prisma——最流行的 TypeScript ORM 使用 CUID2 作为 Prisma Schema v2+ 中
@id字段与@default(cuid())的推荐默认值。 - PlanetScale——他们的文档和启动模板推荐 CUID2 用于应用程序生成的主键,以避免其分布式 MySQL 平台上的顺序扫描性能问题。
- Drizzle ORM——为列定义提供内置的
cuid2()默认辅助函数。 - tRPC 样板——许多社区 tRPC + Prisma 启动模板附带 CUID2 作为主键策略。
- T3 Stack——create-t3-app 脚手架工具在生成的模式文件中使用带 CUID2 默认值的 Prisma。
代码示例
官方 npm 包 @paralleldrive/cuid2 提供了简单的 API:
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"使用 CUID2 与 Prisma 模式:
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}在 Node.js 中不使用 npm 包生成 CUID2(仅使用 Web Crypto API,正如本工具在浏览器中所做的那样):
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"