Як генерувати випадкову буквено-числову рядок?


1741

Я шукав простий алгоритм Java для створення псевдовипадкової алфа-числової рядки. У моїй ситуації він би використовувався як унікальний ідентифікатор сеансу / ключа, який, "ймовірно", буде унікальним протягом 500K+покоління (мої потреби насправді не вимагають нічого більш складного).

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



58
Навіть враховуючи парадокс дня народження, якщо ви використовуєте 12 буквено-цифрових символів (всього 62), вам все одно знадобиться понад 34 мільярди рядків, щоб досягти парадоксу. І парадокс у день народження все одно не гарантує зіткнення, він просто говорить, що це більше 50% шансів.
NullUserException

4
@NullUserException 50% шансів на успіх (за кожну спробу) проклято високий: навіть при 10 спробах рівень успішності становить 0,999. Зважаючи на це і той факт, що ви можете спробувати ЛОТ протягом 24 годин на увазі, вам не потрібно 34 мільярди рядків, щоб бути впевненим, щоб вгадати хоча б одну з них. Ось чому деякі маркери сеансу повинні бути дійсно дуже довгими.
Pijusn

16
Ці 3 однорядкові коди дуже корисні, я здогадуюсь ..Long.toHexString(Double.doubleToLongBits(Math.random())); UUID.randomUUID().toString(); RandomStringUtils.randomAlphanumeric(12);
Manindar

18
@Pijusn Я знаю, що це старе, але ... "50% шанс" у парадоксах до дня народження НЕ "за спробу", це "50% шанс, що з (в даному випадку) 34 мільярдів струн існує на принаймні одна пара дублікатів ". Ви повинні були б 1.6 сепТу Ілліон - 1.6e21 - записи в базі даних для того , щоб там бути 50% шансу спробувати.
Tin Wizard

Відповіді:


1541

Алгоритм

Щоб створити випадковий рядок, об'єднайте символи, намальовані випадковим чином з набору прийнятних символів, поки рядок не досягне потрібної довжини.

Впровадження

Ось досить простий і дуже гнучкий код для генерації випадкових ідентифікаторів. Прочитайте інформацію, що випливає з важливих приміток до програми.

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 x ), де n - кількість фактично створених ідентифікаторів, q - кількість різних символів в алфавіті, а x - довжина ідентифікаторів. Це має бути дуже невелика кількість, наприклад 2-50 або менше.

Опрацювання цього показує, що ймовірність зіткнення між ідентифікаторами 500k 15 символів становить приблизно 2–52 , що, ймовірно, менше ймовірність, ніж невиявлені помилки космічних променів тощо.

Порівняння з UUID

Відповідно до їх специфікації, UUID не розроблені як непередбачувані, і їх не слід використовувати як ідентифікатори сеансу.

UUID в їх стандартному форматі займають багато місця: 36 символів лише 122 біти ентропії. (Не всі біти "випадкового" UUID вибрані випадковим чином.) Вибрана випадковим чином буквено-числова рядок пакує більше ентропії всього лише 21 символом.

UUID не є гнучкими; вони мають стандартизовану структуру та компонування. Це їхня головна чеснота, а також їх основна слабкість. У співпраці із стороною стороною може бути корисна стандартизація, запропонована UUID. Для чисто внутрішнього використання вони можуть бути неефективними.


6
Якщо вам потрібні пробіли, ви можете .replaceAll("\\d", " ");скористатися кінцем return new BigInteger(130, random).toString(32);рядка, щоб здійснити підкачку з регулярними виразами. Він замінює всі цифри пробілами. Для мене це чудово: я використовую це як заміну для передового
Лорема Іпсума

4
@weisjohn Це гарна ідея. Ви можете зробити щось подібне за допомогою другого методу, видаливши цифри symbolsі замість цього використовуючи пробіл; ви можете керувати середньою довжиною "слова", змінюючи кількість пробілів у символах (більше зустрічей для коротших слів). Для дійсно надмірно підробленого текстового рішення ви можете використовувати ланцюжок Маркова!
erickson

4
Ці ідентифікатори вибираються випадковим чином із простору певного розміру. Вони можуть бути 1 символом. Якщо вам потрібна фіксована довжина, ви можете використовувати друге рішення з SecureRandomекземпляром, присвоєним randomзмінній.
erickson

15
Чому .toString (32), а не .toString (36)?
ejain

17
@ejain, тому що 32 = 2 ^ 5; кожен символ буде представляти рівно 5 біт, а 130 біт можна рівномірно розділити на символи.
erickson

817

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

33
Будьте уважні, що це рішення генерує лише випадкову рядок із шістнадцятковими символами. Що може бути добре в деяких випадках.
Дейв

5
Клас UUID корисний. Однак вони не такі компактні, як ідентифікатори, отримані моїми відповідями. Це може бути проблемою, наприклад, у URL-адресах. Залежить від ваших потреб.
erickson

6
@Ruggs - мета - алфа -числові рядки. Як розширюється вихід до будь-яких можливих байтів?
erickson

72
Згідно RFC4122, використання UUID в якості лексем є поганою ідеєю: не вважайте, що UUID важко здогадатися; наприклад, вони не повинні використовуватися в якості можливостей безпеки (ідентифікатори, чиїм володінням надається доступ), наприклад. Передбачуване джерело випадкових чисел погіршить ситуацію. ietf.org/rfc/rfc4122.txt
Somatik

34
UUID.randomUUID().toString().replaceAll("-", "");робить рядок алфавітно-числовим, як вимагається.
Нумід

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

61
+1, тут є найпростішим рішенням для генерації випадкових рядків заданої довжини (крім використання RandomStringUtils від Commons Lang).
Джонік

12
Подумайте про використання SecureRandomзамість Randomкласу. Якщо паролі генеруються на сервері, це може бути вразливим для атак часу.
foens

8
Я б додав також малі літери: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";і деякі інші дозволені символи.
ACV

1
Чому б не помістити static Random rnd = new Random();всередину методу?
Мікро

4
@MicroR Чи є вагомі причини створити Randomоб'єкт у кожному виклику методу? Я не думаю, що так.
кассіомолін

484

Якщо ви із задоволенням користуєтесь класами 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є застарілим.


22
Тільки що переглянули згаданий клас з Apache Commons Lang 3.3.1бібліотеки - і він використовує тільки java.util.Randomдля забезпечення випадкових послідовностей, так що виробляють небезпечну послідовність .
Юрій Наконечний

16
Переконайтеся, що ви використовуєте SecureRandom під час використання RandomStringUtils:public static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random)
Ruslans Uralovs

НЕ ВИКОРИСТОВУВАТИ. Це створює небезпечні послідовності !
Патрік Фавр

109

Ви можете використовувати для цього бібліотеку Apache: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

18
@kamil, я переглянув вихідний код для RandomStringUtils, і він використовує екземпляр java.util.Random, інстанціюваний без аргументів. Документація для java.util.Random говорить, що вона використовує поточний системний час, якщо насіння не передбачено. Це означає, що його не можна використовувати для ідентифікаторів / ключів сеансу, оскільки зловмисник може легко передбачити, що створюються ідентифікатори сеансу в будь-який момент часу.
Іншалла

36
@Inshallah: Ви (зайво) переоблаштовуєте систему. Хоча я погоджуюся, що він використовує час як насіння, зловмисник повинен мати доступ до наступних даних, щоб насправді отримати те, що він хоче. 1. Час до точної мілісекунди, коли код був засіяний 2. Кількість дзвінків, що відбулися до цього часу 3. Атомізм щодо власного дзвінка (так що кількість дзвінків до сих пір залишається однаковою) Якщо у вашого зловмисника є всі три ці речі, то у вас є набагато більша проблема ...
Ajeet Ganga

3
залежність градуса: compile 'commons-lang:commons-lang:2.6'
younes0

4
@Ajeet це неправда. Ви можете отримати стан генератора випадкових чисел з його виводу. Якщо зловмисник може генерувати кілька тисяч викликів для генерації випадкових токенів API, зловмисник зможе передбачити всі майбутні маркери API.
Томас Грейнджер

3
@AjeetGanga Нічого спільного з технікою. Якщо ви хочете створити ідентифікатори сеансу, вам потрібен генератор криптографічних псевдо випадкових випадків. Кожен ПНГ, що використовує час як насіння, передбачуваний і дуже небезпечний для даних, які мають бути непередбачуваними. Просто використовуй, SecureRandomі ти хороший.
Патрік Фавр

105

В одному рядку:

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

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


9
Але лише 6 листів :(
Моше Рева

2
Це мені теж допомогло, але лише шістнадцяткові цифри :(
noquery

@ Zippoxer, ти можеш констатувати, що кілька разів =)
daniel.bavrin

7
Приклад ОП показав наступний рядок як приклад, AEYGF7K0DM1Xякий не є шістнадцятковим. Мене хвилює, як часто люди помиляються буквено-цифровими з шістнадцятковими. Вони не одне і те ж.
hfontanez

6
Це набагато менше випадкових випадків, ніж слід задавати довжину рядка, оскільки Math.random()створює doubleвід 0 до 1, тому частина експонента здебільшого не використовується. Використовуйте random.nextLongдля випадкового longзамість цього некрасивого злому.
maaartinus

80

Це легко досягти без будь-яких зовнішніх бібліотек.

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

Спочатку вам потрібен криптографічний PRNG. Java має SecureRandomдля цього і, як правило, використовує найкраще джерело ентропії на машині (наприклад /dev/random). Детальніше читайте тут.

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

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

2. Необхідний простір можливих значень

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

порівняння розмірів жетонів

Для простих вимог може бути достатньо 8 або 12 байт, але з 16 байтами ви на "безпечній стороні".

І це в основному все. Останнє - подумати про кодування, щоб воно могло бути представлене як текст для друку (read, a String).

3. Бінарне кодування тексту

Типові кодування включають:

  • Base64кожен символ кодує 6 біт, створюючи 33% накладних витрат. На щастя, у Java 8+ та Android є стандартні реалізації . Зі старшою Java ви можете використовувати будь-яку з численних сторонніх бібліотек . Якщо ви хочете, щоб ваші маркери були безпечними для URL, використовуйте безпечну для URL версію RFC4648 (яка зазвичай підтримується більшістю реалізацій). Приклад кодування 16 байт за допомогою прокладки:XfJhfv3C0P6ag7y9VQxSbw==

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

  • Base16(шістнадцятковий) кожен символ кодує 4-бітовий, вимагаючи 2 символи на байт (тобто 16 байт створюють рядок довжиною 32). Тому шестигранний менше місць , ніж ефективні , 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 (безпечний для 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

Якщо ви хочете готовий до використання інструмент cli, ви можете використовувати кістки: https://github.com/patrickfav/dice

Приклад: Пов’язана проблема - захистіть свої поточні ідентифікатори

Якщо у вас вже є ідентифікатор, який ви можете використовувати (наприклад, синтетичний longу вашій сутності), але не хочете публікувати внутрішнє значення , ви можете використовувати цю бібліотеку, щоб зашифрувати її та придушити: https://github.com/patrickfav / id-маска

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
//example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);

3
Ця відповідь є повною і працює без додавання будь-якої залежності. Якщо ви хочете уникнути можливих мінусових знаків у виході, ви можете запобігти негативному BigIntegers, використовуючи параметр конструктора: BigInteger(1, token)замість BigInteger(token).
francoisr

Танки @francoisr для підказки, я відредагував приклад коду
Патрік Фавре

import java.security.SecureRandom;і import java.math.BigInteger;потрібні, щоб приклад працював, але він чудово працює!
черговий

Хороша відповідь, але / dev / random - це метод блокування, що є причиною того, що повільно до точки блокування, якщо ентропія занадто низька. Кращим і не блокуючим методом є / dev / urandom. Це можна налаштувати через <jre> /lib/security/java.security та встановити securerandom.source = файл: / dev /./ urandom
Muzammil

@Muzammil Дивіться tersesystems.com/blog/2015/12/17/… (також пов’язано у відповіді) - new SecureRandom()використовує/dev/urandom
Патрік Фавре

42

використання долара повинно бути простим як:

// "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

чи можливо використовувати SecureRandom з переміщенням?
iwein

34

Ось це на 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

4
Це створить небезпечні послідовності, тобто послідовності, про які можна легко здогадатися.
Юрій Наконечний

8
Все це подвійне зараження випадковим інт-поколінням порушено дизайном, повільним та нечитабельним. Використовуйте Random#nextIntабо nextLong. Перейдіть до SecureRandomнеобхідності.
maaartinus

31

Дивно, але ніхто не запропонував цього, але:

import java.util.UUID

UUID.randomUUID().toString();

Легко.

Перевага в цьому полягає в тому, що UUID є приємними і довгими, і гарантовано їх неможливо зіткнути.

У Вікіпедії це добре пояснено:

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

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

Перші 4 біти - це тип версії, а 2 - для варіанту, тому ви отримуєте 122 біти випадкових випадків. Тож якщо ви хочете, ви можете скоротити їх з кінця, щоб зменшити розмір UUID. Це не рекомендується, але у вас все ще є безліч випадкових випадків, достатньо для ваших записів на 500 000 легко.


39
Хтось це запропонував, приблизно за рік до вас.
erickson

31

Коротке та просте рішення, але використовує лише малі літери та цифри:

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

Розмір становить приблизно 12 цифр до основи 36, і не можна вдосконалити далі. Звичайно, ви можете додати кілька примірників.


11
Пам’ятайте лише про те, що перед результатом є 50% шансу на знак мінус! Тому загортання r.nextLong () у Math.abs () можна використовувати, якщо ви не хочете, щоб знак мінус: Long.toString(Math.abs(r.nextLong()), 36);
Ray Hulha

5
@RayHulha: Якщо ви не хочете, щоб знак мінус був, ви повинні відрізати його, тому що, на диво, Math.abs повертає негативне значення для Long.MIN_VALUE.
користувач невідомий

Цікаво, що Math.abs повертає негатив. Більше тут: bmaurer.blogspot.co.nz/2006/10/…
Філ

1
Проблема з absвирішується за допомогою бітового оператора для очищення найбільш значущого біта. Це буде працювати для всіх цінностей.
Radiodef

1
@Radiodef По суті те, що сказав @userunkown. Я гадаю, ви також могли це зробити << 1 >>> 1.
шмосель

15

Альтернативою Java 8 є:

static final Random random = new Random(); // Or SecureRandom
static final int startChar = (int) '!';
static final int endChar = (int) '~';

static String randomString(final int maxLength) {
  final int length = random.nextInt(maxLength + 1);
  return random.ints(length, startChar, endChar + 1)
        .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
        .toString();
}

3
Це здорово - але якщо ви хочете зберегти його строго буквено - цифрові (0-9, AZ, AZ) дивіться тут rationaljava.com/2015/06 / ...
Dan

12

Використання UUID не є безпечним, оскільки частини UUID зовсім не випадкові. Процедура @erickson дуже акуратна, але не створює рядки однакової довжини. Наступного фрагмента має бути достатньо:

/*
 * The random generator used by this class to create random keys.
 * In a holder class to defer initialization until needed.
 */
private static class RandomHolder {
    static final Random random = new SecureRandom();
    public static String randomKey(int length) {
        return String.format("%"+length+"s", new BigInteger(length*5/*base 32,2^5*/, random)
            .toString(32)).replace('\u0020', '0');
    }
}

Чому вибирати length*5. Припустимо простий випадок випадкової рядки довжиною 1, тому один випадковий символ. Щоб отримати випадковий символ, що містить усі цифри 0-9 та символи az, нам знадобиться випадкове число від 0 до 35, щоб отримати один з кожного символу. BigIntegerзабезпечує конструктор для генерації випадкового числа, рівномірно розподіленого по діапазону 0 to (2^numBits - 1). На жаль, 35 - це не число, яке можна отримати 2 ^ numBits - 1. Отже, у нас є два варіанти: або перейти з 2^5-1=31або 2^6-1=63. Якби ми вибрали, 2^6ми отримали б багато "непотрібних" / "довших" чисел. Тому 2^5кращий варіант, навіть якщо ми втратимо 4 символи (wz). Щоб тепер генерувати рядок певної довжини, ми можемо просто використовувати a2^(length*numBits)-1число. Остання проблема, якщо ми хочемо, що рядок з певною довжиною, випадковий може генерувати невелике число, тому довжина не виконується, тому нам доведеться прокладати рядок до потрібної довжини, попередньо перед нулями.


ви могли б пояснити краще 5?
Джуліан Суарес

11
public static String generateSessionKey(int length){
String alphabet = 
        new String("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); //9
int n = alphabet.length(); //10

String result = new String(); 
Random r = new Random(); //11

for (int i=0; i<length; i++) //12
    result = result + alphabet.charAt(r.nextInt(n)); //13

return result;
}

10
import java.util.Random;

public class passGen{
    //Verison 1.0
    private static final String dCase = "abcdefghijklmnopqrstuvwxyz";
    private static final String uCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String sChar = "!@#$%^&*";
    private static final String intChar = "0123456789";
    private static Random r = new Random();
    private static String pass = "";

    public static void main (String[] args) {
        System.out.println ("Generating pass...");
        while (pass.length () != 16){
            int rPick = r.nextInt(4);
            if (rPick == 0){
                int spot = r.nextInt(25);
                pass += dCase.charAt(spot);
            } else if (rPick == 1) {
                int spot = r.nextInt (25);
                pass += uCase.charAt(spot);
            } else if (rPick == 2) {
                int spot = r.nextInt (7);
                pass += sChar.charAt(spot);
            } else if (rPick == 3){
                int spot = r.nextInt (9);
                pass += intChar.charAt (spot);
            }
        }
        System.out.println ("Generated Pass: " + pass);
    }
}

Отже, це лише додавання пароля в рядок і ... так добре, перевірити це ... дуже просто. Я це написав


Я дозволив собі внести деякі незначні зміни. Чому ви додаєте + 0це часто? Чому ви розділяєте декларацію про спот та початкову ексксасію? Яка перевага індексів 1,2,3,4 замість 0,1,2,3? Найголовніше: ви взяли випадкову величину і порівняли з if-else 4 рази нове значення, яке завжди могло невідповідати, не отримуючи більше випадковості. Але сміливо відкатуйтесь.
користувач невідомий

8

Я знайшов це рішення, яке генерує випадкову шістнадцяткову кодовану рядок. Наданий блок тесту, здається, відповідає моєму випадку основного використання. Хоча, це дещо складніше, ніж деякі надані відповіді.

/**
 * Generate a random hex encoded string token of the specified length
 *  
 * @param length
 * @return random hex string
 */
public static synchronized String generateUniqueToken(Integer length){ 
    byte random[] = new byte[length];
    Random randomGenerator = new Random();
    StringBuffer buffer = new StringBuffer();

    randomGenerator.nextBytes(random);

    for (int j = 0; j < random.length; j++) {
        byte b1 = (byte) ((random[j] & 0xf0) >> 4);
        byte b2 = (byte) (random[j] & 0x0f);
        if (b1 < 10)
            buffer.append((char) ('0' + b1));
        else
            buffer.append((char) ('A' + (b1 - 10)));
        if (b2 < 10)
            buffer.append((char) ('0' + b2));
        else
            buffer.append((char) ('A' + (b2 - 10)));
    }
    return (buffer.toString());
}

@Test
public void testGenerateUniqueToken(){
    Set set = new HashSet();
    String token = null;
    int size = 16;

    /* Seems like we should be able to generate 500K tokens 
     * without a duplicate 
     */
    for (int i=0; i<500000; i++){
        token = Utility.generateUniqueToken(size);

        if (token.length() != size * 2){
            fail("Incorrect length");
        } else if (set.contains(token)) {
            fail("Duplicate token generated");
        } else{
            set.add(token);
        }
    }
}

Я не думаю, що справедливо відмовитись від дублікатів жетонів, що суто грунтується на ймовірності.
Том Віггерс

8
  1. Змініть рядкові символи відповідно до ваших вимог.

  2. Рядок непорушний. Тут StringBuilder.appendє більш ефективним, ніж з'єднання рядків.


public static String getRandomString(int length) {
       final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+";
       StringBuilder result = new StringBuilder();
       while(length > 0) {
           Random rand = new Random();
           result.append(characters.charAt(rand.nextInt(characters.length())));
           length--;
       }
       return result.toString();
    }

3
Це нічого не додає, десятки відповідей, наданих раніше, не охоплювали. І створювати новий Randomекземпляр у кожній ітерації циклу неефективно.
еріксон

7
import java.util.Date;
import java.util.Random;

public class RandomGenerator {

  private static Random random = new Random((new Date()).getTime());

    public static String generateRandomString(int length) {
      char[] values = {'a','b','c','d','e','f','g','h','i','j',
               'k','l','m','n','o','p','q','r','s','t',
               'u','v','w','x','y','z','0','1','2','3',
               '4','5','6','7','8','9'};

      String out = "";

      for (int i=0;i<length;i++) {
          int idx=random.nextInt(values.length);
          out += values[idx];
      }
      return out;
    }
}

7
import java.util.*;
import javax.swing.*;
public class alphanumeric{
    public static void main(String args[]){
        String nval,lenval;
        int n,len;

        nval=JOptionPane.showInputDialog("Enter number of codes you require : ");
        n=Integer.parseInt(nval);

        lenval=JOptionPane.showInputDialog("Enter code length you require : ");
        len=Integer.parseInt(lenval);

        find(n,len);

    }
    public static void find(int n,int length) {
        String str1="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb=new StringBuilder(length);
        Random r = new Random();

        System.out.println("\n\t Unique codes are \n\n");
        for(int i=0;i<n;i++){
            for(int j=0;j<length;j++){
                sb.append(str1.charAt(r.nextInt(str1.length())));
            }
            System.out.println("  "+sb.toString());
            sb.delete(0,length);
        }
    }
}

7

Не дуже подобається жодна з цих відповідей щодо "простого" рішення: S

Я б хотів просту;

public String randomString(int length, String characterSet) {
    return IntStream.range(0, length).map(i -> new SecureRandom().nextInt(characterSet.length())).mapToObj(randomInt -> characterSet.substring(randomInt, randomInt + 1)).collect(Collectors.joining());
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));//charachterSet can basically be anything
    }
}

або (трохи читабельніший старий спосіб)

public String randomString(int length, String characterSet) {
    StringBuilder sb = new StringBuilder(); //consider using StringBuffer if needed
    for (int i = 0; i < length; i++) {
        int randomInt = new SecureRandom().nextInt(characterSet.length());
        sb.append(characterSet.substring(randomInt, randomInt + 1));
    }
    return sb.toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); //charachterSet can basically be anything
    }
}

Але з іншого боку, ви також можете скористатися UUID, який має досить хорошу ентропію ( https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions ):

UUID.randomUUID().toString().replace("-", "")

Сподіваюся, що це допомагає.


6

Ви згадуєте "просте", але про всяк випадок, якщо хтось інший шукає щось, що відповідає більш суворим вимогам безпеки, ви можете поглянути на jpwgen . jpwgen моделюється після pwgen в Unix і дуже настроюється.


Спасибі, виправили. Так що найменше є джерело і посилання є дійсним. З іншого боку, це не схоже на те, що воно було оновлено через деякий час, хоча я бачу, що pwgen був оновлений зовсім недавно.
michaelok

4

Ви можете використовувати клас UUID з його повідомленням getLeastSignificantBits (), щоб отримати 64-бітові випадкові дані, а потім перетворити його в число 36 36 (тобто рядок, що складається з 0-9, AZ):

Long.toString(Math.abs( UUID.randomUUID().getLeastSignificantBits(), 36));

Це дає рядку довжиною до 13 символів. Ми використовуємо Math.abs (), щоб переконатися, що в ньому не прокрадається знак мінус.


2
Чому б у світі ви використовували UUID для отримання випадкових бітів? Чому б просто не використовувати random.nextLong()? Або навіть Double.doubleToLongBits(Math.random())?
erickson

4

Ви можете використовувати наступний код, якщо ваш обов'язковий пароль містить цифри, алфавітні спеціальні символи:

private static final String NUMBERS = "0123456789";
private static final String UPPER_ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWER_ALPHABETS = "abcdefghijklmnopqrstuvwxyz";
private static final String SPECIALCHARACTERS = "@#$%&*";
private static final int MINLENGTHOFPASSWORD = 8;

public static String getRandomPassword() {
    StringBuilder password = new StringBuilder();
    int j = 0;
    for (int i = 0; i < MINLENGTHOFPASSWORD; i++) {
        password.append(getRandomPasswordCharacters(j));
        j++;
        if (j == 3) {
            j = 0;
        }
    }
    return password.toString();
}

private static String getRandomPasswordCharacters(int pos) {
    Random randomNum = new Random();
    StringBuilder randomChar = new StringBuilder();
    switch (pos) {
        case 0:
            randomChar.append(NUMBERS.charAt(randomNum.nextInt(NUMBERS.length() - 1)));
            break;
        case 1:
            randomChar.append(UPPER_ALPHABETS.charAt(randomNum.nextInt(UPPER_ALPHABETS.length() - 1)));
            break;
        case 2:
            randomChar.append(SPECIALCHARACTERS.charAt(randomNum.nextInt(SPECIALCHARACTERS.length() - 1)));
            break;
        case 3:
            randomChar.append(LOWER_ALPHABETS.charAt(randomNum.nextInt(LOWER_ALPHABETS.length() - 1)));
            break;
    }
    return randomChar.toString();

}

4

Ось один код рядка від AbacusUtil

String.valueOf(CharStream.random('0', 'z').filter(c -> N.isLetterOrDigit(c)).limit(12).toArray())

Випадкове не означає, що воно повинно бути унікальним. щоб отримати унікальні рядки, використовуючи:

N.uuid() // e.g.: "e812e749-cf4c-4959-8ee1-57829a69a80f". length is 36.
N.guid() // e.g.: "0678ce04e18945559ba82ddeccaabfcd". length is 32 without '-'



3
public static String randomSeriesForThreeCharacter() {
    Random r = new Random();
    String value="";
    char random_Char ;
    for(int i=0; i<10;i++)
    { 
        random_Char = (char) (48 + r.nextInt(74));
        value=value+random_char;
    }
    return value;
}

2
Ця струнна конкатенація є надмірно неефективною. А шалений відступ робить ваш код майже нечитабельним. Це те саме , що ідея Джеймі, але погано виконана.
erickson

3

Я думаю, що це найменше рішення тут чи майже одне з найменших:

 public String generateRandomString(int length) {
    String randomString = "";

    final char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890".toCharArray();
    final SecureRandom random = new SecureRandom();
    for (int i = 0; i < length; i++) {
        randomString = randomString + chars[random.nextInt(chars.length)];
    }

    return randomString;
}

Код працює чудово. Якщо ви використовуєте цей метод, я рекомендую вам використовувати більше 10 символів. Зіткнення відбувається за 5 символів / 30362 ітерацій. Це зайняло 9 секунд.


3
public static String getRandomString(int length) {
        char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST".toCharArray();

        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            char c = chars[random.nextInt(chars.length)];
            sb.append(c);
        }
        String randomStr = sb.toString();

        return randomStr;
    }

1
Дійсно приємно! Але це повинно бути lengthзамість того , щоб chars.lengthпротягом циклу: for (int i = 0; i < length; i++)
установки для спалювання відходів

2
public static String getRandomString(int length) 
{
   String randomStr = UUID.randomUUID().toString();
   while(randomStr.length() < length) {
       randomStr += UUID.randomUUID().toString();
   }
   return randomStr.substring(0, length);
}

3
Це майже те саме, що відповідь Стіва Маклеода, дана два роки раніше.
еріксон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.