Avatar ·

Как сгенерировать случайную буквенно-цифровую строку в Java?

Всем привет! Пишу небольшое приложение для генерации паролей и временных идентификаторов. Нужно создавать случайные строки, содержащие только латинские буквы (a-z, A-Z) и цифры (0-9). Например, что-то вроде "k3R9tB2x" или "A7fD1qP8". Я пробовал использовать UUID, но он выдает строки с дефисами, которые мне не нужны. Пытался написать свою логику на основе Math.random(), но код получается громоздким и ненадежным — иногда выдает только буквы или только цифры. Вот что я наваял: ```java String generateRandomString(int length) { String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { int index = (int)(Math.random() * chars.length()); sb.append(chars.charAt(index)); } return sb.toString(); } ``` Это работает, но я не уверен, что это правильно с точки зрения производительности и безопасности. Может быть, есть встроенные средства в Java для такой задачи? Нужно, чтобы строка была непредсказуемой (для использования в токенах) и работала быстро. Подскажите, как правильно генерировать случайные буквенно-цифровые строки в Java? <<>>

Лучший способ: использование SecureRandom и готового набора символов

Ваш подход в целом рабочий, но есть несколько важных улучшений. Главная проблема — использование Math.random(), который не подходит для криптографически стойких токенов. Для генерации паролей и идентификаторов используйте java.security.SecureRandom. Вот оптимальное решение:

import java.security.SecureRandom;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;

public class RandomStringGenerator {

    private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String LOWER = UPPER.toLowerCase(Locale.ROOT);
    private static final String DIGITS = "0123456789";
    private static final String ALPHANUM = UPPER + LOWER + DIGITS;

    private final Random random;
    private final char[] symbols;
    private final char[] buf;

    public RandomStringGenerator(int length, Random random, String symbols) {
        if (length < 1) throw new IllegalArgumentException();
        if (symbols.length() < 2) throw new IllegalArgumentException();
        this.random = Objects.requireNonNull(random);
        this.symbols = symbols.toCharArray();
        this.buf = new char[length];
    }

    /**
     * Создаёт генератор для буквенно-цифровых строк заданной длины.
     */
    public RandomStringGenerator(int length, Random random) {
        this(length, random, ALPHANUM);
    }

    /**
     * Создаёт генератор с SecureRandom по умолчанию.
     */
    public RandomStringGenerator(int length) {
        this(length, new SecureRandom());
    }

    public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx) {
            buf[idx] = symbols[random.nextInt(symbols.length)];
        }
        return new String(buf);
    }

    // Пример использования:
    public static void main(String[] args) {
        RandomStringGenerator gen = new RandomStringGenerator(10);
        System.out.println(gen.nextString()); // например: "k3R9tB2xQ7"
    }
}

Почему это решение лучше?

  • Безопасность: SecureRandom использует криптографически стойкий генератор (например, /dev/urandom на Linux). Math.random() предсказуем и не подходит для токенов.
  • Производительность: Массив символов buf переиспользуется для всех вызовов nextString(), что снижает нагрузку на сборщик мусора.
  • Гибкость: Вы можете легко менять набор символов — убрать цифры, добавить спецсимволы или использовать только заглавные буквы.
  • Чистота кода: Вся логика инкапсулирована в одном классе, легко тестируется и переиспользуется.

Короткий однострочник для простых случаев

Если вам не нужна кастомная настройка и безопасность не критична (например, для тестовых данных), можно использовать Apache Commons Lang:

import org.apache.commons.lang3.RandomStringUtils;

String result = RandomStringUtils.randomAlphanumeric(10);
// Результат: "aB3dE5fG7h"

Но для продакшена рекомендую первый вариант с SecureRandom.

<<>>

Альтернативный подход: генерация на основе UUID с фильтрацией

Если вам лень писать свой генератор, можно адаптировать встроенный UUID. Удалите из него дефисы и возьмите нужное количество символов:

import java.util.UUID;

public static String generateFromUUID(int length) {
    String uuid = UUID.randomUUID().toString().replace("-", "");
    // UUID содержит 32 символа (0-9, a-f), без заглавных букв
    return uuid.substring(0, Math.min(length, 32));
}

Минусы этого подхода:

  • UUID использует только шестнадцатеричные символы (0-9, a-f), что сужает алфавит до 16 символов вместо 62.
  • Нет заглавных букв, что снижает энтропию (для 10-символьной строки: 16^10 вместо 62^10 комбинаций).
  • Максимальная длина — 32 символа.

Когда это уместно: Для генерации коротких идентификаторов в небезопасных сценариях (например, для логов или тестовых данных). Для паролей и токенов — не используйте.

Ещё один простой вариант — использовать java.util.Random с вашим исходным кодом, но заменить Math.random() на random.nextInt(chars.length()). Это даст более равномерное распределение:

Random random = new Random();
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
    sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();

Этот код проще, но всё ещё не криптостойкий. Для большинства задач лучше использовать первый ответ с SecureRandom.