パスワードを忘れた場合の識別子を生成したい。私はmt_rand()でタイムスタンプを使用してそれを行うことができると読みましたが、一部の人々はタイムスタンプが毎回一意ではないかもしれないと言っています。だから私はここで少し混乱しています。これでタイムスタンプを使ってできますか?
質問
カスタムの長さのランダム/一意のトークンを生成するためのベストプラクティスは何ですか?
この辺りで多くの質問が寄せられることは承知していますが、さまざまな人々のさまざまな意見を読んだ後、さらに混乱します。
パスワードを忘れた場合の識別子を生成したい。私はmt_rand()でタイムスタンプを使用してそれを行うことができると読みましたが、一部の人々はタイムスタンプが毎回一意ではないかもしれないと言っています。だから私はここで少し混乱しています。これでタイムスタンプを使ってできますか?
質問
カスタムの長さのランダム/一意のトークンを生成するためのベストプラクティスは何ですか?
この辺りで多くの質問が寄せられることは承知していますが、さまざまな人々のさまざまな意見を読んだ後、さらに混乱します。
回答:
PHPでは、を使用しますrandom_bytes()
。理由:パスワードリマインダトークンを取得する方法を探している場合、それが1回限りのログイン資格情報である場合は、実際に保護するデータ(ユーザーアカウント全体)があります。
したがって、コードは次のようになります。
//$length = 78 etc
$token = bin2hex(random_bytes($length));
更新:この回答の以前のバージョンが参照していたものでuniqid()
あり、一意性だけでなくセキュリティの問題がある場合は正しくありません。uniqid()
本質的にmicrotime()
は一部のエンコーディングのみです。microtime()
サーバー上のの正確な予測を取得する簡単な方法があります。攻撃者はパスワードリセットリクエストを発行し、可能性のあるトークンをいくつか試すことができます。追加のエントロピーも同様に弱いため、more_entropyを使用する場合にもこれは可能です。これを指摘してくれた@NikiCと@ScottArciszewskiに感謝します。
詳細については、
random_bytes()
PHP7以降でのみ利用可能であることに注意してください。古いバージョンでは、@ yesitsmeによる回答が最良のオプションのようです。
$length
ですか?ユーザーのID?または何?
これは「最良のランダム」要求に答えます:
Security.StackExchangeからのAdiの回答1には、これに対するソリューションがあります。
OpenSSLをサポートしていることを確認してください。このワンライナーで問題が発生することはありません。
$token = bin2hex(openssl_random_pseudo_bytes(16));
1. Adi、Mon Nov 12 2018、Celeritas、「確認メール用の推測できないトークンの生成」、2013年9月20日7 : 06、https: //security.stackexchange.com/a/40314/
openssl_random_pseudo_bytes($length)
-サポート:PHP 5> = 5.3.0、....................................... ...................(PHP 7以降の場合はを使用random_bytes($length)
)................................ ....................(5.3未満のPHPの場合
受け入れられた回答の以前のバージョン(md5(uniqid(mt_rand(), true))
)は安全ではなく、約2 ^ 60の可能な出力しか提供しません-低予算の攻撃者にとって約1週間の時間内にブルートフォース検索の範囲内です。
mt_rand()
予測可能です(そして最大31ビットのエントロピーしか追加されません)uniqid()
最大29ビットのエントロピーのみを追加しますmd5()
エントロピーを追加せず、決定論的に混合するだけです以来、56ビットDES鍵が約24時間でブルート強制することができ、平均ケースは、エントロピーの59約ビットを有することになる、我々は、8日間、約2 ^ 2分の59 ^ 56 =を計算することができます。このトークン検証の実装方法によっては、タイミング情報を実際にリークし、有効なリセットトークンの最初のNバイトを推測することが可能な場合があります。
質問は「ベストプラクティス」に関するものであり、次のように始まります...
パスワードを忘れた場合の識別子を生成したい
...このトークンには暗黙のセキュリティ要件があると推測できます。また、乱数発生器にセキュリティ要件を追加する場合のベストプラクティスは、常に暗号で保護された疑似乱数発生器(省略形はCSPRNG)を使用することです。
PHP 7では、bin2hex(random_bytes($n))
($n
は15より大きい整数)を使用できます。
PHP 5では、を使用random_compat
して同じAPIを公開できます。
または、インストールしたbin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
場合ext/mcrypt
。別の良いワンライナーはbin2hex(openssl_random_pseudo_bytes($n))
です。
以前のPHPでの安全な "remember me" Cookieに関する作業から、前述のタイミングリーク(通常はデータベースクエリによって導入される)を軽減する唯一の効果的な方法は、ルックアップと検証を分離することです。
テーブルが次のようになっている場合(MySQL)...
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id)
);
...次のselector
ように、列をもう1つ追加する必要があります。
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
selector CHAR(16),
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id),
KEY(selector)
);
CSPRNGを使用するパスワードリセットトークンが発行されたら、両方の値をユーザーに送信し、ランダムトークンのセレクターとSHA-256ハッシュをデータベースに保存します。セレクターを使用してハッシュとユーザーIDを取得し、ユーザーが提供するトークンのSHA-256ハッシュを計算しますhash_equals()
。
PDOを使用してPHP 7(またはrandom_compatを使用した5.6)でリセットトークンを生成する:
$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);
$urlToEmail = 'http://example.com/reset.php?'.http_build_query([
'selector' => $selector,
'validator' => bin2hex($token)
]);
$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 hour
$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
'userid' => $userId, // define this elsewhere!
'selector' => $selector,
'token' => hash('sha256', $token),
'expires' => $expires->format('Y-m-d\TH:i:s')
]);
ユーザー提供のリセットトークンの確認:
$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
$calc = hash('sha256', hex2bin($validator));
if (hash_equals($calc, $results[0]['token'])) {
// The reset token is valid. Authenticate the user.
}
// Remove the token from the DB regardless of success or failure.
}
これらのコードスニペットは完全なソリューションではありませんが(入力の検証とフレームワークの統合を避けました)、何をすべきかの例として役立つはずです。
hash('sha256', bin2hex($token))
、2)で確認してくださいif (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...
?ありがとう!
id
セレクタとしてユニークを使用しないのはなぜですか?つまり、account_recovery
テーブルの主キーです。セレクターに追加のセキュリティレイヤーは必要ありません。ありがとう!
id:secret
大丈夫です。selector:secret
大丈夫です。secret
それ自体はそうではありません。目標は、データベースクエリ(タイミングが漏れる)と認証プロトコル(一定時間である必要があります)を分離することです。
openssl_random_pseudo_bytes
いるrandom_bytes
場合、代わりに使用することに害はありますか?また、リンクのクエリ文字列にバリデータではなくセレクタだけを追加するべきではありませんか?
DEV_RANDOMを使用することもできます。128=生成されたトークンの長さの1/2。以下のコードは256トークンを生成します。
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
MCRYPT_DEV_URANDOM
以上提案しMCRYPT_DEV_RANDOM
ます。