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

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

新規登録して質問してみよう
ただいま回答率
85.50%
PHP

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

Q&A

解決済

1回答

350閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

PHP

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

0グッド

1クリップ

投稿2017/12/11 16:20

編集2017/12/11 16:26
<?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('//は何にでもマッチするという意味になるのでしょうか?

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ockeghem

2017/12/11 22:14

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

回答1

0

ベストアンサー

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

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

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

PHP

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

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

PHP

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

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

PHP

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

そして、その長さは、

PHP

1$charsLen = count($chars);

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

次に$bytesですが、

PHP

1$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 00:47

編集2017/12/12 02:42
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/12/12 05: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 07:40

>length=10だと0~255の数字が10個並んだものが生成されるのでしょうか? >例10117610596155242653324127(1個1個をスペースで区切流と101 176 105 96 155 242 65 33 24 127の10個) ちょっとちがいます。全くランダムな10bytesのバイナリ文字列データができます。 文字列なので、その時点では数値とは別物です。
退会済みユーザー

退会済みユーザー

2017/12/12 11:41 編集

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

退会済みユーザー

2017/12/12 11:41 編集

>ordは「$stringで渡された文字列の最初の1文字目のASCIIコードを整数で返す関数」であるのになぜordでランダムな整数を得る事ができるのでしょうか? ord()ではランダムな整数は得られないです。 ランダムな整数はopenssl_random_pseudo_bytes()で既に生成されています。ランダムな10文字分のバイナリ文字列ですね。 で、これは文字であってそのままでは数値として扱えないため、このバイナリ文字列をASCIIコードの文字列として扱えばASCIIコードのコード番号を数値として得られる便利な関数ord()があるので、これを利用しましょう、というわけです。
退会済みユーザー

退会済みユーザー

2017/12/12 07:47

Array ( [0] => a [1] => b [2] => c [3] => d [4] => e [5] => _ [6] => ! [7] => # [8] => $ [9] => @ )
退会済みユーザー

退会済みユーザー

2017/12/12 11:43 編集

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

退会済みユーザー

2017/12/12 11:44 編集

ものすごく簡単なランダム文字列の実装としては $str = ""; for($i = 0; $i < 10; $i++) { $idx = mt_rand(0, 9); $str .= $chars[$idx]; } で済んでしまうでしょうね。 ただ、この場合はランダムな数値の生成が必要な文字数回繰り返して行われます。
退会済みユーザー

退会済みユーザー

2017/12/12 08:04 編集

提示されたコードでは、ランダムな要素の生成を最初に1回だけ、必要な文字数分全部計算してしまっています。 そのあとで、1つずつ取り出して最終的に利用する文字を決めています。 この場合、ランダムに生成したのは文字であって数値ではないため、文字を数値に置き換える処理がどうしても必要になり、その処理が結構難解だった、という感じですね。
退会済みユーザー

退会済みユーザー

2017/12/13 14:21

ありがとうございます。 ランダムな文字列を作成し、その文字列の先頭をASCIIの数字に変換し、余りで取り出すという流れは理解ができました。 バイナリ文字列の意味がググってみてもよくわからないのですが詳しいサイトをご存知であれば教えていただけないでしょうか?文字コードの全体的な知識を取得したいです。
退会済みユーザー

退会済みユーザー

2017/12/14 17: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.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問