Лучшая практика для генерации случайного токена для забытого пароля

Я хочу сгенерировать идентификатор забытого пароля. Я прочитал, что могу сделать это, используя метку времени с mt_rand(), но некоторые люди говорят, что метка времени может быть не всегда уникальной. Так что я немного запутался здесь. Могу ли я сделать это с помощью отметки времени с этим?

Вопрос
Как лучше всего генерировать случайные / уникальные токены произвольной длины?

Я знаю, что здесь задают много вопросов, но я все больше путаюсь после прочтения разных мнений разных людей.

6 ответов

Решение

В PHP используйте random_bytes(), Причина: вы ищете способ получить токен для напоминания пароля, и, если это одноразовые учетные данные, у вас действительно есть данные для защиты (то есть - вся учетная запись пользователя)

Итак, код будет следующим:

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

Обновление: предыдущие версии этого ответа имели в виду uniqid() и это неверно, если речь идет о безопасности, а не только об уникальности. uniqid() по сути просто microtime() с некоторой кодировкой. Есть простые способы получить точные прогнозы microtime() на вашем сервере. Злоумышленник может отправить запрос на сброс пароля, а затем попробовать пару возможных токенов. Это также возможно, если используется more_entropy, так как дополнительная энтропия также слаба. Спасибо NikiC и Scott Arciszewski за указание на это.

Для более подробной информации смотрите

Это отвечает на "лучший случайный" запрос:

Ответ Ади1 от Security.StackExchange имеет решение для этого:

Убедитесь, что у вас есть поддержка OpenSSL, и вы никогда не ошибетесь с этим однострочником

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Ади, понедельник, 12 ноября 2018 г., Celeritas, "Генерирование неопровержимого токена для писем с подтверждением", 20 сентября 13 г. в 7:06, https://security.stackexchange.com/a/40314/

Более ранняя версия принятого ответа (md5(uniqid(mt_rand(), true))) небезопасен и предлагает только около 2^60 возможных выходных данных - вполне в пределах диапазона поиска грубой силы примерно за неделю для малобюджетного злоумышленника:

Поскольку 56-битный ключ DES может быть подвергнут грубому принуждению примерно за 24 часа, а в среднем случае будет около 59 битов энтропии, мы можем вычислить 2^59 / 2^56 = около 8 дней. В зависимости от того, как реализована эта проверка токена, может быть практически возможной утечка информации о синхронизации и вывод первых N байтов действительного маркера сброса.

Так как вопрос о "лучших практиках" и начинается с...

Я хочу сгенерировать идентификатор забытого пароля

... мы можем сделать вывод, что этот токен имеет неявные требования безопасности. А когда вы добавляете требования безопасности к генератору случайных чисел, рекомендуется всегда использовать криптографически безопасный генератор псевдослучайных чисел (сокращенно CSPRNG).


Использование 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)),

Отделение поиска от валидатора

Исходя из моей предыдущей работы над безопасными cookie-файлами "помни меня" в PHP, единственный эффективный способ уменьшить вышеупомянутую утечку времени (обычно вносимую в запрос к базе данных) - это отделить поиск от проверки.

Если ваша таблица выглядит так (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, вот так:

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 случайного токена в базе данных. Используйте селектор, чтобы получить хеш и идентификатор пользователя, вычислить хеш SHA-256 токена, который пользователь предоставляет с хешем, хранящимся в базе данных, используя hash_equals(),

Пример кода

Генерация токена сброса в PHP 7 (или 5.6 с random_compat) с помощью PDO:

$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.
}

Эти фрагменты кода не являются полными решениями (я отказался от проверки входных данных и интеграции фреймворка), но они должны служить примером того, что делать.

Вы также можете использовать DEV_RANDOM, где 128 = 1/2 сгенерированной длины токена. Код ниже генерирует 256 токенов.

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));

Это может быть полезно, когда вам нужен очень очень случайный токен

<?php
   echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>

Ты можешь использовать

echo str_shuffle('ASGDHFfdgfdre5475433fd');
Другие вопросы по тегам