[Перевод] Случайность в PHP7 – Повезет ли мне?
В этой статье мы проанализируем проблемы, относящиеся к генерации случайных чисел, используемых в криптографии. PHP5 не обеспечивает простой механизм генерации криптостойких случайных чисел, в то время как PHP7 решает эту проблему путем введения CSPRNG-функций.
Что такое CSPRNG?
Цитируя википедию, криптографически стойкий генератор псевдослучайных чисел (англ. Cryptographically secure pseudorandom number generator, CSPRNG) — это генератор псевдослучайных чисел с определёнными свойствами, позволяющими использовать его в криптографии.
CSPRNG в основном используется для следующих целей:
- Генерация ключей (в том числе, генерация public/private ключей)
- Создание случайных паролей для аккаунтов пользователей
- Системы шифрования
Главным аспектом сохранения высокого уровня безопасности является высокое качество случайности.
CSPRNG в PHP7
PHP7 вводит две новых функции, которые могут быть использованы для CSPRNG: random_bytes
и random_int
.
Функция random_bytes
возвращает строку и принимает в качестве входных параметров int
, задающий длину (в байтах) возвращаемого значения:
$bytes = random_bytes('10');
var_dump(bin2hex($bytes));
//possible ouput: string(20) "7dfab0af960d359388e6"
random_int
возвращает целое число в заданном диапазоне:
var_dump(random_int(1, 100));
//possible output: 27
За кадром
Источники случайности вышеперечисленных функций отличаются в зависимости от среды:
- В Windows всегда будет использоваться
CryptGenRandom();
- На других платформах — при условии доступности будет задействована
arc4random_buf()
(верно в случае BSD-производных систем или систем сlibbsd
). - В случае недоступности вышеобозначенного, в Linux будет использоваться системный
getrandom(2)
. - Если все это терпит неудачу, в качестве финальной попытки PHP попробует задействовать
/dev/urandom
. - При невозможности использовать эти источники будет выброшена ошибка.
Простой тест
Хорошая система генерации случайных числе определяется «качеством» генераций. Чтобы его проверить часто используется набор статистических тестов, позволяющих, не вникая в сложную тему статистики, сравнить известное эталонное поведение с результатом генератора и помочь в оценке его качества.
Один из самых простых тестов — игра в кости. Предполагаемая вероятность выпадения шестерки при одной кости — один к шести, в то же время, если я брошу три кости 100 раз, то ожидаемые выпадения 1, 2 и 3х шестерок примерно такие:
- 0 шестерок = 57.9 раз
- 1 шестерка = 34.7 раз
- 2 шестерки = 6.9 раз
- 3 шестерки = 0.5 раз
Вот код для воспроизведения броска костей 1 000 000 раз:
$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);
Прогонка кода в PHP7 c использованием random_int
и простого rand
выдаст следующие результаты:
Шестерки | Ожидаемый результат | random_int | rand |
---|---|---|---|
0 | 579000 | 579430 | 578179 |
1 | 347000 | 346927 | 347620 |
2 | 69000 | 68985 | 69586 |
3 | 5000 | 4658 | 4615 |
Для лучшего сравнения rand
и random_int
построим график результатов, применяя формулу: результат PHP
— ожидаемый результат
/ sqrt(ожидаемый результат)
.
График будет выглядеть так (чем ближе к нулю, тем лучше):
Даже несмотря на плохой результат с тремя шестерками и всю простоту теста, мы видим явное превосходство random_int
над rand
.
А что насчет PHP5?
По умолчанию, PHP5 не предусматривает каких-либо сильных псевдо-генераторов случайных чисел. Но на самом деле есть несколько вариантов, таких как openssl_random_pseudo_bytes()
, mcrypt_create_iv()
или непосредственное использование /dev/random
или /dev/urandom
с fread()
. Есть также такие библиотеки, как RandomLib или libsodium.
Если вы хотите начать использовать хороший генератор случайных чисел и в то же время пока еще не готовы к переходу на PHP7, вы можете использовать библитеку random_compat
от Paragon Initiative Enterprises. Она позволяет использовать random_bytes()
и random_int()
в PHP 5.х проектах.
Библиотеку можно установить через 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)
По сравнению с PHP7, random_compat
использует несколько другие приоритеты:
fread()
/dev/urandom
если доступноmcrypt_create_iv($bytes, MCRYPT_CREATE_IV)
COM('CAPICOM.Utilities.1')->GetRandom()
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;
//possible output: 7rgG8GHu
Краткий итог
Вы всегда должны применять криптографически стойкие генераторы псевдослучайных чисел, и random_compat
является хорошим решением для этого.
Если же вам необходим надежный источник случайных данных, то посмотрите в сторону random_int
и random_bytes
.
Ссылки по теме