Генератор случайных строк в PHP: что скрывается под капотом

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

PHP random string generator: как работает генератор случайных строк под капотом

Каждый разработчик хотя бы раз сталкивался с задачей: сгенерировать случайную строку. Для токенов, временных паролей, ID сессий или просто для тестовых данных. В PHP есть с десяток способов это сделать, но какой из них правильный? И главное — почему?

На Stack Overflow вопрос «PHP random string generator» набрал тысячи просмотров и сотни ответов. Но большинство из них — это просто готовые сниппеты без объяснения. Сегодня мы заглянем под капот PHP и разберёмся, как на самом деле работают генераторы случайных строк, чем отличается rand() от random_int() и почему ваш «случайный» пароль может быть совсем не случайным.

Что такое «случайность» в PHP?

В программировании случайность бывает двух видов: псевдослучайность и криптостойкая случайность. Разница между ними — как между игральной костью и предсказуемым калькулятором.

Псевдослучайные числа (PRNG) генерируются алгоритмом на основе начального зерна (seed). Если вы знаете зерно — вы знаете всю последовательность. Для простых задач (рандомизация цвета в дизайне, тестовые данные) этого достаточно. Но для токенов аутентификации и паролей — это катастрофа.

PHP предлагает несколько функций, и каждая из них — это разный уровень «случайности»:

  • rand() — классический PRNG, быстрый, но предсказуемый
  • mt_rand() — улучшенный алгоритм Mersenne Twister, быстрее и с большим периодом
  • random_int() и random_bytes() — криптостойкие генераторы (PHP 7+)
  • openssl_random_pseudo_bytes() — ещё один криптостойкий вариант

Как работает самый популярный сниппет?

Вот классический код, который гуляет по интернету:

<?php
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}
?>

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

Проблема №1: rand() и его зерно

Функция rand() в PHP по умолчанию использует алгоритм Linear Congruential Generator (LCG). Это один из самых старых и самых предсказуемых методов. Если вы не вызвали srand() с случайным зерном, PHP автоматически инициализирует генератор текущим временем (в микросекундах).

Зная точное время запуска скрипта (например, из логов сервера), злоумышленник может воспроизвести всю последовательность rand(). Для токена восстановления пароля — это прямой путь к взлому.

Проблема №2: Смещение вероятности (Bias)

Даже если не думать о безопасности, есть математическая проблема. Представьте, что rand(0, 61) (62 символа в строке). PHP внутренне использует диапазон от 0 до getrandmax() (обычно 2147483647). 2147483647 не делится нацело на 62. Остаток от деления — это 31. Это значит, что первые 31 число из диапазона имеют чуть большую вероятность выпасть, чем остальные.

На практике разница мизерная, но если вам нужна равномерно распределённая случайная строка, rand() — не лучший выбор. random_int() решает эту проблему, используя алгоритм, который гарантирует равномерное распределение.

Криптостойкий генератор: что под капотом?

В PHP 7 появились функции random_int() и random_bytes(). Они используют системный источник энтропии:

  • На Linux — /dev/urandom (или getrandom() системный вызов)
  • На Windows — CryptGenRandom()
  • На macOS — SecRandomCopyBytes()

Эти источники собирают «шум» из физического мира: движения мыши, нажатия клавиш, сетевые пакеты, задержки диска. Это настоящая случайность, которую невозможно предсказать.

Когда вы вызываете random_bytes(16), PHP просит у операционной системы 16 байт случайных данных. Операционная система собирает энтропию из пула ядра и возвращает результат. Это медленнее, чем rand(), но для токенов безопасности — это единственно верный путь.

Пишем правильный генератор случайных строк

Теперь, когда мы понимаем теорию, напишем безопасный и эффективный генератор. Вот версия, которую я рекомендую для продакшена:

<?php
function secureRandomString(int $length = 16): string {
    // Используем криптостойкий источник случайности
    $bytes = random_bytes($length);
    // Преобразуем байты в шестнадцатеричную строку
    return bin2hex($bytes);
}
?>

Этот код генерирует строку длиной $length * 2 символов (каждый байт — два hex-символа). Для токена длиной 32 символа (128 бит энтропии) это более чем достаточно.

Но что если нужна строка из букв и цифр, а не только hex? Вот универсальный генератор:

<?php
function alphanumericRandomString(int $length = 16): string {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    
    for ($i = 0; $i < $length; $i++) {
        // random_int гарантирует равномерное распределение
        $randomString .= $characters[random_int(0, $charactersLength - 1)];
    }
    
    return $randomString;
}
?>

Обратите внимание: мы используем random_int() вместо rand(). Это устраняет обе проблемы — и предсказуемость, и смещение вероятности.

Сравнение производительности: rand vs random_int

Многие избегают random_int() из-за страха, что это медленно. Давайте проверим. Напишем простой бенчмарк:

<?php
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
    rand(0, 61);
}
echo "rand: " . (microtime(true) - $start) . " сек\n";

$                

Блоги

Рекомендаций по книгам