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

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

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

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

Ответ 1

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

Итак, код будет выглядеть следующим образом:

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

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

Подробнее см.

Ответ 2

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

Ади ответ 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/

Ответ 3

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

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

Поскольку вопрос касается "лучших практик" и открывается с помощью...

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

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

Отделив поиск от Validator

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

Пример кода

Создание токена reset в 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')
]);

Проверка токена, предоставленного пользователем reset:

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

echo str_shuffle('ASGDHFfdgfdre5475433fd');