CUID2生成器

Generate secure next-generation CUID2 identifiers

数量
长度

已生成的 CUID2

点击"生成"以创建 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 设计原则

不可预测
不嵌入时间戳、计数器或主机指纹。每个 ID 从新鲜的密码随机盐与 SHA-512 组合生成。
平坦分布
SHA-512 摘要的 base-36 编码产生近乎均匀的字符分布,减少 B 树数据库中的索引热点。
默认 URL 安全
字母表限制为小写字母 a–z 和数字 0–9——无连字符、下划线或混合大小写——使 ID 在 URL 中无需百分比编码即可安全使用。
可配置长度
你选择长度(2–32)。较短的 ID 意味着更高的碰撞概率;推荐的默认值 24 提供约 4 × 10³⁷ 个唯一值。
始终以字母开头
第一个字符始终是随机小写字母,确保 CUID2 值是有效的 HTML 元素 ID 和 CSS 选择器,无需转义。
无需服务器
CUID2 仅依赖所有现代浏览器和 Node.js 15+ 中可用的 Web Crypto API,因此 ID 可以在客户端生成,具有与服务器相同的安全保证。

CUID2 vs CUID v1——比较

下表总结了 CUID2 与现已弃用的 CUID v1 之间的主要差异。如果你当前正在使用 CUID v1,强烈建议迁移到 CUID2。

属性CUID2CUID v1
安全性密码(SHA-512)非密码(基于指纹)
可预测性不透明——无元数据泄露时间戳 + 指纹在 ID 中可见
长度可配置(2–32 字符)固定 25 字符
前缀随机字母 a–z始终以 "c" 开头
分布平坦 / 均匀单调递增段
状态积极维护被原始作者弃用

CUID2 vs UUID v4——比较

UUID v4 是随机唯一 ID 的主导标准。CUID2 在不牺牲安全性的情况下提供了相对于 UUID v4 的几个实际优势。

属性CUID2UUID v4
默认长度24 个字符36 个字符(含连字符)
URL 安全是——小写 a–z + 0–9需要编码(含连字符)
自定义长度是(2–32)否——始终 128 位 / 36 字符
可排序否(设计如此)否(v4 是随机的)
熵源SHA-512 + Web CryptoCSPRNG
字符集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:

JavaScript (npm — @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"

使用 CUID2 与 Prisma 模式:

Prisma Schema
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,正如本工具在浏览器中所做的那样):

Node.js (Web Crypto — no dependencies)
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"

常见问题

CUID2 与 CUID v1 向后兼容吗?
否。CUID2 ID 与 CUID v1 ID 看起来完全不同。CUID v1 始终以字母 "c" 开头,长度固定为 25 个字符。CUID2 以随机字母开头,长度可配置(默认 24)。如果你迁移现有数据库,你需要处理两种格式或运行迁移来替换所有 CUID v1 值。
应该使用什么长度?
默认的 24 个字符是大多数应用程序的推荐选择。它提供约 4 × 10³⁷ 个唯一值,使碰撞在大规模下统计上不可能发生。如果存储很关键且数据集在几十亿条记录以下,使用 16 个字符。使用 32 个字符可获得最大安全余量。
CUID2 可以按创建时间排序吗?
否——这是有意为之。CUID2 故意丢弃所有时间信息以防止枚举攻击和指纹识别。如果你需要按时间排序的 ID,请考虑改用 ULID 或 UUID v7。CUID2 以可排序性换取安全性和不透明性。
CUID2 vs NanoID——应该选择哪个?
两者都是安全且 URL 安全的。NanoID 默认略短(21 个字符),使用更大的字母表(A–Za–z0–9_-),每个字符提供更多熵。CUID2 使用受限字母表(a–z, 0–9),在 CSS 选择器中更安全,且始终以字母开头。如果你想要最大熵密度,选择 NanoID;如果 Prisma/ORM 集成或 CSS 安全 ID 很重要,选择 CUID2。
CUID2 对 URL 安全吗?
是。CUID2 仅使用小写字母(a–z)和数字(0–9)。它不包含连字符、下划线、加号、斜杠或等号,因此可以直接嵌入 URL、HTML id 属性、CSS 选择器和文件名而无需任何编码。
我可以使用 CUID2 作为数据库主键吗?
是,这是主要使用场景之一。CUID2 避免了自增整数的顺序模式(可能泄露行数并允许枚举),比 UUID 更短(节省索引空间),且 URL 安全。大多数数据库将其存储为 VARCHAR(24) 或 TEXT 列。请注意,与 ULID 或 UUID v7 不同,CUID2 值不按时间排序,因此如果你的查询严重依赖插入顺序扫描,你可能更喜欢可排序的替代方案。