Parallels Desktop 25%OFFクーポンありますCheck!

【PHP入門】ランダムな文字列(パスワード、トークン)を生成する方法

PHPでランダムな文字列を生成する方法

パスワードやトークンを生成する際、どのような方法があるでしょうか?また、その方法は安全かつユニークなものでしょうか?本記事では、PHPを使ってランダムな文字列を生成する方法を紹介します。

ユニークなIDやトークンの生成に使える関数や、安全なパスワードを生成するための方法など、初心者でも簡単に理解できるように解説していきます。あなたのWebアプリケーションやサービスでセキュリティを向上させるためのヒントになれば幸いです。

目次

ユニークなIDとトークンとパスワードの違い

この記事では、ユニークなID、トークン、パスワードの3つに分けて、生成方法を解説します。これらは用途や求められるセキュリティレベルが違うので、それぞれの生成方法も異なります。

ユニークなIDユーザIDや商品IDとして使う文字列です。
一意な識別子である必要がありますが、第三者に推測されても問題ありません。なんなら積極的に見せていきたいですね(なぜ)。
トークン認証トークン、アクセストークン、CSRFトークンなどとして使う文字列です。
1回限りの使い捨てだったり、有効期限が短めに設定されることが多いです。
認証されたユーザを識別するために使うことが多いので、第三者に推測されないものでなければなりません。
パスワードユーザIDとパスワードのペアで、ユーザ認証をするために使われます。
一度設定されると変更されずに長期間使われることが多いので、最も高いセキュリティレベルが求められます。

むかしむかし、パスワードは定期的に変更するのが吉とされていました。現在は、十分な強度のパスワードを設定していれば定期的な変更は不要とされています。アメリカの偉い人がそう言っています。

ユニークなIDの生成

この章で紹介する関数が生成する値は、暗号学的に安全ではありません。つまり、第三者によって推測される可能性があります。なので、パスワードやトークンを生成する目的で使わないでください。

uniqid()

PHPのuniqid()関数を使うことで、他と重複しないIDを簡単に生成することができます。ユニークなIDを生成するときの最もおすすめな方法です。

$unique_id = uniqid();
echo $unique_id;

/* 出力の例
641dcf0364f10
*/

このように、アルファベットの小文字と数字を使用した13文字の文字列が生成されます。

uniqid()関数は、引数にプレフィックスを指定することができます。プレフィックスを付けることで、生成されるIDの先頭に指定した文字列が付加されます。

$unique_id = uniqid('user_');
echo $unique_id;

/* 出力の例
user_641dd07c096c5
*/

いい感じにユーザIDっぽくなりました。

uniqid()関数は、マイクロ秒単位のUNIXタイムスタンプに基づいた一意なIDを生成します。つまり、生成する時間が異なれば一意性は保証されるということです。

mt_rand()

PHPのmt_rand()関数は、メルセンヌ・ツイスター乱数生成器を介して乱数値を生成するために使われます。急に難しいことを言い出したと思ったでしょう? 安心してください、私もよく分かってません。mt_rand()関数は、指定した範囲内のランダムな整数を生成することができます。

$random_number = mt_rand(1, 100);
echo $random_number;

/* 出力の例
56
*/

上記のコードを実行すると、1から100の範囲内のランダムな整数が出力されます。これだけではIDとして使えないので、次のようなコードを書きます。

$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = 16;
$random_string = '';
for ($i = 0; $i < $length; $i++) {
  $random_string .= $characters[mt_rand(0, strlen($characters) - 1)];
}
echo $random_string;

/* 出力の例
8K2g2aYZtCVRBGEl
*/

上記のコードは、変数$charactersに入っている文字列からn番目の文字を取り出して、変数$random_stringに結合代入するという処理を$length回繰り返します。n番目の部分をmt_rand()関数で生成しています。

uniqid()よりもややこしいですが、使用する文字種と文字列の長さを自由に設定できるのが利点です。また、一意性は保証されないので、過去に生成したIDと重複していないかチェックする処理が別途必要です。

str_shuffle()

PHPのstr_shuffle()関数は、文字列をランダムに並び替えるために使われます。

$string = 'My mother is a bitch.';
$shuffled_string = str_shuffle($string);
echo $shuffled_string;

/* 出力の例
htc tbas M.eyomhii r
*/

str_shuffle()関数を使って、ランダムな文字列を生成する場合は、次のようなコードになります。

$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = 16;
$random_string = str_shuffle($characters);
$random_string = substr($random_string, 0, $length);
echo $random_string;

/* 出力の例
Age6XtQFqG4rmSjE
*/

上記のコードは、変数$charactersに入っている文字列をシャッフルして、1文字目から$length文字目までの文字列を変数$random_stringに代入します。

ただし、この方法だと同じ文字が2回以上使われることはありません。気に喰わねえなと思ったあなたのために改良版のコードをお届けします。

$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = 16;
$random_string = str_shuffle(str_repeat($characters, 5));
$random_string = substr($random_string, 0, $length);
echo $random_string;

/* 出力の例
QQ1LhhQjMPZNIqHP
*/

str_repeat()関数を使って$charactersを5回繰り返して、同じ文字が出現するようにしました。

この方法も一意性は保証されないので、過去に生成したIDと重複していないかチェックする処理が別途必要です。

トークンの生成

random_bytes()

PHPのrandom_bytes()関数は、ランダムなバイト列を生成するために使用されます。この関数は、暗号学的に安全です。つまり、生成されたデータは第三者に推測されません。トークンを生成するときの最もおすすめな方法です。

random_bytes()関数を使って、ランダムな文字列を生成するコードは次のようになります。

$random_string = bin2hex(random_bytes(16));
echo $random_string;

/* 出力の例
c8f2ee2961f0d1d86f27bfd9180f1d71
*/

random_bytes()関数は、引数で長さを指定します。ここでは16にしました。random_bytes()関数はバイト列(バイナリ)を返すので、バイナリデータを16進数に変換するbin2hex()関数を使って文字列に変換し、変数$random_stringに代入します。

バイナリから文字列に変換するときに長さが2倍になるので、生成される文字列は32文字になります。

random_bytes()関数はPHP7.0以上で利用可能です。PHP7.0未満では、代わりにopenssl_random_pseudo_bytes()関数を使うことができます。

$random_string = bin2hex(openssl_random_pseudo_bytes(16));
echo $random_string;

/* 出力の例
f88bdb85ab38198dbd06b39359aafc04
*/

random_int()

PHPのrandom_int()関数は、指定された範囲内でランダムな整数を生成するために使用されます。この関数は、暗号学的に安全です。

$random_int = random_int(1, 100);
echo $random_int;

/* 出力の例
79
*/

上記のコードを実行すると、1から100の範囲でランダムな整数が出力されます。

random_int()関数を使って、ランダムな文字列を生成するコードは次のようになります。

$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = 16;
$random_string = '';
for ($i = 0; $i < $length; $i++) {
  $random_int = random_int(0, strlen($characters) - 1);
  $random_string .= $characters[$random_int];
}
echo $random_string;

/* 出力の例
1iPUZj8lM6ilGEFS
*/

上記のコードは、変数$charactersに入っている文字列から$random_int番目の文字を取り出して、変数$random_stringに結合代入するという処理を$length回繰り返します。$random_int番目の部分をrandom_int()関数で生成しています。

random_bytes()よりもコードが複雑ですが、使用する文字種を自由に設定したいならばこちらを使いましょう。

パスワードの生成

パスワードは、ユニークIDやトークンと比べて最も高いセキュリティレベルが求められます。優秀なパスワードは次の条件を満たすものです。

  • 十分な長さを持つ。長ければ長いほど安全。10文字以上がベター。
  • 複数の文字種を使う。アルファベットの大文字、小文字、数字、記号をすべて使っていたら合格。
  • ランダムな文字列。辞書に載っているような単語は使わない。

この条件を満たすパスワードを生成するPHPのコードを簡単に書くことはできません。なので、私が関数を作成しました。ご査収ください。

以下の要件で作成しました。

  • 文字数を指定できる。
  • 使用する文字種を指定できる。
  • 指定した文字種が必ず使われるようにする。
function generate_password(int $length = 16, array $char_type = ['uppercase', 'lowercase', 'number', 'symbol']): string
{
  //文字の配列
  $char_types = [
    'uppercase' => range('A', 'Z'),
    'lowercase' => range('a', 'z'),
    'number' => range('0', '9'),
    'symbol' => str_split('!"#$%&\'()*+,-./:;<=>?@[\\]^_`~')
  ];

  //長さ($length)が不正な場合は例外を投げる
  if ($length < 8 || $length > 128) {
    throw new InvalidArgumentException('Invalid argument.');
  }

  //文字種($char_type)が不正な場合は例外を投げる
  foreach ($char_type as $type) {
    if (!array_key_exists($type, $char_types)) {
      throw new InvalidArgumentException('Invalid argument.');
    }
  }

  //使用する文字種に基づいて文字を配列に格納する
  $characters = array();
  foreach ($char_type as $type) {
    $characters = array_merge($characters, $char_types[$type]);
  }

  //パスワードを生成する 指定した文字種が含まれていなければ生成し直す
  $password = '';
  do {
    $password = '';
    $max = count($characters) - 1;
    for ($i = 0; $i < $length; $i++) {
      $password .= $characters[random_int(0, $max)];
    }
  } while (
    (in_array('uppercase', $char_type) && !preg_match('/[A-Z]/', $password)) ||
    (in_array('lowercase', $char_type) && !preg_match('/[a-z]/', $password)) ||
    (in_array('number', $char_type) && !preg_match('/\d/', $password)) ||
    (in_array('symbol', $char_type) && !preg_match('/[\W]/', $password))
  );

  return $password;
}
使い方
  • 第一引数でパスワードの長さを指定します。8 - 128の範囲で指定できます。初期値は16です。
  • 第二引数で使用する文字種を配列で指定します。使用できる文字種は次のとおりです。初期値は全指定です。
    • uppercase: アルファベットの大文字
    • lowercase: アルファベットの小文字
    • number: 数字
    • symbol: 記号

使用例です。

//引数指定なし(16文字、すべての文字種)
$password = generate_password();
echo htmlspecialchars($password);

/* 出力の例
b3M6[kDFc?lo/~<'
*/

//10文字、すべての文字種
$password = generate_password(10);
echo htmlspecialchars($password);

/* 出力の例
Z&v6vD"0I&
*/

//12文字、記号以外を使用
$password = generate_password(12, ['uppercase', 'lowercase', 'number']);
echo $password;

/* 出力の例
zRLq8eyO7zTb
*/

//64文字、数字のみを使用
$password = generate_password(64, ['number']);
echo $password;

/* 出力の例
2723435792348763680974380405276932483622390220412415790485401898
*/

さいごに

本記事ではPHPでランダムな文字列を生成する方法を紹介しました。目的やセキュリティ上の観点に応じて生成方法を選択する必要があることがおわかりいただけたかと思います。これであなたも「password123」を卒業できるのではないでしょうか。

でわでわ

プログラミング学習、お疲れさまです

プログラミングの独学に行き詰まっていませんか?誰かに相談したい、もっと効率よく学びたいなら、プログラミングスクールを検討してみてください。

PHPでランダムな文字列を生成する方法

この記事が気に入ったら
いいね または フォローしてね!

シェアしてね

コメント

コメントする

目次