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

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

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

Начать курс

Как на самом деле работает генератор случайных строк в PHP? Заглядываем под капот

Вы когда-нибудь задумывались, что происходит, когда вы нажимаете «Сгенерировать пароль» на сайте? Или когда система создаёт уникальный идентификатор для вашей сессии? В девяти случаях из десяти за этим стоит генератор случайных строк на PHP. Казалось бы, что может быть проще: взял массив символов, перемешал, выдал результат. Но дьявол, как всегда, кроется в деталях.

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

Почему rand() — это зло? (спойлер: предсказуемость)

Самый популярный «детский» способ — использовать rand() в цикле:

<?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;
}
echo generateRandomString(); // Вывод: "aB3kL9xQ2m"
?>

Код работает. Но только если вам плевать на безопасность. Функция rand() использует линейный конгруэнтный генератор (LCG). Это детерминированный алгоритм: зная текущее состояние, можно предсказать все следующие числа. Более того, rand() по умолчанию инициализируется единицей, если вы не вызвали srand(). А если вызвали — seed часто берётся от time(), что даёт всего 2-3 миллиона вариантов. Перебор занимает секунды.

Вывод: rand() годится для игр и рандомного выбора аватарки, но категорически не подходит для генерации токенов, паролей или ключей API.

Встречайте mt_rand() — быстрее, но не безопаснее

В PHP 4 появился mt_rand(), основанный на вихре Мерсенна (Mersenne Twister). Он быстрее и имеет лучший период (2^19937 против 2^31 у rand). Многие до сих пор используют его как «продвинутую» версию:

<?php
function generateTokenMtRand($length = 32) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $token = '';
    for ($i = 0; $i < $length; $i++) {
        $token .= $chars[mt_rand(0, strlen($chars) - 1)];
    }
    return $token;
}
?>

Но и здесь есть подвох. Вихрь Мерсенна — криптографически нестойкий. По последовательности из 624 сгенерированных чисел можно восстановить внутреннее состояние генератора и предсказать все будущие значения. Для атаки на токен сброса пароля этого более чем достаточно.

Мораль: mt_rand() — отличный выбор для симуляций и тестов, но не для безопасности.

Святая троица: random_int(), CSPRNG и энтропия

Начиная с PHP 7.0, у нас есть random_int() и random_bytes(). Это криптографически безопасные генераторы псевдослучайных чисел (CSPRNG). Что это значит на практике?

  • Энтропия: PHP собирает «шум» из системы: время между нажатиями клавиш, движения мыши, сетевые пакеты, прерывания дисков. В Linux используется /dev/urandom, в Windows — CryptGenRandom.
  • Непредсказуемость: Даже зная алгоритм, нельзя восстановить seed или предсказать следующее число.
  • Равномерное распределение: Каждый байт выпадает с вероятностью ровно 1/256.

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

<?php
function generateSecureRandomString(int $length = 32, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string {
    if ($length < 1) {
        throw new \InvalidArgumentException('Длина должна быть больше 0');
    }
    $max = mb_strlen($keyspace, '8bit') - 1;
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $keyspace[random_int(0, $max)];
    }
    return $randomString;
}

// Пример использования:
echo generateSecureRandomString(16); // "X7kL9pQ2mR4vN8wZ"
echo generateSecureRandomString(64, 'abcdefghijklmnopqrstuvwxyz'); // только буквы нижнего регистра
?>

Обратите внимание на mb_strlen(..., '8bit'). Это защита от случая, если в $keyspace попадёт многобайтовый символ (например, UTF-8). Мы принудительно считаем длину в байтах.

Альтернативный подход: random_bytes() + битовые маски

Если вам нужно сгенерировать очень длинную строку (например, 256 символов), цикл с random_int() может быть медленным. Есть более элегантный способ — использовать random_bytes() и преобразовать сырые байты в нужный алфавит:

<?php
function generateStringFromBytes(int $length = 32): string {
    // Получаем сырые байты
    $bytes = random_bytes($length);
    // Преобразуем в hex-строку (каждый байт -> 2 hex-символа)
    return bin2hex($bytes); // "a1b2c3d4e5f6..."
}

// Для кастомного алфавита используем base64:
function generateBase64String(int $length = 32): string {
    $bytes = random_bytes($length);
    // Убираем символы '+' и '/', заменяем на буквы
    return substr(strtr(base64_encode($bytes), '+/', '-_'), 0, $length);
}
?>

Метод с bin2hex() даёт только hex-символы (0-9, a-f). Если нужен полный алфавит (A-Z, a-z, 0-9), используйте base64_encode() с заменой неалфавитных символов. Этот способ быстрее, потому что вызов системного CSPRNG происходит один раз, а не в цикле.

Подводные камни и best practices

Блоги

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