UUIDの構造
UUID(Universally Unique Identifier)は128ビットの識別子で、中央で番号を割り振らなくてもレコードやファイルに一意な名前を付けられます。 通常は16進数32桁を8-4-4-4-12の形でハイフンでつなぎ、36文字で書きます。現行の規格は2024年5月発行のRFC 9562で、従来のRFC 4122を置き換えました。 どのバージョンでも、3番目のグループの先頭の1桁がバージョン、4番目のグループの先頭の1桁がバリアント(標準では8・9・a・bのいずれか。上位2ビットが 10)を表します。xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx の「4」がv4を示し、yは8・9・a・bのどれかです。
v4ではこの6ビットを固定し、残りの122ビットを乱数で埋めます。ほかに、時刻とノードから作るv1、名前空間と名前のハッシュ(MD5・SHA-1)で同じ入力から同じ値になるv3・v5、 先頭にUnixミリ秒の時刻を持ち作成順に並ぶv7などがあります。全ビット0のNil UUIDと全ビット1のMax UUIDも特別な値として定義されています。
このツールのv4の作り方
ブラウザ標準の crypto.randomUUID() を呼び出します。これは暗号学的に安全な乱数源から作ったUUID v4を返す関数で、HTTPSまたはlocalhostのような安全なコンテキストでのみ使えます。 使えない環境では crypto.getRandomValues() で16バイトの乱数を取り、6バイト目の上位4ビットを 0100(v4)、8バイト目の上位2ビットを 10(標準バリアント)に置き換えて、8-4-4-4-12の形に整えます。 どちらの場合も乱数の源はMath.random()ではありません。予測されやすいMath.random()は、推測されては困る識別子には向かないためです。個数・大文字・ハイフン・波括弧のオプションは表記を変えるだけで、値は変わりません。
衝突確率は計算で見る
ランダムなビット数をbとして、n個のうちどれかが重複する確率は、誕生日問題の近似式 p ≈ n² / (2 · 2b) で求められます(pが小さい間だけ有効)。v4はb = 122で、2122は約5.3 × 10³⁶です。
| 生成数 n | 重複の確率 p |
|---|---|
| 1,000,000 | 9.4 × 10⁻²⁶ |
| 1,000,000,000 | 9.4 × 10⁻²⁰ |
| 1,000,000,000,000 | 9.4 × 10⁻¹⁴ |
| 1,000,000,000,000,000 | 9.4 × 10⁻⁸ |
10億個作っても確率は約9.4 × 10⁻²⁰です。確率が50%に達するには約2.7 × 10¹⁸個が必要で、毎秒10億個を作り続けても約86年かかります。 10億分の1以下に抑えられる上限は約1.0 × 10¹⁴個です。ただしこれは乱数源が一様で予測不能であることが前提です。重複が現実に起きるのは、弱い乱数やクローンされた仮想マシン、値を使い回すバグが原因で、計算上の確率が理由ではありません。 そのため、クライアントが作るIDを保存する列にはデータベースでUNIQUE制約を付けてください。
データベースの主キーに使うとき
乱数のv4はどこでも調整なしに作れて便利ですが、順序付きインデックスでは挿入位置が分散し、ページ分割が増えます。連番や時刻順のIDは末尾に追記されるだけで済みます。時刻順のv7はこの問題を和らげますが、作成時刻が値に現れ、このツールが作るのはv4だけです。 保存形式も重要で、36文字の文字列は36バイト、専用のuuid型や16バイトのバイナリなら16バイトです。10億行なら1コピーあたり20GBの差になり、エンジンによっては二次インデックスにも主キーが複製されます。可能なら専用の型を使いましょう。
計算例
例1(読み取り)。 f47ac10b-58cc-4372-a567-0e02b2c3d479 の3番目のグループは4で始まるのでv4、4番目のグループはa(2進で1010)で始まり上位2ビットが10なので標準バリアントです。上の判定欄に貼ると同じ結果が出ます。 固定されるのは4+2ビットなので、乱数部は 128 − 6 = 122ビットです。
例2(トークン)。 nバイトのHEXトークンは文字数が2n、ビット数が8nです。16バイトは32文字・128ビット、32バイトは64文字・256ビットなので、16バイトのトークンはv4(122ビット)より探索空間がわずかに大きく、形式にも縛られません。
よくある誤解と注意点
- UUIDは秘密ではありません。識別用であり、アクセス権の証明にはなりません。URLが漏れた瞬間に開けてしまう作りでは危険なので、権限チェックはサーバーで行います。
- パスワード再設定やセッションに使わない。専用のランダムトークン(16バイト以上、できれば32バイト)を使い、保存するのはそのハッシュだけにします。
- 大文字小文字。規格は出力を小文字、入力は両方受け付けると定めています。比較・保存前に統一してください。
- 数値型に入れない。128ビットはJavaScriptのNumberにも64ビット整数にも収まりません。文字列かバイナリで扱います。
- GUIDとの違い。Microsoft系の呼び名で同じ形式です。生のバイト列をやり取りするときは、先頭3フィールドをリトルエンディアンで持つ実装があることに注意してください。
HEXトークンを使う場面
APIキー、セッションID、招待リンク、CSRFトークンなど、推測されては困る値に使います。crypto.getRandomValues()で得たバイトを1バイトあたり16進2文字で表します。 利用者には一度だけ見せ、保存はハッシュにするのが安全です。ハッシュの確認にはハッシュ生成ツール、人が入力するパスワードにはパスワード生成ツール、UUIDの形式を検証する正規表現の試験には正規表現テスターが使えます。
よくある質問
生成した値はサーバーに送られたり保存されたりしますか?
いいえ。ブラウザのWeb Crypto APIで作られ、送信も保存もされません。ページを再読み込みすると別の値になります。
UUIDが重複することはありますか?
理論上はゼロではありませんが、v4は122ビットの乱数空間なので、10億個でも上の近似式で約9.4 × 10⁻²⁰です。実際のリスクは乱数源の質にあるため、重要な列にはUNIQUE制約を付けましょう。
UUIDとGUIDは違うものですか?
同じ128ビット形式で、GUIDはMicrosoft系での呼び名です。バイト列を直接やり取りする場合のみ、バイト順に注意が必要です。
ハイフンを外したり大文字にしても同じUUIDですか?
同じです。値の本体は16進数32桁で、ハイフンや波括弧は表記上の装飾、大文字小文字も意味は変わりません。保存形式を1つに決めて、比較前にそろえてください。
APIキーにはUUIDとHEXトークンのどちらが向きますか?
HEXトークンです。UUIDは一意性のための識別子で秘密向けではなく、形式が決まっていて乱数部も122ビットしかありません。UUIDは名前付け、トークンは認証に使い分けましょう。
関連ツール
- ハッシュ生成ツール - トークン保存用のSHAダイジェスト
- パスワード生成ツール - 人が入力するパスワード
- 乱数ジェネレーター - 範囲を指定した乱数
- 正規表現テスター - UUIDの形式検証