рекомендуется создать случайный токен для забытого пароля
Я хочу создать идентификатор для забытого пароля . Я читал, что могу сделать это, используя метку времени с mt_rand (), но некоторые люди говорят, что метка времени не может быть уникальной каждый раз. Так что я немного запутался здесь. Могу ли я сделать это с помощью метки времени с этим ?
вопрос
Как лучше всего генерировать случайные / уникальные токены пользовательской длины?
Я знаю, что здесь задают много вопросов, но я все больше запутываюсь после читаю разные мнения от разных людей.
6 ответов:
в PHP, используйте
random_bytes()
. Причина: вы ищете способ получить токен напоминания пароля, и, если это одноразовые учетные данные для входа, то у вас действительно есть данные для защиты (то есть - вся учетная запись пользователя)таким образом, код будет выглядеть следующим образом:
//$length = 78 etc $token = bin2hex(random_bytes($length));
обновление:предыдущие версии этот ответ имел в виду
uniqid()
и это неверно, если есть вопрос безопасности и не только уникальность.uniqid()
по сути простоmicrotime()
С какой-то кодировки. Есть простые способы, чтобы получить точные прогнозыmicrotime()
на ваш сервер. Злоумышленник может выдать запрос на сброс пароля, а затем попытаться использовать несколько вероятных токенов. Это также возможно, если используется more_entropy, так как дополнительная энтропия также слаба. Спасибо @NikiC и @ScottArciszewski для указания на это.Подробнее см.
более ранняя версия принятого ответа (
md5(uniqid(mt_rand(), true))
) небезопасно и предлагает только около 2^60 возможных выходов - хорошо в пределах поиска грубой силы примерно через неделю для малобюджетного злоумышленника:
mt_rand()
предсказуем (и только добавляет до 31 бит энтропии)uniqid()
только добавляет до 29 бит энтропииmd5()
не добавлять энтропии, он просто смешивает его детерминистическиС a 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 "remember me" в 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));