Безопасное генерирование случайных чисел в PHP
Случай использования: кнопка "Я забыл пароль". Мы не можем найти оригинальный пароль пользователя, потому что он хранится в хэшированной форме, поэтому единственное, что нужно сделать - это создать новый случайный пароль и отправить ему по электронной почте. Для этого требуются криптографически непредсказуемые случайные числа, для которых mt_rand недостаточно хорош, и в целом мы не можем предположить, что служба хостинга предоставит доступ к операционной системе для установки криптографического модуля случайных чисел и т.д., Поэтому я ищу способ для генерации безопасных случайных чисел в самом PHP.
Решение, которое я придумал до сих пор, включает в себя сохранение начального семени, затем для каждого вызова
result = seed
seed = sha512(seed . mt_rand())
Это основано на безопасности хэш-функции sha512 (вызов mt_rand только для того, чтобы сделать жизнь немного сложнее для противника, который получает копию базы данных).
Я что-то упустил или есть более известные решения?
Ответы
Ответ 1
Вы также можете использовать OpenSSL openssl_random_pseudo_bytes, доступный с PHP 5.3.
string openssl_random_pseudo_bytes ( int $length [, bool &$crypto_strong ] )
Генерирует строку псевдослучайных байтов с количеством байтов, определяемым параметром длины. Он также указывает, использовался ли криптографически сильный алгоритм для создания псевдослучайных байтов и делает это с помощью необязательного параметра crypto_strong. Для этого редко бывает ЛОЖЬ, но некоторые системы могут быть разбиты или стары.
http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php
Так как PHP 7 существует также функция random_bytes
string random_bytes ( int $length )
http://php.net/manual/en/function.random-bytes.php
Ответ 2
Я настоятельно рекомендую использовать таргетинг /dev/urandom в системах unix или криптоапи на платформе Windows в качестве источника энтропии для паролей.
Я не могу подчеркнуть, что важность реализации хэшей - это не волшебные энтропийные устройства. Неправильное использование их таким образом не более безопасно, чем использование данных seed и rand() до того, как оно было хэшировано, и я уверен, что вы признаете, что это не очень хорошая идея. Семя отменяет (детерминированный mt_rand()), и поэтому нет никакого смысла даже в том, чтобы включить его.
Люди считают, что они умны и умны, а результатом их труда являются хрупкие системы и устройства, которые ставят под угрозу безопасность своих систем и безопасность других систем (с помощью плохих советов).
Две ошибки не делают правильного. Система настолько же сильна, как и ее самая слабая часть. Это не лицензия или оправдание, чтобы признать, что еще больше это небезопасно.
Вот некоторый код PHP для получения безопасной случайной 128-битной строки, от этот комментарий на php.net от Mark Seecof:
"Если вам нужны псевдослучайные биты для безопасности или криптографические цели (яйцо, случайный IV для блочного шифрования, случайная соль для хэша пароля) mt_rand() - плохой источник. На большинстве платформ Unix/Linux и/или MS-Windows вы можете получить более качественную оценку псевдослучайных битов из ОС или системной библиотеки, например:
<?php
// get 128 pseudorandom bits in a string of 16 bytes
$pr_bits = '';
// Unix/Linux platform?
$fp = @fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
$pr_bits .= @fread($fp,16);
@fclose($fp);
}
// MS-Windows platform?
if (@class_exists('COM')) {
// http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
try {
$CAPI_Util = new COM('CAPICOM.Utilities.1');
$pr_bits .= $CAPI_Util->GetRandom(16,0);
// if we ask for binary data PHP munges it, so we
// request base64 return value. We squeeze out the
// redundancy and useless ==CRLF by hashing...
if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
} catch (Exception $ex) {
// echo 'Exception: ' . $ex->getMessage();
}
}
if (strlen($pr_bits) < 16) {
// do something to warn system owner that
// pseudorandom generator is missing
}
?>
NB: в целом безопасно оставлять как попытку чтения /dev/urandom, так и попытку доступа к CAPICOM в вашем коде, хотя каждый из них будет терпеть неудачу на другой платформе. Оставьте их обоих, чтобы ваш код был более портативным.
Ответ 3
PHP поставляется с новым набором функций CSPRNG (random_bytes()
и random_int()
). Тривиально превратить последнюю функцию в функцию генератора строк:
<?php
/**
* Generate a random string, using a cryptographically secure
* pseudorandom number generator (random_int)
*
* For PHP 7, random_int is a PHP core function
* For PHP 5.x, depends on https://github.com/paragonie/random_compat
*
* @param int $length How many characters do we want?
* @param string $keyspace A string of all possible characters
* to select from
* @return string
*/
function random_str(
$length,
$keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
) {
$str = '';
$max = mb_strlen($keyspace, '8bit') - 1;
if ($max < 1) {
throw new Exception('$keyspace must be at least two characters long');
}
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[random_int(0, $max)];
}
return $str;
}
Если вам нужно использовать это в проекте PHP 5, не стесняйтесь брать копию random_compat, которая является polyfill для этих функций.