🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
セキュリティー

このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

PHP

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

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

Q&A

解決済

1回答

6301閲覧

CSRF トークンの作成方法と、random_bytes の適切なバイト数

7968

総合スコア253

セキュリティー

このタグは、コンピューターシステムの安全性やデータの機密性に関連したトピックの為に使われます。

PHP

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

CSRF

クロスサイトリクエストフォージェリ (Cross site request forgeries、CSRF)は、 外部Webページから、HTTPリクエストによって、 Webサイトの機能の一部が実行されてしまうWWWにおける攻撃手法です。

0グッド

4クリップ

投稿2019/09/12 21:22

編集2019/09/12 21:28

CSRF トークンの作成方法について質問があります。

uniqidrand などは論外で random_bytes を使う前提です。

【質問1】CSRF トークンの作成方法による違いはありますか?

CSRF トークンの作成方法として主に 3 つの方法を見かけました。

bin2hex

徳丸先生が teratail で回答していた bin2hex で 16 進数表記にするパターンです。

bin2hex(random_bytes(32))

引用:CSRF対策|teratail

ハッシュ関数

mpyw 先生の記事でサンプルコードとして紹介しているハッシュ関数を通すパターンです。

sha1(random_bytes(30))

引用:Qiita - これで完璧!今さら振り返る CSRF 対策と同一オリジンポリシーの基礎

base64_encode

Laravel のコードですが、base64_encode を使ってごにょごにょしています。
抜粋しているので、詳細のコードは引用元を参照してください。

public static function random($length = 16) { $string = ''; while (($len = strlen($string)) < $length) { $size = $length - $len; $bytes = random_bytes($size); $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } return $string; }

引用:Qiita - CSRFトークン インタビューズ

3 つの方法を見かけましたが、セキュリティ面での違いなどあるのでしょうか?

いずれも random_bytes でトークンを生成しているので、どれもトークンとしては問題ないと思っているのですが、どうなのでしょうか。

【質問2】random_bytes の適切なバイト数はいくつですか?

random_bytes で生成される文字列の長さですが、30 バイト以上にしている記述を見かけます。

何バイトから安全と言えるのでしょうか?

なぜ、そのバイト数なのかも教えていただけると嬉しいです。

ご存じの方いれば、教えてください。

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

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

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

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

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

guest

回答1

0

ベストアンサー

【質問1】への回答

私の案も記載いただいているので、なぜその方法を推奨するかを説明しましょう。random_bytes関数は「暗号論的に安全」であることが確認された擬似乱数生成器です。一方、その結果にハッシュ関数を通すなどの変換を施したものは、必ずしも「暗号論的に安全」であることが確認されていません。直感的には大丈夫そうにも思えますが、専門家の検証を得たものではないわけです。なので、random_bytes関数の結果に何か変換を施すことは、かえって安全でなくなる可能性がある、という考え方です。ただし、16進数文字列への変換やbase64エンコードは、可逆なエンコードに過ぎないので、乱数としての安全性は変わらないと判断します。
このように説明すると、暗号論的に安全な乱数を生成する方法として暗号論的ハッシュ関数(SHA-256等)を使うものがあるではないかという反論がありそうです。確かにそうですが、暗号論的ハッシュ関数を使えば自動的に暗号論的に安全な乱数が得られるわけでもありません。そのような悪い例として、古いPHPのセッションID生成があります。これはハッシュ関数としてMD5を使っていますが、極めて限定された状況ではあるものの、セッションIDが推測できる場合があるという論文があります。

PHPのセッションIDは暗号論的に弱い乱数生成器を使っており、セッションハイジャックの危険性がある

MD5という名前を見ただけで、MD5は危殆化したハッシュ関数だからだろうと思う人が出てくると思います。しかし、上記論文はMD5の脆弱性には依存していないので、MD5を他のハッシュ関数に置き換えても原理は変わりません。

Laravelのコードは、base64エンコードした結果から記号を取り除き、足りない分を補充しています。これは可逆なエンコードではないので、(たぶん大丈夫だろうとは思うものの)安全性が担保されているわけではないと考えます。率直に言って、「なぜこんな余計なことをするのだろう、イマイチなセンスだな」と思います。
トークンではなくハッシュの例ですが、「余計な変換」を加えたために安全になるどころか深刻な脆弱性が生まれた事例を以下に紹介します。

bcryptの72文字制限をSHA-512ハッシュで回避する方式の注意点

…ということで、徳丸としては、不要なことをするのは脆弱性その他のバグのもとであり、プログラムはシンプルであるべきだと思います。

【質問2】への回答
こちらについては、ちょっと計算をしてみましょう。
パスワードの総当りと、トークン推測の総当りは、似ているようですが、特性が異なります。パスワードの照合は、パスワードがストレッチングありでハッシュされているという想定ではかなり重たい処理になるので、1秒あたりの試行可能な回数はかなり制限されます。一方、トークンの照合はセッション変数に記憶された値との文字列比較にすぎないので、1秒あたりに試行可能な回数はかなり多くなると想定されます。
ここでは、少し余裕を見て(安全側に倒して)1秒回に1万回のトークン照合が可能だと仮定します。
トークンは、短い例として、8バイトバイナリのデータ(実際には16桁の16進数)とします。
そうすると、トークンの取り得るパターンの総数は、

256 ^ 8 = 1.84 * 10^19 個(18.4エクサ個、18400ペタ個、1840000テラ個)

となります。
これを毎秒1万回照合したとして、総当たりに要する時間は

(256 ^ 8) / 10000 / 60 / 60 / 24 / 365 = 約5850万年

となります。まぁ、これでも十分でしょうね。
ただし、トークンの長さを長くすることは、安全性を大幅に向上する一方で、(パスワードの場合とは異なり)悪影響はほとんどありません。そして、攻撃者がランダムに選択したトークンが偶然一致してしまう確率は、かなり小さいものの 0 ではありません。なので、この確率を少しでも減らすために、悪影響のない範囲で、できるだけ長いトークンを使ったほうがよい、という理屈です。

なので、トークンは最低何文字必要かということに、厳密な境界があるわけではありません。

投稿2019/09/13 09:07

ockeghem

総合スコア11705

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

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

7968

2019/09/13 10:59

徳丸先生からご回答いただけるとは感謝です。 【質問1】 なるほど、bin2hex や base64_encode などでエンコードするだけなら安全性は変わらないが、ハッシュ関数を通したり、記号を取り除くなどの処理をするこで、逆に安全ではなくなる可能性を否定できないのですね。 【質問2】 計算式も教えてくださり、ありがとうございます。毎秒1万回試行すると仮定して8バイトバイナリのデータでも約5850万年もかかるのですね。驚きです。そう考えると30バイト前後に設定しておけば十分なのですね。 大変参考になりました。 ご回答ありがとうございました ((_ _ (´ω` )ペコ
mikkame

2019/09/14 06:09

横から失礼します。 【質問2】への回答についてですが セッション変数はサーバ上でした確認できないため、 検証を試行するには都度、サーバへのアクセスが必要という認識ですが間違っていますでしょうか。 その場合は、サーバが秒間に捌けるリクエストが1秒間あたりの試行回数の上限となる認識です。
退会済みユーザー

退会済みユーザー

2019/09/14 06:17 編集

> mikkame さん 横からに横からで失礼w CSRF 対策のトークンの話としてみると、試行回数はさらにぐっと減るかと。 ただ、試行回数の変化では結論は変わらないので、「そんなもんだ」って理解で良い気がします。
mikkame

2019/09/14 07:18

>te2ji さん ありがとうございます。認識があっていたようでホッとしました
ockeghem

2019/09/14 09:03

> その場合は、サーバが秒間に捌けるリクエストが1秒間あたりの試行回数の上限となる認識です。 はい。その通りです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問