質問: openssl_random_pseudo_bytes($length); return $bytes; } echo generateSecureRandomString(10) . ''; echo generateSecureRandomString(15, 'abcde_!#$@') . ''; ?> ``` ランダムな文字列を生成するコードなのですが $chars[ord($bytes[$i]) % $charsLen] の部分はどういう意味になるのでしょうか? なぜ$charsの配列の要素の数で割っているのでしょうか? $bytes[$i]で$bytesの要素を一つずつ取り出しているのになぜordで先頭の文字の ASCII値を取得する必要があるのでしょうか?要素を一つだけ取り出しているということは、取り出された要素は一つしかない以上、その要素は必ず先頭になるのでordで先頭を指定する必要はないのではないのでしょうか?意味がよくわかりません。 またpreg_split('//は何にでもマッチするという意味になるのでしょうか?
回答: なーんか難しいんだか変なんだかよくわからない事してるコードですね。 > > $chars[ord($bytes[$i]) % $charsLen] > > の部分はどういう意味になるのでしょうか? > > > これはですね、ややこしいです。まず、$charsですが、これは ``` $chars = preg_split('//', $elem, -1, PREG_SPLIT_NO_EMPTY); ``` ここで生成していて、$elemに格納されている文字列を1文字ずつ区切って連番配列に格納しています。$elemが10文字あったら、10個要素のある配列になります。たとえば、ここから渡されてくる文字列などですね。 ``` echo generateSecureRandomString(15, 'abcde_!#$@') . ''; ``` 結果はこんなんになっています。 ``` 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文字ごとにバラバラにされた配列として得られます。