Вопрос: Как создать случайную буквенно-цифровую строку?


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


1438


источник


Ответы:


Алгоритм

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

Реализация

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

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-адреса или повторно введены вручную.

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

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

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

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

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

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

Сравнение с UUID

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

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

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


1383



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

726



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();
}

462



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

Пример:

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не рекомендуется.


451



В одной строке:

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

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


90



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

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

84



using Dollar should be simple as:

// "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));
    }
}

it outputs something like that:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7

35



This is easily achievable without any external libraries.

1. Cryptographic Pseudo Random Data Generation

First you need a cryptographic PRNG. Java has SecureRandom for that typically uses the best entropy source on the machine (e.g. /dev/random) . Read more here.

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

Note: SecureRandom is the slowest, but most secure way in Java of generating random bytes. I do however recommend NOT considering performance here since it usually has no real impact on your application unless you have to generate millions of tokens per second.

2. Required Space of Possible Values

Next you have to decide "how unique" your token needs to be. The whole and only point of considering entropy is to make sure that the system can resist brute force attacks: the space of possible values must be so large that any attacker could only try a negligible proportion of the values in non-ludicrous time1. Unique identifiers such as random UUID have 122bit of entropy (ie. 2^122 = 5.3x10^36) - the chance of collision is "*(...) for there to be a one in a billion chance of duplication, 103 trillion version 4 UUIDs must be generated2". We will choose 128 bit since it fits exactly into 16 bytes and is seen as highly sufficient for being unique for basically every, but the most extreme, use cases and you don't have to think about duplicates. Here is a simple comparison table of entropy including simple analysis of the birthday problem.

comparison of token sizes

For simple requirements 8 or 12 byte length might suffice, but with 16 bytes you are on the "safe side".

And that's basically it. Last thing is to think about encoding so it can be represented as a printable text (read, a String).

3. Binary to Text Encoding

Typical encodings include:

  • Base64 every character encodes 6bit creating a 33% overhead. Unfortunatly there is no standard implementation in the JDK (7 and below - there is in Android and Java 8+). But numerous libraries exist that add this. The downside is, that standard Base64 is not safe for eg. urls and as filename in most file systems requiring additional encoding (e.g. url encoding) or the URL safe version of Base64 is used. Example encoding 16 bytes with padding: XfJhfv3C0P6ag7y9VQxSbw==

  • Base32 every character encodes 5bit creating a 40% overhead. This will use A-Z and 2-7 making it reasonably space efficient while being case-insensitive alpha-numeric. There is no standard implementation in the JDK. Example encoding 16 bytes without padding: WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16 (hex) every character encodes 4bit requiring 2 characters per byte (ie. 16 byte create a string of length 32). Therefore hex is less space efficient than Base32 but is safe to use in most cases (url) since it only uses 0-9 and A to F. Example encoding 16 bytes: 4fa3dd0f57cb3bf331441ed285b27735. See a SO discussion about converting to hex here.

Additional encodings like Base85 and the exotic Base122 exist with better/worse space efficiency. You can create your own encoding (which basically most answers in this thread do) but I would advise against it, if you don't have very specific requirements. See more encoding schemes in the Wikipedia article.

4. Summary and Example

  • Use SecureRandom
  • Use at least 16 bytes (2^128) of possible values
  • Encode according to your requirements (usually hex or base32 if you need it to be alpha-numeric)

Don't

  • ... use your home brew encoding: better maintainable and readable for others if they see what standard encoding you use instead of weird for loops creating chars at a time.
  • ... use UUID: you are wasting 6bits of entropy and have verbose string representation

Example: Hex Token Generator

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

Example: Tool

If you want a ready-to-use cli tool you may use dice: https://github.com/patrickfav/dice


32



Here it is in 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();
  }
}

Here's a sample run:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy

28



Surprising no-one here has suggested it but:

import java.util.UUID

UUID.randomUUID().toString();

Easy.

Benefit of this is UUIDs are nice and long and guaranteed to be almost impossible to collide.

Wikipedia has a good explanation of it:

" ...only after generating 1 billion UUIDs every second for the next 100 years, the probability of creating just one duplicate would be about 50%."

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

The first 4 bits are the version type and 2 for the variant so you get 122 bits of random. So if you want to you can truncate from the end to reduce the size of the UUID. It's not recommended but you still have loads of randomness, enough for your 500k records easy.


28



A short and easy solution, but uses only lowercase and numerics:

Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);

The size is about 12 digits to base 36 and can't be improved further, that way. Of course you can append multiple instances.


21