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

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

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

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

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

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

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

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

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

https://security.stackexchange.com/questions/40310/generating-an-unguesable-token-for-confirmation-e-mails

это отвечает лучшим случайным

 $token = bin2hex(openssl_random_pseudo_bytes(16)); 

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

  • mt_rand() является предсказуемым (и только добавляет до 31 бит энтропии)
  • uniqid() добавляет только до 29 бит энтропии
  • md5() не добавляет энтропии, он просто смешивает ее детерминистически

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

Поскольку вопрос касается «лучших практик» и открывается с …

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

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


Использование CSPRNG

В PHP 7 вы можете использовать bin2hex(random_bytes($n)) (где $n – целое число больше 15).

В PHP 5 вы можете использовать random_compat для random_compat и того же API.

Альтернативно, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) если у вас установлен ext/mcrypt . Еще один хороший однострочный bin2hex(openssl_random_pseudo_bytes($n))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('Ymd\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');