PHPでuniqid()を安全に使うために試行錯誤してみる

by

カテゴリー:

PHPでユニークなIDを生成するためにそれっぽい名前の uniqid() がありますが、こちらは戻り値の一意性を保証されないものになっています。

警告

この関数は、戻り値の一意性を保証するものではありません。

https://www.php.net/manual/ja/function.uniqid.php

つまり、ユニークなIDを取得するには相応しくない関数ということになります。

この記事では、PHPで uniqid() を使う方法を、安全性・重複リスク・代替手段の観点から整理します。基本的な使い方から、第2引数の more_entropyrandom_bytes() との違い、UUIDとの使い分けまでまとめました。

  • PHP uniqid() の基本的な使い方と戻り値
  • 重複リスクと、more_entropy で一意性を高める方法
  • 暗号学的に安全な random_bytes() / random_int() との違い
  • UUIDや他のランダムID生成との使い分け

先に結論だけ知りたい方向けに、PHPで uniqid() を使う最も基本的な形をまとめておきます。引数なしで呼び出せば、13文字のユニークな文字列がそのまま返ります。

<?php

$id = uniqid(); // string(13) "64f1fc67a9236"

ただし uniqid() は一意性を保証しないため、用途によっては引数の指定や別の関数が必要です。以下でPHPの uniqid() の使い方と注意点を順に見ていきます。

uniqid() 関数の構文・引数・戻り値

関数の使い方を先に確認したい方向けに、uniqid() の構文・引数・戻り値・注意点を短くまとめます。基本の構文(シグネチャ)は次のとおりで、どちらの引数も省略できます。

uniqid(string $prefix = "", bool $more_entropy = false): string

引数は次の2つです。

引数既定値役割
$prefixstring""戻り値の先頭に付ける接頭辞。複数ホストで同時にIDを作るときの識別などに使う
$more_entropyboolfalsetrue にすると末尾に乱数を追加し、一意になる可能性を高める

戻り値は16進数を含む文字列です。引数なしでは 13文字$more_entropytrue にすると 23文字(接頭辞を付けた場合はその分だけ長くなる)になります。

注意点として、uniqid() は現在時刻(マイクロ秒)を元にするため戻り値の一意性は保証されず、値が推測されやすいので暗号用途には使えません。セキュアな値が必要なときは後述の random_bytes() やUUIDを使います。

PHP uniqid() の基本的な使い方

<?php

// 通常の使用
uniqid(); // string(13) "64f1fc67a9236"

// プレフィックスを指定する場合
$prefix = 'myPrefix_';
uniqid($prefix); // string(22) "myPrefix_64f1fc67a924f"

// マイクロ秒単位のタイムスタンプを含める場合
uniqid($prefix, true); // string(32) "myPrefix_64f1fc67a92539.84035833"

uniqid() は実行時のマイクロ秒で 13文字の文字列を返します。

uniqid() の重複リスクとmore_entropyでの対策

マイクロ秒を元に生成するため、実行時のタイミングでは同じ文字列を出力されてしまいます。

第1引数の $prefix は文字列の前半に指定した値を結合します。この指定が有効なパターンとしては複数ホストで同時にIDを作成するシステム構造に使用する事が想定されます。

第2引数には $more_entropy として true を渡すことで新たな乱数を結合して結果が一意になる可能性を高めます。こちらの引数が指定された場合には 23文字になります。

つまり、複数ホストでの実行があれば $prefix でホスト名を記載し、第2引数の $more_entropytrue にする事でマイクロ秒以外の乱数を結合することでユニークな可能性が高いIDを取得することができます。

PHP8系では複数回実行しても前回の実行を記録しており、なるべく重複しない値を取得する実装になっているようです。

参考: https://medium.com/paronym/phpのuniqid-を連続で呼んでも重複しないのは何故か-e9adf48f8ab0

uniqid() を使うべきか|古い関数である点の注意

uniqid() でも引数を適切に指定することで、ユニークな可能性が高いIDが取得出来る事を解説してきました。

ですが、この uniqid() はPHP4系から存在している関数になります。PHP4系は2000年5月にリリースされたものなので現在 (2023年9月) で23年前の実装です。

かなり昔に実装された物であるということと、下位互換のためにそれほど出力結果は変わらないはずなので、そのまま関数が残っているのではと感じています。

現在、一般的に使われている暗号手法でも量子コンピューターのように計算速度が飛躍的に向上してしまうと脆弱になってしまうため、23年の期間もあれば技術が陳腐化してしまいます。

新たなユニークIDを生成する手法なども確立しており、UUIDなどの他の方法でユニークIDを生成する方が素早く確実な物を取得することが可能になっています。

古いから使わない方がいいとは思いませんが、少なくとも暗号化のためのユニークIDを生成するのには使わない方が良いです。

  • 暗号学的なセキュアな値は必要ない
  • 古くからある枯れた技術だからこその安全性

を重視したユニークIDを取得したい場合に、uniqid() は選択肢に上がる方法かと考えます。

割と用途が限定的な物になってしまうので、フレームワークなどでUUIDの生成ができるのであればUUIDを使用したり、別の手法を使用してもいいかもしれません。

uniqid() の代替|random_bytes()で安全にランダムIDを生成する

uniqid() が苦手とする暗号学的にセキュアな方法でランダムな文字列や数値を取得する方法を紹介します。

<?php

$bytes = random_bytes(32);
bin2hex($bytes); // string(64) "b087a977c9b91d1b1d44b4d5d0fce9596a6cfa877ea2ae9f9098ab9967a12a17"

random_int(100, 999); // int(311)
random_int(-1000, 0); // int(-862)

こちらの random_bytes()random_int() を使用することで暗号学的にセキュアな値を取得することが可能になります。

他にも、UUIDを使用する方法があり、こちらの記事にもまとめました。

uniqid()・random_bytes()・UUIDの違いと使い分け

ここまで紹介した3つの生成方法を、用途ごとに整理すると次のようになります。目的に合わない方法を選ぶと、重複やセキュリティの問題につながるため、必要な性質から逆算して選ぶのがおすすめです。

生成方法一意性暗号学的な安全性主な用途
uniqid()タイミング次第で重複あり(more_entropy で軽減)なし一時的なファイル名やキーなど、推測されても問題ない値
random_bytes() / random_int()実質的に衝突しにくいありセッションIDやトークンなど、外部から推測されては困る値
UUID (v4)実質的に一意v4は乱数ベースで推測されにくい分散環境でのレコードID、外部公開する識別子

まとめると、推測されても問題ない一時的なIDなら uniqid()、セキュアな値が必要なら random_bytes()、分散環境でも一意な識別子が欲しいならUUID、という使い分けになります。

終わりに

以上、PHPの uniqid() についてまとめてみました。

ユニークIDを使いたい用途を正しく整理し、暗号学的にセキュアな値が必要な場面(セッションIDなどの、外部から参照されて変更が可能な値等)には uniqid() は不向きな物でした。

永続的ではなく、一時的なユニークIDとして使用するのであれば uniqid() はアリかと感じています。が、引数の指定は適切に設定しましょう。

お疲れ様でした。

ホーム » 記事一覧 » 技術メモ » 開発 » PHP開発 » PHPでuniqid()を安全に使うために試行錯誤してみる

投稿者

ここまで読んでいただき、ありがとうございます。

このサイトでは、私自身が関わっていることや、興味を持って試していることをそのまま置いています。

内容を見ていて、
「少し話を聞いてみたい」
「協業できる部分がありそう」
と感じる点があれば、下記のお問い合わせフォームからご連絡ください。状況を確認したうえで、必要な範囲でお返事します。

特に、以下のWordPressサイトに関する相談にも対応しているのでご興味ありましたらご相談ください。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です