왜 crypto.randomUUID()를 꼭 써야 하나 — UUID 충돌 확률과 예측 가능성
UUID v4를 만드는 코드는 두 갈래로 나뉩니다. crypto.randomUUID()처럼 브라우저의 Web Crypto API를 쓰는 방식과, Math.random()으로 직접 16진수 문자열을 조립하는 방식입니다. 결과물은 똑같이 8-4-4-4-12 형태의 문자열이라 겉보기엔 구분이 안 되지만, 이 둘은 "무작위성의 품질"이라는 근본적으로 다른 성질을 가집니다. 충돌 확률과 예측 가능성은 서로 다른 문제라는 것부터 짚고 시작합니다.
1. 충돌 확률: v4는 사실상 걱정할 필요가 없다
UUID v4는 128비트 중 버전(4비트)과 변형(variant, 2비트)을 제외한 122비트가 무작위로 채워집니다. 이만큼의 경우의 수를 가진 값이 우연히 겹칠 확률은 "생일 문제(birthday problem)"로 계산합니다 — 사람이 많아질수록 생일이 같은 두 사람이 나올 확률이 생각보다 빨리 올라가는 것과 같은 원리로, N개를 생성했을 때 충돌 확률은 대략 N²/2^123에 비례합니다. 초당 10억 개씩 UUID v4를 계속 생성해도 충돌 확률이 50%에 도달하려면 약 85년이 걸립니다. 즉 v4의 충돌 확률 자체는 CSPRNG를 쓰든 안 쓰든 실무에서 문제될 수준이 아닙니다. 이 사이트의 다른 가이드에서 다루는 "DB 기본키로 쓰면 느려지는 이유"도 충돌이 아니라 무작위 정렬 순서가 B-트리 인덱스 페이지 분할을 유발하는 별개의 문제입니다.
2. 진짜 위험은 충돌이 아니라 예측 가능성
충돌이 문제가 아니라면 왜 CSPRNG를 강조할까요? UUID를 세션 토큰, 비밀번호 재설정 링크, 공유 전용 URL처럼 "추측 불가능해야 하는 값"으로 쓰는 경우가 많기 때문입니다. 이때 진짜 위협은 "다른 사용자와 같은 UUID가 우연히 나올 확률"이 아니라 "공격자가 특정 UUID를 미리 계산해낼 수 있는가"입니다. Math.random()은 암호학적 안전성을 목표로 설계되지 않은 PRNG(의사난수 생성기)로, 내부 시드값의 상태 공간이 좁고 일부 엔진 구현은 시드나 이전 출력값으로부터 다음 값을 역산할 수 있는 취약점이 알려져 있습니다. 이 값으로 UUID를 만들면 "형식은 UUID지만 보안 강도는 훨씬 낮은 무작위값"이 나옵니다. 반면 crypto.randomUUID()와 crypto.getRandomValues()는 OS 수준의 CSPRNG(암호학적으로 안전한 난수 생성기)를 사용해, 이전 출력값을 아무리 많이 모아도 다음 값을 예측할 수 없도록 설계되어 있습니다.
const u = crypto.randomUUID(); 한 줄로 UUID를 만듭니다. 반면 직접 구현할 때 흔히 보이는 안티패턴은 다음과 같습니다.
| 방식 | 난수 소스 | 보안 용도 적합성 |
|---|---|---|
crypto.randomUUID() | OS CSPRNG | 세션 토큰·비밀 URL에 적합 |
crypto.getRandomValues() | OS CSPRNG | 커스텀 포맷에도 적합 |
Math.random() 기반 조립 | 비암호학적 PRNG | UI용 임시 key 등 비보안 용도만 |
3. 언제 어느 쪽을 써도 되는가
모든 UUID가 보안에 민감한 것은 아닙니다. React 리스트의 key prop, 임시 DOM id, 로컬 테스트 픽스처처럼 "추측당해도 아무 피해가 없는" 용도라면 Math.random() 기반 라이브러리를 써도 실질적 위험은 없습니다. 반대로 세션 ID, 이메일 인증 토큰, 파일 공유 링크, API 키처럼 제3자가 값을 추측하면 실제 피해가 생기는 용도라면 반드시 CSPRNG 기반 생성 방식을 써야 합니다. 판단 기준은 "충돌 확률이 낮은가"가 아니라 "이 값을 누군가 예측했을 때 문제가 되는가"입니다.
4. 브라우저 호환성과 폴백 주의사항
crypto.randomUUID()는 비교적 최근 API라 구형 브라우저나 HTTP(비-HTTPS) 컨텍스트에서는 지원되지 않을 수 있습니다(Web Crypto API는 보안 컨텍스트에서만 동작). 이때 폴백으로 Math.random()을 쓰는 코드가 종종 섞여 들어가는데, 이렇게 되면 겉으로는 정상 동작하지만 보안 강도가 조용히 낮아진 상태로 배포될 수 있습니다. 폴백이 필요하다면 crypto.getRandomValues()로 바이트를 직접 뽑아 RFC 4122 형식으로 조립하는 방식을 쓰는 것이 안전합니다 — 이 역시 CSPRNG 소스이므로 예측 가능성 문제가 생기지 않습니다.
자주 묻는 질문
Q. UUID v4가 우연히 겹칠 확률이 실무에서 걱정할 수준인가요?
아닙니다. 122비트 무작위 공간 덕분에 초당 10억 개씩 생성해도 충돌 확률 50%에 도달하려면 약 85년이 걸립니다. 일반적인 서비스 규모에서는 사실상 무시할 수 있는 확률입니다.
Q. 그럼 Math.random()으로 만든 UUID는 왜 위험한가요?
충돌 확률이 아니라 예측 가능성이 문제입니다. Math.random()은 암호학적 안전성을 목표로 하지 않는 PRNG라 시드·이전 출력값을 통해 다음 값을 추측할 수 있는 경우가 있습니다. 세션 토큰이나 비밀 URL로 쓰면 공격자가 값을 추측해 접근할 위험이 생깁니다.
Q. 이 사이트의 UUID 생성기는 어떤 방식을 쓰나요?
UUID 생성기는 crypto.randomUUID()를 사용합니다. 생성된 값이 브라우저 밖으로 전송되지 않으며, OS 수준 CSPRNG 기반이라 보안이 필요한 용도에도 안전하게 쓸 수 있습니다.
Q. UUID를 DB 기본키로 쓰는 성능 문제와 이 가이드의 보안 이슈는 같은 건가요?
다릅니다. 성능 문제는 무작위 정렬 순서가 B-트리 인덱스 페이지 분할을 유발하는 별개의 이슈이며, 이 가이드가 다루는 것은 난수 생성 방식에 따른 예측 가능성·보안 강도입니다. DB 성능 이슈는 다른 가이드에서 다룹니다.