はじめに
一時ファイル名の生成、フォームのCSRFトークンの元データ、リクエストの追跡ID、キャッシュキーなど、PHPアプリケーションでは「一意な文字列」が必要になる場面が数多くあります。そうした用途で手軽に使えるのが uniqid() 関数です。
ただし、この関数名の “unique”(一意)という言葉から、多くの初心者が「絶対に重複しない、暗号学的に安全なIDが生成できる」と誤解してしまいます。実際には uniqid() は現在時刻をベースにした、予測可能性のある文字列を生成する関数であり、セキュリティトークンや真の一意性が求められる場面(データベースの主キー代わりなど)には不向きです。本記事では、uniqid() の正しい仕組みと適切な使い所、そして誤用を避けるための代替手段について詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | uniqid() |
| 所属拡張 | コア関数(標準で常に利用可能) |
| シグネチャ | uniqid(string $prefix = "", bool $more_entropy = false): string |
| 引数1 | $prefix — 生成される文字列の先頭に付与する接頭辞 |
| 引数2 | $more_entropy — trueにするとより長く、追加のエントロピーを含む文字列になる |
| 戻り値 | 現在時刻をベースにした一意な文字列(13文字、more_entropy時は約23文字) |
| 対応バージョン | PHP 4以降 |
| 生成の基準 | マイクロ秒単位の現在時刻 |
| セキュリティ用途 | 非推奨(予測可能なため) |
生成の仕組み(イメージ図)
uniqid() の内部処理イメージ
現在時刻(マイクロ秒単位)
例: 2026-08-05 10:23:45.678912
│
▼
時刻を16進数文字列に変換
│
┌──────────┴──────────────┐
│ $prefix を先頭に付与 │
│ $more_entropy=trueなら │
│ 追加のランダム性を末尾に付与 │
└──────────┬──────────────┘
▼
"68931a2b1c4d3" のような文字列
▲
└─ 時刻ベースなので、生成時刻が近いIDは
見た目も似通ってしまう(予測されやすい)
ポイントは、uniqid() が内部的に現在時刻(マイクロ秒精度)をエンコードしているだけという点です。同じサーバー上で短時間に大量生成すると理論上は衝突しにくいものの、生成された文字列から「おおよその生成時刻」が推測できてしまいます。これはセキュリティトークンとして使う上で致命的な弱点になります。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicUniqidDemo
{
public function generate(): string
{
// 接頭辞なし、追加エントロピーなしのデフォルト呼び出し
return uniqid();
}
}
$demo = new BasicUniqidDemo();
echo $demo->generate() . PHP_EOL; // 例: "68931a2b1c4d3"
例2:接頭辞を付けて用途がわかるIDを生成する
<?php
class PrefixedIdGenerator
{
/**
* 用途ごとに接頭辞を変えることで、
* ログ上でIDの種類を判別しやすくする
*/
public function generateForUpload(): string
{
return uniqid('upload_');
}
public function generateForSession(): string
{
return uniqid('sess_', true); // more_entropyを併用
}
}
$generator = new PrefixedIdGenerator();
echo $generator->generateForUpload() . PHP_EOL; // "upload_68931a2b1c4d3"
echo $generator->generateForSession() . PHP_EOL; // "sess_68931a2b1c4d3.12345678"
例3:一時ファイル名を安全に生成するクラス
<?php
class TempFileNamer
{
/**
* アップロード処理などで、一時的に使うファイル名の
* 衝突を避けるために利用する(あくまで衝突回避目的)
*/
public function generateTempFileName(string $extension): string
{
$id = uniqid('tmp_', true);
// ドットを含む可能性があるためファイル名として整形する
$safeId = str_replace('.', '_', $id);
return $safeId . '.' . ltrim($extension, '.');
}
}
$namer = new TempFileNamer();
$fileName = $namer->generateTempFileName('jpg');
echo $fileName . PHP_EOL;
echo sys_get_temp_dir() . '/' . $fileName . PHP_EOL;
例4:uniqidの予測可能性を確認するデモ(セキュリティ教育用)
<?php
class PredictabilityDemonstrator
{
/**
* 短時間に連続生成したuniqid()が
* どれだけ似通った値になるかを確認する
*/
public function demonstrate(int $count = 5): array
{
$ids = [];
for ($i = 0; $i < $count; $i++) {
$ids[] = uniqid();
// 生成間隔を意図的に空けない(近い時刻での生成を再現)
}
return $ids;
}
}
$demo = new PredictabilityDemonstrator();
print_r($demo->demonstrate());
// 出力例(先頭部分がほぼ共通することが確認できる)
// ["68931a2b1c4d3", "68931a2b1c4d5", "68931a2b1c4d8", ...]
例5:安全なトークン生成のための正しい代替実装
<?php
class SecureTokenGenerator
{
/**
* セキュリティトークンにはuniqid()ではなく、
* 暗号学的に安全な random_bytes() を使うべき
*/
public function generateCsrfToken(): string
{
// 32バイトのランダムなバイト列を16進数文字列に変換
return bin2hex(random_bytes(32));
}
public function generateApiKey(): string
{
// Base64URLエンコードでURLセーフな文字列にする例
return rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '=');
}
}
$secureGenerator = new SecureTokenGenerator();
echo $secureGenerator->generateCsrfToken() . PHP_EOL;
echo $secureGenerator->generateApiKey() . PHP_EOL;
例6:ログのトレースIDとして活用するクラス
<?php
class RequestTraceIdManager
{
private static ?string $traceId = null;
/**
* 1リクエストの中で共通のトレースIDを使い、
* ログを追跡しやすくする(セキュリティ用途ではない一時的な識別子)
*/
public static function getTraceId(): string
{
if (self::$traceId === null) {
self::$traceId = uniqid('req_', true);
}
return self::$traceId;
}
}
class Logger
{
public function info(string $message): void
{
$traceId = RequestTraceIdManager::getTraceId();
echo "[{$traceId}] INFO: {$message}" . PHP_EOL;
}
}
$logger = new Logger();
$logger->info('リクエスト処理を開始しました');
$logger->info('データベースへの問い合わせが完了しました');
例7:uniqidとより堅牢な代替手段の比較実装
<?php
class IdGenerationComparator
{
/**
* 用途に応じた3つのID生成方法を比較する
*/
public function compare(): array
{
return [
// 衝突回避が目的の軽量な一時識別子
'uniqid' => uniqid('', true),
// ランダム性が必要な場合の安全な代替
'random_bytes' => bin2hex(random_bytes(16)),
// 標準化された一意識別子が必要な場合(UUID v4相当の簡易実装)
'uuid_v4_like' => $this->generateUuidV4(),
];
}
private function generateUuidV4(): string
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
}
$comparator = new IdGenerationComparator();
print_r($comparator->compare());
関連関数との比較
| 関数 | 役割 | uniqidとの違い |
|---|---|---|
uniqid() | 現在時刻ベースの一意な文字列を生成 | 本記事の対象。予測可能性がありセキュリティ用途には不向き |
random_bytes() | 暗号学的に安全なランダムバイト列を生成 | 予測不可能性が保証されており、トークン生成に適する |
random_int() | 暗号学的に安全なランダム整数を生成 | 文字列ではなく整数の乱数が必要な場合に使う |
bin2hex() | バイナリデータを16進数文字列に変換 | random_bytes()の結果を扱いやすい文字列にする際によく組み合わせる |
session_create_id() | セッションID用の安全な文字列を生成 | セッションIDに特化した、より安全性の高い専用関数 |
よくある落とし穴(注意点)
- セキュリティトークンとして使ってしまう
uniqid()の値は現在時刻から逆算・推測されうるため、CSRFトークン、パスワードリセット用トークン、APIキーなどのセキュリティ用途には絶対に使うべきではありません。これが最も重要な注意点です。 - 「絶対に重複しない」と過信する
more_entropyを指定しない場合、同一マイクロ秒内に複数回呼び出すと理論上は同じ値が生成される可能性があります。真の一意性(データベースの主キーなど)が必要な場合はUUIDやデータベースの自動採番機能を使うべきです。 more_entropyの意味を誤解するtrueを指定すると生成される文字列は長くなりランダム性も多少向上しますが、それでも暗号学的に安全なレベルには達しません。「より安全になった」と誤解しないよう注意しましょう。- 戻り値の形式に依存しすぎる
uniqid()は英数字とピリオド(more_entropy=trueの場合)を含む文字列を返しますが、桁数や形式は将来的に変わる可能性があるため、固定長を前提としたパース処理などは避けるべきです。 - 分散システムでの一意性を過信する 複数のサーバーで同時に
uniqid()を実行した場合、サーバー間でのクロック同期のずれなどにより、時刻ベースの一意性の保証はさらに弱くなります。分散環境では専用のID生成サービスやUUIDライブラリの利用が推奨されます。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | 現在時刻(マイクロ秒)をベースにした、比較的一意性の高い文字列を生成する |
| 主な用途 | 一時ファイル名、ログのトレースID、衝突回避が目的の軽量な識別子など |
| 適さない用途 | セキュリティトークン、CSRFトークン、真の一意性が求められる主キーなど |
| 予測可能性 | 時刻ベースであるため、生成時刻がある程度推測できてしまう |
| 安全な代替手段 | random_bytes()(トークン生成)、UUID(真の一意識別子) |
uniqid() は名前とは裏腹に「絶対的な一意性」や「セキュリティ」を保証する関数ではありません。あくまで衝突しにくい軽量な識別子を手軽に生成するための関数として位置づけ、セキュリティが関わる場面では必ず random_bytes() などの暗号学的に安全な関数を使うようにしましょう。
