質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.62%

  • PHP

    19786questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

ランダムな文字列を生成したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 470

tanakamaruki

score 66

<?php
 function generateSecureRandomString($length, $elem = false){
      if ($elem === false) {
        $elem = 'abcdefghijklmnopqrstuvwxyz';
      }
      $chars = preg_split('//', $elem, -1, PREG_SPLIT_NO_EMPTY);
      $chars = array_unique($chars);
      $bytes = getRandomBytes($length);
      if (strlen($bytes) <= 0) {
        echo 'パスワードの生成に失敗しました。<br>';
        return '';
      }
      $str = '';
      $charsLen = count($chars);
      for ($i = 0; $i < $length; $i++) {
        $str .= $chars[ord($bytes[$i]) % $charsLen];
      }
      return $str;
  }

  function getRandomBytes($length){
    $bytes = '';
    if (function_exists('openssl_random_pseudo_bytes')) {
      $bytes = openssl_random_pseudo_bytes($length);
    return $bytes;
  }

echo generateSecureRandomString(10) . '<br>';
echo generateSecureRandomString(15, 'abcde_!#$@') . '</p>';
?>


ランダムな文字列を生成するコードなのですが
$chars[ord($bytes[$i]) % $charsLen]
の部分はどういう意味になるのでしょうか?
なぜ$charsの配列の要素の数で割っているのでしょうか?
$bytes[$i]で$bytesの要素を一つずつ取り出しているのになぜordで先頭の文字の ASCII値を取得する必要があるのでしょうか?要素を一つだけ取り出しているということは、取り出された要素は一つしかない以上、その要素は必ず先頭になるのでordで先頭を指定する必要はないのではないのでしょうか?意味がよくわかりません。

またpreg_split('//は何にでもマッチするという意味になるのでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ockeghem

    2017/12/12 07:14

    このソースコードの出典はどちらでしょうか? PHP逆引きレシピ 第2版 P162のコードに酷似していますが、微妙に違いますね。

    キャンセル

回答 1

checkベストアンサー

+4

なーんか難しいんだか変なんだかよくわからない事してるコードですね。

$chars[ord($bytes[$i]) % $charsLen]
の部分はどういう意味になるのでしょうか?

これはですね、ややこしいです。まず、$charsですが、これは

$chars = preg_split('//', $elem, -1, PREG_SPLIT_NO_EMPTY);

ここで生成していて、$elemに格納されている文字列を1文字ずつ区切って連番配列に格納しています。$elemが10文字あったら、10個要素のある配列になります。たとえば、ここから渡されてくる文字列などですね。

echo generateSecureRandomString(15, 'abcde_!#$@') . '</p>';

結果はこんなんになっています。

Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => d
    [4] => e
    [5] => _
    [6] => !
    [7] => #
    [8] => $
    [9] => @
)

そして、その長さは、

$charsLen = count($chars);

で、測っています。上記の場合、abcde_!#$@ で10文字ですから$charsLenには10が入ります。

次に$bytesですが、

$bytes = openssl_random_pseudo_bytes($length);

ここで生成していて「希望するバイト長($length)の疑似乱数バイト文字列を生成」しています。つまり、ここで命題の「ランダム」な要件を満たしているわけですね。

で、ここではランダムなバイト文字列を生成しただけなので、使用して良い文字とかは全く考慮されていないです。

ですから、最初に定義してある$chars配列の要素をこのランダムさを利用して必要文字数分だけ取り出そう、というわけです。

$bytesは長さ$lengthのバイト文字列だったので、for文を使って$bytesの最初の文字から最後の文字までを1つずつ取り出し、ord()します。

int ord(string $string)

$stringで渡された文字列の最初の1文字目のASCIIコードを整数で返す関数です。

ordで先頭を指定する必要はないのではないのでしょうか?

についてですが、ここでord()を使っている意図は先頭を取り出すことにあるのではなく、ランダムな整数 を得る事にあります。

取り出された要素は一つしかない以上、その要素は必ず先頭になるので

についてですが、ord()に渡しているのは$bytesではなく $bytes[$i] つまり文字列ではなくバイト文字1文字であって、for()ループの毎に、0文字目から最後の文字まで1文字ずつ変わっていくわけです。

※コマンドラインでこんな事を数回繰り返してみると、ランダムな数字が10個生成されていて、それが得られるのがわかります。バイト文字なので、0から255までのランダムな数字になります。

# php -r "$bytes = openssl_random_pseudo_bytes(10); for($i=0; $i < strlen($bytes); $i++){ echo ord($bytes[$i]) . \"\n\"; }"
130
160
69
116
180
250
89
198
21
248

さて、これで、ランダムな整数が取得できました。それがこの太字の部分です。

$chars[ ord($bytes[$i])  % $charsLen]

ですが、これではまだ、ランダムな整数なだけで文字ですらなく、使って良い文字も全く考慮していません。

そこでこのランダムな整数を、使ってよい文字の範囲に制限したいわけですが、ここで出てくるのが % です。余りを求める記号(合同算術のmodular)ですね。

ある整数Aを別の整数Bで割った余りは、必ず0から(B-1)の範囲に収まる、という性質を利用しています。

それが、以下の太字の部分です。$charsLenには、使用してよい文字種の個数が入っていましたね?

$chars[ ord($bytes[$i]) % $charsLen ]

これで、0から(使用してよい文字種の数 - 1)までのランダムな数 が求められたので、晴れて、使用してよいランダムな文字が求まるわけです。$charsは使用してよい文字の配列でしたね。

$chars[ord($bytes[$i]) % $charsLen]

あとはこれを、必要な文字数分だけfor文で繰り返すことで、必要な文字数の使用してよいランダムな文字列 を得るだけです。

というわけで、提示のコードは OpenSSLの乱数を使ったとても高度なランダム文字列の生成 を行うコードなわけですが、たかがランダム文字列を生成するためにこんな盛大に難解な事する必要どれ程あるの? っつーのが、正直な感想です。どうせ、仮パスワードに使うくらいの事なんでしょ?って。

またpreg_split('//は何にでもマッチするという意味になるのでしょうか?

preg_split() なので、マッチを求めているのではなく、分割しています。

パターンに空文字列( // )を指定しているので特定の文字・パターンに関係なく区切られ、結果は、文字が1文字ごとにバラバラにされた配列として得られます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/12 14:42

    回答して頂いてありがとうございます。

    >希望するバイト長($length)の疑似乱数バイト文字列を生成
    length=10だと0~255の数字が10個並んだものが生成されるのでしょうか?例10117610596155242653324127(1個1個をスペースで区切流と101 176 105 96 155 242 65 33 24 127の10個)
    で、それをforを使い一つずつ取り出しているということになるのでしょうか?
    試してみるとecho $bytesだと文字化けをしてforでechoさせてみると10個の数字が表示されていました。

    >ord()を使っている意図は先頭を取り出すことにあるのではなく、ランダムな整数 を得る事にあります
    ordは「$stringで渡された文字列の最初の1文字目のASCIIコードを整数で返す関数」であるのになぜordでランダムな整数を得る事ができるのでしょうか?forと$bytes[$i]で一個ずつランダムな数字を得ているの二、ordでさらにランダムな整数を得るという意味がよくわかりません

    %とpreg_splitの部分は理解ができました

    キャンセル

  • 2017/12/12 16:40

    >length=10だと0~255の数字が10個並んだものが生成されるのでしょうか?
    >例10117610596155242653324127(1個1個をスペースで区切流と101 176 105 96 155 242 65 33 24 127の10個)

    ちょっとちがいます。全くランダムな10bytesのバイナリ文字列データができます。

    文字列なので、その時点では数値とは別物です。

    キャンセル

  • 2017/12/12 16:42 編集

    >試してみるとecho $bytesだと文字化けをして

    バイナリ文字列なので、ASCIIとしてアルファベットや数字に置き換えられる値「以外」も含みます。
    例えば制御コードに相当する値や、ASCII文字が定義されていない領域の値もあるわけで、このため文字化けします。(正確には文字化けではないですけどね)

    キャンセル

  • 2017/12/12 16:45 編集

    >ordは「$stringで渡された文字列の最初の1文字目のASCIIコードを整数で返す関数」であるのになぜordでランダムな整数を得る事ができるのでしょうか?

    ord()ではランダムな整数は得られないです。
    ランダムな整数はopenssl_random_pseudo_bytes()で既に生成されています。ランダムな10文字分のバイナリ文字列ですね。
    で、これは文字であってそのままでは数値として扱えないため、このバイナリ文字列をASCIIコードの文字列として扱えばASCIIコードのコード番号を数値として得られる便利な関数ord()があるので、これを利用しましょう、というわけです。

    キャンセル

  • 2017/12/12 16:47

    Array
    (
    [0] => a
    [1] => b
    [2] => c
    [3] => d
    [4] => e
    [5] => _
    [6] => !
    [7] => #
    [8] => $
    [9] => @
    )

    キャンセル

  • 2017/12/12 16:51 編集

    上記配列から文字1文字を取り出すには、添え字である番号を指定する必要があります。
    全部で10文字取り出さなければいけません。
    その文字はランダムでなければいけません。

    ですから、まず、ランダムな文字列10byteを生成しています。
    次に、そのランダム文字を端から1byte(1文字)取り出して数値にします。
    その数値を10で割って余りを求めることで0から9までの数値を得ます。
    もともとランダムな数を10で割ったので、余りも当然ランダムです。
    ですから、ランダムであるこの余りを、上記配列から取り出す文字の添え字にしましょう。
    これを必要な文字数分だけ繰り返してすべての文字を連結しましょう。
    その際、最初のランダムな文字列から取り出す文字は2byte目、3byte目とずらして行けばよいのです。

    これで、文字種が限定されたランダムな10文字の完成です。

    キャンセル

  • 2017/12/12 17:00 編集

    ものすごく簡単なランダム文字列の実装としては

    $str = "";
    for($i = 0; $i < 10; $i++)
    {
    $idx = mt_rand(0, 9);
    $str .= $chars[$idx];
    }

    で済んでしまうでしょうね。

    ただ、この場合はランダムな数値の生成が必要な文字数回繰り返して行われます。

    キャンセル

  • 2017/12/12 17:02 編集

    提示されたコードでは、ランダムな要素の生成を最初に1回だけ、必要な文字数分全部計算してしまっています。

    そのあとで、1つずつ取り出して最終的に利用する文字を決めています。

    この場合、ランダムに生成したのは文字であって数値ではないため、文字を数値に置き換える処理がどうしても必要になり、その処理が結構難解だった、という感じですね。

    キャンセル

  • 2017/12/13 23:21

    ありがとうございます。
    ランダムな文字列を作成し、その文字列の先頭をASCIIの数字に変換し、余りで取り出すという流れは理解ができました。

    バイナリ文字列の意味がググってみてもよくわからないのですが詳しいサイトをご存知であれば教えていただけないでしょうか?文字コードの全体的な知識を取得したいです。

    キャンセル

  • 2017/12/15 02:50

    そうですね、バイナリ文字列という言葉はマニュアルにはでてくるんですが、それが何であるかの説明はあまり見ないので、僕も少し自信ないのですが、
    おそらく、C言語でいうところの unsigned char[] でしょうね。
    https://qiita.com/huhu/items/453ad5875e0da641f629

    取れる値は0~255ですが、これは文字コードとしての0~255であって、数値ではないです。
    ですから、そのままでは数値に対して演算を行うことはできないです。

    int a = 10;
    unsigned char b[] = "A"; //← ASCII では99
    printf("a * b = %d", a * b);

    Main.c:6:28: error: invalid operands to binary expression ('int' and 'unsigned char *')
    printf("a * b = %d", a * b);
    ~ ^ ~
    1 error generated.

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • PHP

    19786questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。