Avatar ·

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

Всем привет! Я новичок в PHP и пишу небольшой сайт. Мне нужно генерировать случайные строки для токенов сессий, временных паролей и идентификаторов ссылок. Я нашел в интернете такой код: ```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()` — слышал, что она не очень случайная. 2. Если нужно 1000 строк, скрипт работает медленно. 3. Не знаю, подойдет ли это для криптографических целей (токены). Можно ли использовать эту функцию для генерации паролей? Как сделать генерацию по-настоящему случайной и безопасной? Есть ли в PHP встроенные функции для этого? Буду благодарен за подробное объяснение с примерами. Версия PHP — 8.2. <<>>

Основная проблема: небезопасный генератор случайных чисел

Ваш код использует rand() — это устаревшая функция, которая дает предсказуемые результаты. Для токенов и паролей это недопустимо. В PHP есть три уровня генерации случайности:

  • rand() / mt_rand() — только для игр, симуляций, не для безопасности.
  • random_int() — криптографически безопасный, но возвращает целое число.
  • random_bytes() — лучший выбор для строк: возвращает случайные байты.

Правильное решение: используем random_bytes() и bin2hex()

Самый простой и безопасный способ — сгенерировать случайные байты и преобразовать их в hex-строку:

function generateSecureToken(int $length = 32): string {
    // Длина в байтах: нам нужно в 2 раза меньше, так как hex удваивает длину
    $bytes = random_bytes($length / 2);
    return bin2hex($bytes);
}

// Пример: 64 символа
echo generateSecureToken(64); // Вывод: a4f8c9d0e1b2...

Кастомная строка из произвольного набора символов

Если нужны буквы + цифры (без hex), используйте random_int() для индексации строки:

function generateRandomString(int $length = 16): string {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charsLen = strlen($chars) - 1;
    $result = '';

    for ($i = 0; $i < $length; $i++) {
        $result .= $chars[random_int(0, $charsLen)];
    }

    return $result;
}

echo generateRandomString(20); // Пример: "aF3kL9pQwZ8vR2xY7mN1"

Важно: random_int() выбрасывает Exception, если нет источника энтропии — это хорошо, так как вы узнаете о проблеме сразу.

Для токенов и паролей — используйте встроенные функции

PHP 8+ предлагает готовые решения:

  • Токены: bin2hex(random_bytes(32)) — создает 64-символьный hex-токен.
  • Пароли: используйте password_hash() с PASSWORD_BCRYPT, а не генерируйте их вручную.
  • UUID v4: ramsey/uuid или кастомная функция на random_bytes().

Сравнение производительности

Для 1000 строк длиной 32 символа:

  • rand() — ~0.002 сек (но небезопасно)
  • random_int() — ~0.008 сек (безопасно, приемлемо)
  • random_bytes() + bin2hex — ~0.001 сек (самый быстрый)

Вывод: Для любых задач, связанных с безопасностью (токены, пароли, сессии), используйте только random_bytes() или random_int(). Функция rand() подходит только для неответственных задач вроде выбора случайного баннера.

Полный пример для production

class RandomStringGenerator {
    public static function alphanumeric(int $length = 16): string {
        $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charsLen = strlen($chars) - 1;
        $result = '';
        for ($i = 0; $i < $length; $i++) {
            $result .= $chars[random_int(0, $charsLen)];
        }
        return $result;
    }

    public static function hexToken(int $bytes = 32): string {
        return bin2hex(random_bytes($bytes));
    }

    public static function password(int $length = 12): string {
        // Включает спецсимволы
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
        $charsLen = strlen($chars) - 1;
        $result = '';
        for ($i = 0; $i < $length; $i++) {
            $result .= $chars[random_int(0, $charsLen)];
        }
        return $result;
    }
}

// Использование:
echo RandomStringGenerator::hexToken(32); // 64-символьный токен
echo RandomStringGenerator::password(16); // Надежный пароль
<<>>

Альтернативный подход: использование sodium расширения

Если у вас установлено расширение sodium (доступно в PHP 7.2+ по умолчанию), можно использовать его для генерации случайных строк. Это особенно удобно для криптографических ключей:

function sodiumRandomString(int $length = 32): string {
    // Генерируем случайные байты
    $bytes = sodium_randombytes_buf($length);
    // Преобразуем в base64 без символов +/=
    return sodium_bin2base64($bytes, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
}

echo sodiumRandomString(16); // Вывод: "aB3dEfGhIjKlMnOp" (URL-safe)

Преимущества:

  • Максимальная криптостойкость (используется libsodium).
  • URL-safe base64 без лишних символов.
  • Производительность выше, чем у random_bytes().

Недостатки: требует расширение, но оно есть в большинстве современных хостингов.

Также можно комбинировать: random_bytes() для простоты, sodium для максимальной безопасности.