Генерация случайной строки в PHP: как сделать правильно и безопасно?
Почему ваш текущий код небезопасен
Ваш подход с rand() действительно имеет серьёзные недостатки. rand() использует линейный конгруэнтный генератор (LCG), который предсказуем — зная несколько значений, можно восстановить всё состояние генератора. Для токенов аутентификации и паролей это недопустимо. Кроме того, rand() не гарантирует равномерное распределение.
Правильное решение: используем random_int() и random_bytes()
Начиная с PHP 7, появились криптостойкие функции random_int() и random_bytes(). Они используют CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) операционной системы. Вот улучшенная версия:
<?php
function generateRandomString(int $length = 32, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string
{
if ($length < 1) {
throw new \InvalidArgumentException('Length must be a positive integer');
}
$pieces = [];
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$pieces[] = $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
// Примеры использования:
echo generateRandomString(16); // 16 символов: "kF8mZ2xP9qL4vR1w"
echo generateRandomString(32, 'abcdefghijklmnopqrstuvwxyz'); // только строчные
echo generateRandomString(8, '0123456789'); // только цифры (для OTP-кодов)
?>
Специализированные решения для разных задач
- Токены для API/аутентификации — используйте
random_bytes()с hex-кодированием:
$token = bin2hex(random_bytes(32)); // 64 символа, криптостойкий
- Временные пароли — комбинируйте буквы, цифры и спецсимволы:
$password = generateRandomString(12, 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%');
Примечание: исключены похожие символы (o, 0, l, 1, i, I) для удобства чтения.
- Уникальные ID — используйте UUID v4:
// PHP 8+ встроенная поддержка:
$uuid = uuid_create(UUID_TYPE_RANDOM); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// Для PHP 7.4 и старше — через random_bytes:
function guidv4(): string {
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // версия 4
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // вариант
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
Важные рекомендации по безопасности
- Не изобретайте велосипед — используйте встроенные функции PHP или проверенные библиотеки (например,
ramsey/uuid). - Храните пароли правильно — даже сгенерированные пароли нужно хэшировать через
password_hash(). - Для сессий — PHP сам генерирует безопасные ID, не нужно создавать свои.
- Проверяйте энтропию — для токенов используйте минимум 128 бит (16 байт = 32 hex-символа).
Бонус: однострочники для быстрых задач
// Случайная строка из букв и цифр (PHP 8.1+):
$random = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 10);
// Случайные байты в base64 (безопасно для URL):
$token = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
Помните: безопасность — это процесс. Даже лучший генератор не спасёт, если вы храните токены в открытом виде или передаёте через HTTP.
<<Альтернативный подход: использование специализированных функций
Если вы не хотите писать свою реализацию, в PHP есть готовые решения для конкретных задач:
<?php
// 1. Для создания хэшей (например, для CSRF-токенов):
$hash = hash('sha256', random_bytes(32)); // 64 символа
// 2. Для генерации паролей (PHP 8.4+):
$password = \Password::generate(); // "kF8mZ2xP9qL4vR1w!@#"
// 3. Для создания читаемых кодов (без путаницы):
function readableCode(int $length = 8): string {
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // без 0, O, I, L
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $chars[random_int(0, strlen($chars) - 1)];
}
// Добавляем разделитель для читаемости
return chunk_split($code, 4, '-'); // "K8FM-Z2XP-9QL4"
}
echo readableCode(12); // "K8FM-Z2XP-9QL4"
?>
Когда использовать каждый метод
- Хэши — для токенов, которые будут сравниваться (например, "запомнить меня").
- Пароли — только через
password_hash()после генерации. - Коды подтверждения — цифровые (6-8 символов) для SMS/email OTP.
Главное правило: никогда не используйте rand() или mt_rand() для безопасности. Только random_int() или random_bytes().