Случайности в PHP 7 – ощущаете ли себя удачливым?

1444736085dice

В статье анализируются проблемы, связанные с генерацией случайных чисел, используемых в целях криптографии. PHP 5 не обеспечивает простого механизма для генерации криптографически стойких случайных чисел, в то время как PHP 7 решает это с помощью функций CSPRNG.

Итак, что такое CSPRNG?

Цитируя Википедию, Криптографически стойкий генератор псевдослучайных чисел (Cryptographically Secure Pseudorandom Number Generator) — это генератор псевдослучайных чисел со свойствами, делающими его подходящим для использования в криптографии.

CSPRNG в основном полезен для:

  • генерации ключей;
  • создания случайных паролей для новых учётных записей пользователей;
  • систем шифрования.

Основным аспектом для поддержания высокого уровня безопасности является высокое качество случайности.

CSPRNG в PHP 7

PHP 7 вводит две новые функции, которые используются для CSPRNG: random_bytes и random_int.

Функция random_bytes возвращает строку, содержащую указанное количество (в int) криптографически защищенных случайных байт.

Например:

$bytes = random_bytes('10');
var_dump(bin2hex($bytes));
// возможный вывод: string(20) "7dfab0af960d359388e6"

Функция random_int возвращает криптографически безопасное случайное целое число в заданном диапазоне.

Например:

var_dump(random_int(1, 100));
// возможный вывод: 27

За кулисами

Источники случайности вышеупомянутых функций отличаются в зависимости от среды выполнения:

  • В Windows всегда будет использоваться CryptGenRandom();
  • На других платформах — arc4random_buf(), по возможности (на BSD производных системах или системах с libbsd);
  • В противном случае, системный вызов Linux-getrandom(2);
  • Если всё остальное перестанет работать, тогда будет использоваться /dev/urandom в качестве окончательного запасного варианта;
  • Если ни один из вышеперечисленных источников недоступны, то будет брошено Error.

Простой тест

Хорошая система генерации случайных чисел гарантирует правильное «качество» поколений. Для проверки этого качества часто выполняется комплекс статических тестов. Не вдаваясь в сложные статистические темы, сравнение известного поведения с результатом генератора чисел может помочь в оценке качества.

Таким простым тестом может быть игра в кости. Исходя из того, что шанс выпадения шестёрки на единственном кубике составляет 1/6, то если я брошу 3 кубика сотню раз, тогда, по грубым расчётам, шестёрки выпадут на 0, 1, 2 и всех 3 кубиках:

  • 0 шестёрок = 57,9 раз;
  • 1 шестёрка = 34,7 раз;
  • 2 шестёрки = 6,9 раз;
  • 3 шестёрки = 0,5 раза.

Вот код для воспроизведения бросания кубика миллион раз:

$times = 1000000;
$result = [];
for ($i=0; $i<$times; $i++){
    $dieRoll = array(6 => 0); //initializes just the six counting to zero
    $dieRoll[roll()] += 1; //first die
    $dieRoll[roll()] += 1; //second die
    $dieRoll[roll()] += 1; //third die
    $result[$dieRoll[6]] += 1; //counts the sixes
}
function roll(){
    return random_int(1,6);
}
var_dump($result);

Тестирование кода выше в PHP 7 с использованием random_int и функцией простого рандома может показать подобный результат:

2015-11-02_232731

Чтобы лучше увидеть разницу между rand и random_int, мы можем построить результаты на графике с применением формулы для увеличения различий между значениями:

php result - expected result / sqrt(expected)

Полученный график:

1444736090random-graph
(чем ближе к нулю, тем лучше)

Даже если комбинация из трёх шестёрок не выполняется особо хорошо, и тест слишком прост для реального приложения, мы всё равно можем ясно видеть, что random_int лучше, чем rand.

Кроме того, безопасность приложения увеличивается за счёт отсутствия предсказуемого и повторяемого поведения в принятом генераторе случайных чисел.

Что насчёт PHP 5?

По умолчанию, PHP 5 не предоставляет каких-либо генераторов стойких псевдослучайных чисел. В действительности, есть разные варианты, например openssl_random_pseudo_bytes(), mcrypt_create_iv() или непосредственное использование файлов /dev/random или /dev/urandom с fread(). Также существуют такие пакеты, как RandomLib или libsodium.

Если вы хотите начать использовать хороший генератор случайных чисел и в то же время быть готовым к PHP 7, то попробуйте библиотеку random_compat от Paragon Initiative Enterprises. Библиотека random_compat позволяет использовать random_bytes() и random_int() в вашем PHP 5.x проекте.

Библиотеку можно установить с помощью Composer:

composer require paragonie/random_compat
require 'vendor/autoload.php';
$string = random_bytes(32);
var_dump(bin2hex($string));
// string(64) "8757a27ce421b3b9363b7825104f8bc8cf27c4c3036573e5f0d4a91ad2aaec6f"
$int = random_int(0,255);
var_dump($int);
// int(81)

Библиотека использует иной порядок предпочтений по сравнению с PHP 7:

  1. fread() /dev/urandom, если возможно;
  2. mcrypt_create_iv($bytes, MCRYPT_CREATE_IV);
  3. COM(‘CAPICOM.Utilities.1’)->GetRandom();
  4. openssl_random_pseudo_bytes().

Для получения более подробной информации о том, почему используется именно такой порядок, я предлагаю почитать документацию.

Простой способ использования библиотека для создания пароля:

$passwordChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$passwordLength = 8;
$max = strlen($passwordChar) - 1;
$password = '';
for ($i = 0; $i < $passwordLength; ++$i) {
    $password .= $passwordChar[random_int(0, $max)];
}
echo $password;
//возможный вывод: 7rgG8GHu

Заключение

Всегда применяйте криптографически безопасный генератор псевдослучайных чисел. Библиотека random_compat обеспечивает хорошую реализацию этого.

Если вы хотите использовать надёжный случайный источник данных, как в статье, предлагаю как можно скорее начать использовать random_int и random_bytes.

Примечание

Это авторский перевод статьи «Randomness in PHP – Do You Feel Lucky?» на русский язык.

Комментарии

  1. Радуют комментарий:
    //возможный вывод: 7rgG8GHu

    А возможно и нет=)))

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *