Как сгенерировать случайную буквенно-цифровую строку? - PullRequest
1606 голосов
/ 03 сентября 2008

Я искал простой алгоритм Java для генерации псевдослучайной буквенно-цифровой строки. В моей ситуации он будет использоваться в качестве уникального идентификатора сеанса / ключа, который «вероятно» будет уникальным в течение 500K+ генерации (мои потребности на самом деле не требуют ничего более сложного)

В идеале я мог бы указать длину в зависимости от моих потребностей в уникальности. Например, сгенерированная строка длиной 12 может выглядеть примерно так: "AEYGF7K0DM1X".

Ответы [ 45 ]

1485 голосов
/ 03 сентября 2008

Алгоритм

Чтобы сгенерировать случайную строку, объедините символы, выбранные случайным образом из набора допустимых символов, пока строка не достигнет желаемой длины.

Осуществление

Вот несколько довольно простых и очень гибких кодов для генерации случайных идентификаторов. Прочтите следующую информацию для получения важных замечаний по применению.

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

public class RandomString {

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

    public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static final String lower = upper.toLowerCase(Locale.ROOT);

    public static final String digits = "0123456789";

    public static final String alphanum = upper + lower + digits;

    private final Random random;

    private final char[] symbols;

    private final char[] buf;

    public RandomString(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];
    }

    /**
     * Create an alphanumeric string generator.
     */
    public RandomString(int length, Random random) {
        this(length, random, alphanum);
    }

    /**
     * Create an alphanumeric strings from a secure generator.
     */
    public RandomString(int length) {
        this(length, new SecureRandom());
    }

    /**
     * Create session identifiers.
     */
    public RandomString() {
        this(21);
    }

}

Примеры использования

Создание небезопасного генератора для 8-значных идентификаторов:

RandomString gen = new RandomString(8, ThreadLocalRandom.current());

Создание безопасного генератора для идентификаторов сеансов:

RandomString session = new RandomString();

Создание генератора с удобочитаемыми кодами для печати. Строки длиннее буквенно-цифровых строк, чтобы компенсировать использование меньшего количества символов:

String easy = RandomString.digits + "ACEFGHJKLMNPQRUVWXYabcdefhijkprstuvwx";
RandomString tickets = new RandomString(23, new SecureRandom(), easy);

Использовать в качестве идентификаторов сессии

Создание идентификаторов сеансов, которые могут быть уникальными, недостаточно, или вы можете просто использовать простой счетчик. Злоумышленники перехватывают сессии, когда используются предсказуемые идентификаторы.

Существует противоречие между длиной и безопасностью. Более короткие идентификаторы легче угадать, потому что возможностей меньше. Но более длинные идентификаторы потребляют больше памяти и пропускной способности. Помогает больший набор символов, но он может вызвать проблемы с кодировкой, если идентификаторы включены в URL-адреса или введены вручную.

Основной источник случайности или энтропии для идентификаторов сеансов должен исходить от генератора случайных чисел, предназначенного для криптографии. Однако инициализация этих генераторов иногда может быть вычислительно дорогой или медленной, поэтому следует предпринять усилия для их повторного использования, когда это возможно.

Использовать в качестве идентификаторов объектов

Не каждое приложение требует безопасности. Случайное назначение может быть эффективным способом для нескольких объектов генерировать идентификаторы в совместно используемом пространстве без какой-либо координации или разделения. Координация может быть медленной, особенно в кластерной или распределенной среде, а разделение пространства вызывает проблемы, когда у сущностей в конечном итоге слишком большие или слишком большие доли.

Идентификаторы, сгенерированные без принятия мер по их непредсказуемости, должны быть защищены другими средствами, если злоумышленник сможет их просматривать и манипулировать, как это происходит в большинстве веб-приложений. Должна быть отдельная система авторизации, которая защищает объекты, чей идентификатор может быть угадан злоумышленником без разрешения доступа.

Необходимо также соблюдать осторожность, чтобы использовать идентификаторы, которые являются достаточно длинными, чтобы сделать коллизии маловероятными, учитывая ожидаемое общее количество идентификаторов. Это называется «парадоксом дня рождения». Вероятность столкновения, p , составляет приблизительно n 2 / (2q x ), где n - количество фактически сгенерированных идентификаторов, q - количество различных символов в алфавите, а x - длина идентификаторов. Это должно быть очень маленькое число, например, 2 ‑50 или меньше.

Работа над этим показывает, что вероятность столкновения между 500k 15-символьными идентификаторами составляет около 2 ‑52 , что, вероятно, менее вероятно, чем необнаруженные ошибки космических лучей и т. Д.

Сравнение с UUID

В соответствии с их спецификацией UUID не предназначены для непредсказуемости, и не следует использовать в качестве идентификаторов сеанса.

UUID в их стандартном формате занимают много места: 36 символов только для 122 бит энтропии. (Не все биты «случайного» UUID выбираются случайным образом.) Случайно выбранная буквенно-цифровая строка упаковывает больше энтропии всего в 21 символ.

UUID не являются гибкими; они имеют стандартизированную структуру и расположение. Это их главное достоинство, а также их главная слабость. При сотрудничестве с внешней стороной может оказаться полезной стандартизация, предлагаемая UUID. Для чисто внутреннего использования они могут быть неэффективными.

782 голосов
/ 03 сентября 2008

Java предоставляет способ сделать это напрямую. Если вы не хотите тире, их легко удалить. Просто используйте uuid.replace("-", "")

import java.util.UUID;

public class randomStringGenerator {
    public static void main(String[] args) {
        System.out.println(generateString());
    }

    public static String generateString() {
        String uuid = UUID.randomUUID().toString();
        return "uuid = " + uuid;
    }
}

Выход:

uuid = 2d7428a6-b58c-4008-8575-f05549f16316
515 голосов
/ 01 октября 2008
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();

String randomString( int len ){
   StringBuilder sb = new StringBuilder( len );
   for( int i = 0; i < len; i++ ) 
      sb.append( AB.charAt( rnd.nextInt(AB.length()) ) );
   return sb.toString();
}
471 голосов
/ 04 сентября 2008

Если вы счастливы использовать классы Apache, вы можете использовать org.apache.commons.text.RandomStringGenerator (commons-text).

Пример:

RandomStringGenerator randomStringGenerator =
        new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .build();
randomStringGenerator.generate(12); // toUpperCase() if you want

Поскольку commons-lang 3.6, RandomStringUtils устарела.

103 голосов
/ 17 сентября 2009

В одну строку:

Long.toHexString(Double.doubleToLongBits(Math.random()));

http://mynotes.wordpress.com/2009/07/23/java-generating-random-string/

98 голосов
/ 20 июля 2012

Для этого вы можете использовать библиотеку Apache: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();
54 голосов
/ 28 мая 2017

Это легко достижимо без каких-либо внешних библиотек.

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

Сначала вам нужен криптографический PRNG. В Java есть SecureRandom, для которого обычно используется лучший источник энтропии на машине (например, /dev/random). Подробнее здесь.

SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);

Примечание: SecureRandom - самый медленный, но самый безопасный способ в Java генерирования случайных байтов. Однако я рекомендую НЕ рассматривать производительность здесь, поскольку она обычно не оказывает реального влияния на ваше приложение, если вам не нужно генерировать миллионы токенов в секунду.

2. Требуемое пространство возможных значений

Затем вы должны решить, насколько уникальным должен быть ваш токен. Единственный смысл рассмотрения энтропии заключается в том, чтобы убедиться, что система может противостоять атакам грубой силы: пространство возможных значений должно быть настолько большим, что любой злоумышленник может попробовать пренебрежимо малую долю значений в несмешное время 1 . Уникальные идентификаторы, такие как случайные UUID, имеют 122-битную энтропию (т. Е. 2 ​​^ 122 = 5,3x10 ^ 36) - вероятность столкновения равна "* (...), так как существует один в с миллиардной вероятностью дублирования необходимо сгенерировать 103 триллиона UUID версии 4 2 ". Мы выберем 128 бит, так как он вписывается точно в 16 байтов и рассматривается как весьма достаточный для того, чтобы быть уникальным в основном для каждого, но наиболее экстремального варианта использования, и у вас нет думать о дубликатах. Вот простая таблица сравнения энтропии, включая простой анализ проблемы дня рождения .

comparison of token sizes

Для простых требований может хватить длины 8 или 12 байтов, но с 16 байтами вы на «безопасной стороне».

И это в основном все. И последнее, что нужно подумать о кодировке, чтобы ее можно было представить в виде печатного текста (читай, String).

3. Бинарное в текстовое кодирование

Типичные кодировки включают в себя:

  • Base64 каждый символ кодирует 6 бит, создавая 33% накладных расходов. К счастью, есть стандартные реализации в Java 8 + и Android . В более старой Java вы можете использовать любую из многочисленных сторонних библиотек . Если вы хотите, чтобы ваши токены были безопасными для URL, используйте версию RFC4648 url-safe (которая обычно поддерживается большинством реализаций). Пример кодирования 16 байтов с заполнением: XfJhfv3C0P6ag7y9VQxSbw==

  • Base32 каждый символ кодирует 5 бит, создавая 40% накладных расходов. При этом будут использоваться A-Z и 2-7, что делает его достаточно экономичным при сохранении буквенно-цифрового значения без учета регистра. В JDK нет стандартной реализации . Пример кодирования 16 байтов без заполнения: WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16 (шестнадцатеричный) каждый символ кодирует 4 бита, требуя 2 символа на байт (т. Е. 16 байт создают строку длиной 32). Поэтому hex меньше места, чем Base32, но в большинстве случаев безопасно использовать (url), поскольку он использует только от 0-9 и A до F. Пример кодирования 16 байтов: 4fa3dd0f57cb3bf331441ed285b27735. См. Обсуждение SO о преобразовании в гекс здесь.

Дополнительные кодировки, такие как Base85 и экзотические Base122 существуют с лучшей / худшей эффективностью пространства. Вы можете создать свою собственную кодировку (что обычно делают большинство ответов в этой теме), но я бы посоветовал против этого, если у вас нет особых требований. См. больше схем кодирования в статье Википедии.

4. Сводка и пример

  • Использование SecureRandom
  • Используйте не менее 16 байтов (2 ^ 128) возможных значений
  • Кодировать в соответствии с вашими требованиями (обычно hex или base32, если вам нужно, чтобы оно было буквенно-цифровым)

Не

  • ... используйте кодировку домашнего приготовления: лучше обслуживаемую и удобочитаемую для других, если они видят, какую стандартную кодировку вы используете вместо странных для циклов, создающих символы одновременно. ... используйте UUID: нет гарантий случайности; вы тратите 6 бит энтропии и имеете подробное представление строк

Пример: Генератор шестнадцатеричных токенов

public static String generateRandomHexToken(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return new BigInteger(1, token).toString(16); //hex encoding
}

//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd

Пример: Base64 Token Generator (безопасный URL)

public static String generateRandomBase64Token(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return Base64.getUrlEncoder().withoutPadding().encodeToString(token); //base64 encoding
}

//generateRandomBase64Token(16) -> EEcCCAYuUcQk7IuzdaPzrg

Пример: Java CLI Tool

Если вам нужен готовый инструмент cli, вы можете использовать кости: https://github.com/patrickfav/dice

41 голосов
/ 01 февраля 2010

с использованием Доллар должно быть простым как:

// "0123456789" + "ABCDE...Z"
String validCharacters = $('0', '9').join() + $('A', 'Z').join();

String randomString(int length) {
    return $(validCharacters).shuffle().slice(length).toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int i : $(5)) {
        System.out.println(randomString(12));
    }
}

выводит что-то вроде этого:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7
31 голосов
/ 03 сентября 2008

Вот это на Java:

import static java.lang.Math.round;
import static java.lang.Math.random;
import static java.lang.Math.pow;
import static java.lang.Math.abs;
import static java.lang.Math.min;
import static org.apache.commons.lang.StringUtils.leftPad

public class RandomAlphaNum {
  public static String gen(int length) {
    StringBuffer sb = new StringBuffer();
    for (int i = length; i > 0; i -= 12) {
      int n = min(12, abs(i));
      sb.append(leftPad(Long.toString(round(random() * pow(36, n)), 36), n, '0'));
    }
    return sb.toString();
  }
}

Вот пример прогона:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy
31 голосов
/ 25 октября 2012

Удивительно, что никто здесь не предложил это, но:

import java.util.UUID

UUID.randomUUID().toString();

Easy.

Преимущество этого заключается в том, что UUID красивы и длинны, а столкновение с ними практически невозможно.

В Википедии есть хорошее объяснение этого:

"... только после генерирования 1 миллиарда UUID каждую секунду в течение следующих 100 лет вероятность создания только одного дубликата будет около 50%."

http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates

Первые 4 бита являются типом версии и 2 для варианта, поэтому вы получаете 122 бита случайным образом. Так что если вы хотите , вы можете обрезать с конца, чтобы уменьшить размер UUID. Это не рекомендуется, но у вас все еще есть множество случайностей, достаточно для ваших 500k записей легко.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...