99

パスワードを忘れた場合の識別子を生成したい。私は mt_rand() でタイムスタンプを使用することでそれを行うことができると読みましたが、タイムスタンプは毎回一意ではない可能性があると言っている人もいます。だから私はここで少し混乱しています。これでタイムスタンプを使用してそれを行うことはできますか?

質問
カスタムの長さのランダム/一意のトークンを生成するためのベスト プラクティスは何ですか?

ここでたくさんの質問が寄せられていることは知っていますが、さまざまな人々からのさまざまな意見を読んだ後、ますます混乱しています.

4

5 に答える 5

156

PHP では、 を使用しますrandom_bytes()。理由: パスワード リマインダ トークンを取得する方法を探しており、それが 1 回限りのログイン資格情報である場合、実際には保護するデータ (つまり、ユーザー アカウント全体) があります。

したがって、コードは次のようになります。

//$length = 78 etc
$token = bin2hex(random_bytes($length));

更新この回答の以前のバージョンuniqid()は参照されていましたが、一意性だけでなくセキュリティの問題がある場合、それは正しくありません。uniqid()基本的にmicrotime()は、いくつかのエンコーディングのみです。サーバー上の の正確な予測を取得する簡単な方法がありmicrotime()ます。攻撃者はパスワード リセット リクエストを発行し、いくつかの可能性のあるトークンを試すことができます。これは、追加のエントロピーも同様に弱いため、more_entropy が使用されている場合にも可能です。これを指摘してくれた@NikiC@ScottArciszewskiに感謝します。

詳細については、

于 2013-09-20T07:14:17.107 に答える
73

これは「最良のランダム」リクエストに答えます:

Security.StackExchange からの Adi の回答1には、これに対する解決策があります。

OpenSSL がサポートされていることを確認してください。このワンライナーで間違いはありません。

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi、2018 年 11 月 12 日、Celeritas、「確認メール用の推測不可能なトークンの生成」、13 年 9 月 20 日 7:06、https: //security.stackexchange.com/a/40314/

于 2015-03-19T05:17:32.583 に答える
56

受け入れられた回答 ( md5(uniqid(mt_rand(), true))) の以前のバージョンは安全ではなく、約 2^60 の可能な出力しか提供しません。これは、低予算の攻撃者が約 1 週間でブルート フォース検索を実行できる範囲内です。

56 ビットの DES キーは約 24 時間でブルート フォースされる可能性があり、平均的なケースでは約 59 ビットのエントロピーがあるため、2^59 / 2^56 = 約 8 日と計算できます。このトークン検証の実装方法によっては、実際にタイミング情報が漏洩し、有効なリセット トークンの最初の N バイトを推測できる可能性があります

質問は「ベストプラクティス」に関するものであり、次で始まります...

パスワードを忘れた場合の識別子を生成したい

...このトークンには暗黙のセキュリティ要件があると推測できます。また、乱数ジェネレーターにセキュリティ要件を追加する場合のベスト プラクティスは、常に暗号学的に安全な疑似乱数ジェネレーター(略して CSPRNG) を使用することです。


CSPRNG の使用

bin2hex(random_bytes($n))PHP 7 では、 ($nは 15 より大きい整数)を使用できます。

PHP 5 では、 を使用random_compatして同じ API を公開できます。

または、インストールbin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))している場合。ext/mcryptもう 1 つの優れたワンライナーは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.
}

これらのコード スニペットは完全なソリューションではありませんが (入力の検証とフレームワークの統合は避けました)、何をすべきかの例として役立つはずです。

于 2015-07-14T23:47:32.177 に答える
7

DEV_RANDOM を使用することもできます。ここで、128 = 生成されたトークンの長さの 1/2 です。以下のコードは 256 トークンを生成します。

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
于 2013-11-21T18:13:58.867 に答える