UUID v1 생성기
Generate time-based UUID v1 with embedded timestamp
…
포맷
UUID v1이란 무엇인가요?
UUID v1은 RFC 4122(2005)에서 표준화된 원래 UUID 버전입니다. 고정밀 타임스탬프와 생성 호스트의 MAC 주소, 하위 타임스탬프 해상도를 처리하는 짧은 클럭 시퀀스를 결합하여 고유 식별자를 생성합니다.
타임스탬프가 내장되어 있으므로 동일한 호스트의 UUID v1 값은 시간이 지남에 따라 단조 증가합니다——자연스럽게 순서가 있습니다. 이는 각 노드가 조율 없이 독립적으로 UUID를 생성할 수 있는 분산 시스템을 위해 설계되었습니다.
오늘날 UUID v1은 UUID v7(정렬 가능, MAC 누출 없음)과 UUID v4(완전히 무작위, 비공개)로 크게 대체되었습니다. Apache Cassandra 및 레거시 분산 데이터베이스 같은 시스템에서 계속 사용됩니다.
UUID v1 구조 분석
550e8400-e29b-11d4-a716-446655440000과 같은 UUID v1 문자열은 6개의 서로 다른 필드를 인코딩합니다:
| 필드 | 크기 | 설명 |
|---|---|---|
| time_low | 32 bits | 60비트 그레고리안 타임스탬프의 32비트 하위 필드(1582년 10월 15일부터 100나노초 간격) |
| time_mid | 16 bits | 60비트 타임스탬프의 중간 16비트 필드 |
| time_hi_and_version | 16 bits | 60비트 타임스탬프의 상위 12비트와 4비트 버전 번호(항상 <code>1</code>) |
| clock_seq_hi_res | 8 bits | 2비트 RFC 4122 변형 마커와 결합된 6비트 클럭 시퀀스 상위 필드 |
| clock_seq_low | 8 bits | 클럭 시퀀스의 하위 8비트 |
| node | 48 bits | 48비트 노드 식별자——일반적으로 생성 네트워크 인터페이스의 MAC 주소, 또는 MAC을 사용할 수 없는 경우 무작위 48비트 값 |
클럭 시퀀스 필드(clock_seq_hi_res + clock_seq_low)는 14비트 카운터입니다. 시스템 클럭이 뒤로 이동(예: NTP 조정)하거나 마지막으로 알려진 타임스탬프를 저장하지 않고 시스템이 재시작될 때 증가합니다.
UUID v1 타임스탬프 디코딩
60비트 타임스탬프는 UUID의 세 필드에 흩어져 있습니다. 생성 시간을 재구성하려면:
time_low(바이트 0-3),time_mid(바이트 4-5),time_hi(바이트 6-7, 버전 니블 제외)를 추출- 재조립:
(time_hi << 48) | (time_mid << 32) | time_low - 결과는 1582년 10월 15일(그레고리안 달력 에포크)부터의
100나노초 간격의 60비트 카운트 - 그레고리안에서 Unix 오프셋 빼기: 122,192,928,000,000,000(1582년 10월 15일부터 1970년 1월 1일까지의 100ns 간격)
10,000으로 나누어 100나노초 간격을 밀리초로 변환- 결과를
Unix 밀리초 타임스탬프로 Date 객체 구성 - 사람이 읽을 수 있도록
ISO 8601로 포맷
타임스탬프 정밀도는 100나노초——UUID v7의 밀리초 정밀도보다 훨씬 세밀합니다. 그러나 실제로 대부분의 OS는 밀리초 미만 클럭 해상도를 제공하지 않아 하위 비트가 종종 0이거나 합성입니다.
프라이버시 우려
UUID v1의 가장 중요한 단점은 노드 필드에 생성 호스트의 MAC 주소를 내장한다는 것입니다. 즉 모든 UUID v1은 생성한 머신의 영구적이고 전 세계적으로 고유한 지문을 갖습니다.
UUID v1을 얻은 공격자는 (1) ID가 생성된 대략적인 시간, (2) 생성 호스트의 MAC 주소, (3) 여러 UUID를 분석하여 ID 생성 속도를 결정할 수 있습니다.
이런 이유로 이 정보를 공개하는 것이 편하지 않다면 UUID v1을 공개 식별자(URL이나 API 응답 등)로 절대 사용해서는 안 됩니다.
UUID v1이 여전히 적절한 경우
UUID v1 대 UUID v7
UUID v7은 시간 순서 식별자를 위한 UUID v1의 현대적 후계자입니다. 직접 비교:
| 측면 | UUID v1 | UUID v7 |
|---|---|---|
| 에포크 / 시간 기준 | 그레고리안 에포크(1582년 10월 15일) | Unix 에포크(1970년 1월 1일) |
| 정밀도 | 100나노초 | 1밀리초 |
| 노드 식별자 | MAC 주소(호스트 신원 누출) | 무작위(비공개) |
| 프라이버시 | MAC 주소와 생성 타임스탬프 누출 | 호스트 정보 내장 없음 |
| DB 인덱스 성능 | 양호——호스트별 순차 | 탁월——모든 생성기에 걸쳐 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))
}