Как на самом деле работает генератор случайных строк в 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
Блоги
Рекомендаций по книгам
Читайте также
Как заменить все вхождения строки в JavaScript: 4 лучших способа 2025
TypeScript в 2025 году: новые возможности и обзор ключевых изменений
WebSocket на C++: создание real-time приложений с нуля
Как писать документацию для C# проекта: лучшие практики и примеры
Rust в портфолио: 5 проектов, которые впечатлят любого работодателя
Блоги
Рекомендаций по книгам
Читайте также
WebSocket на C++: создание real-time приложений с нуля