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