Avatar ·

Генерация случайной строки в PHP: как сделать правильно и безопасно?

Всем привет! Я начинающий PHP-разработчик, пишу свой первый сайт с регистрацией пользователей. Мне нужно генерировать случайные токены для подтверждения email и временные пароли. Я нагуглил простой способ, но не уверен, что он правильный. Вот что я сейчас использую: ```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(32); // Например: "a3F8kL9mN2..." ``` Проблемы, которые я вижу: 1. Функция `rand()` — я читал, что она не криптостойкая. 2. Нет гарантии, что строка будет уникальной. 3. Непонятно, как сделать так, чтобы строка содержала только буквы или только цифры. Подскажите, как правильно генерировать случайные строки в PHP? Нужны примеры для разных целей: токены, пароли, ID. И важно, чтобы это было безопасно с точки зрения криптографии. Заранее спасибо! <<>>

Почему ваш текущий код небезопасен

Ваш подход с 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().