CUID2 생성기
Generate secure next-generation CUID2 identifiers
생성된 CUID2
CUID2란 무엇인가요?
CUID2(충돌 저항 고유 ID, 버전 2)는 데이터베이스, URL, 분산 시스템에서 기본 키로 안전하게 사용할 수 있는 짧고, 암호학적으로 안전하며 불투명한 ID를 생성하도록 설계된 CUID v1의 차세대 후계자입니다.
이전 버전과 달리 CUID2는 생성 시간, 호스트 머신 또는 생성한 프로세스에 대한 정보를 일절 드러내지 않습니다. 각 ID는 무작위 소문자로 시작하고 SHA-512에서 파생된 base-36 해시가 뒤따르는 겉보기에 무작위인 문자열입니다. 기본 길이는 24자이지만 저장소 제약에 맞게 2~32자로 설정할 수 있습니다.
CUID2는 현대적인 데이터베이스 툴킷에서 널리 권장됩니다. Prisma는 @default(cuid()) 스칼라의 기본 ID 전략으로 채택했으며, PlanetScale, Neon 및 기타 서버리스 데이터베이스 공급자는 CUID2를 선호 ID 형식으로 명시적으로 나열합니다.
CUID2가 CUID v1을 대체한 이유
2012년 Eric Elliott이 출시한 CUID v1은 클라이언트 측 ID 생성에서 일반 UUID에 비해 큰 개선이었습니다. 그러나 보안 연구자들이 설계의 두 가지 근본적인 문제를 발견했습니다:
- 핑거프린팅: 모든 CUID v1 값에 내장된 호스트 지문을 사용하여 ID를 생성한 머신이나 프로세스를 식별할 수 있었으며, ID를 관찰할 수 있는 누구에게나 운영 메타데이터를 누출했습니다.
- 예측 가능성: CUID v1이 단조 증가 카운터와 타임스탬프 세그먼트를 포함했기 때문에, 여러 ID를 관찰한 공격자가 미래 ID의 대략적인 범위를 예측하여 ID를 유일한 인증 검사로 사용하는 API에 대한 열거 공격을 가능하게 했습니다.
- 비암호화 해시: CUID v1은 현대 보안 표준을 충족하지 못하는 간단한 비암호화 해싱 단계를 사용했습니다.
원래 저자 Eric Elliott은 CUID v1을 공식적으로 더 이상 사용하지 않음으로 표시하고 이러한 모든 문제를 해결하기 위해 CUID2를 처음부터 작성했습니다. 새 알고리즘은 Web Crypto API(SHA-512)를 사용하고 모든 결정론적 컴포넌트를 제거합니다.
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) | 16진수 + 하이픈 |
주요 트레이드오프는 친숙함입니다: UUID v4는 거의 모든 데이터베이스, 프로그래밍 언어, API 프레임워크에서 즉시 인식되는 IETF 표준(RFC 4122)입니다. CUID2는 성장하고 있지만 보편적이지 않은 지원을 가진 커뮤니티 표준입니다. 외부 시스템과의 상호 운용성이 가장 중요할 때 UUID v4를 선택하고; 양쪽을 제어하고 더 짧고 URL 안전한 ID를 선호할 때 CUID2를 선택하세요.
CUID2를 사용하는 곳
CUID2는 현대적인 JavaScript 및 TypeScript 생태계에서 빠르게 채택되고 있습니다:
- Prisma — 가장 인기 있는 TypeScript ORM은 Prisma Schema v2+에서
@id필드와@default(cuid())에 CUID2를 권장 기본값으로 사용합니다. - PlanetScale — 문서와 스타터 템플릿은 분산 MySQL 플랫폼의 순차 스캔 성능 문제를 피하기 위해 애플리케이션 생성 기본 키에 CUID2를 권장합니다.
- 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"Prisma 스키마와 함께 CUID2 사용:
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}npm 패키지 없이 Node.js에서 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"