Jak wygenerować losowy ciąg alfanumeryczny?

1741

Szukałem prostego algorytmu Java do generowania pseudolosowego ciągu alfanumerycznego. W mojej sytuacji byłby on używany jako unikalny identyfikator sesji / klucza, który „prawdopodobnie” byłby unikalny na przestrzeni 500K+pokolenia (moje potrzeby tak naprawdę nie wymagają niczego bardziej zaawansowanego).

Idealnie byłbym w stanie określić długość w zależności od moich potrzeb dotyczących wyjątkowości. Na przykład wygenerowany ciąg o długości 12 może wyglądać mniej więcej tak "AEYGF7K0DM1X".

Todd
źródło
150
Strzeż się paradoksu urodzinowego .
pablosaraiva
58
Nawet biorąc pod uwagę paradoks urodzinowy, jeśli użyjesz 12 znaków alfanumerycznych (łącznie 62), nadal potrzebujesz znacznie ponad 34 miliardów ciągów, aby osiągnąć paradoks. Paradoks urodzinowy i tak nie gwarantuje kolizji, tylko mówi, że ma ponad 50% szansy.
NullUserException
4
@NullUserException 50% szansa na sukces (na próbę) jest cholernie wysoka: nawet przy 10 próbach wskaźnik sukcesu wynosi 0,999. Mając to na uwadze i fakt, że możesz wypróbować DUŻO w ciągu 24 godzin, nie potrzebujesz 34 miliardów ciągów, aby być pewnym, że zgadniesz przynajmniej jeden z nich. To jest powód, dla którego niektóre tokeny sesji powinny być naprawdę, bardzo długie.
Pijusn
16
Te 3 kody jednoliniowe są bardzo przydatne, jak sądzę ..Long.toHexString(Double.doubleToLongBits(Math.random())); UUID.randomUUID().toString(); RandomStringUtils.randomAlphanumeric(12);
Manindar,
18
@Pijusn Wiem, że to jest stare, ale ... „50% szansa” w paradoksie urodzin NIE jest „na próbę”, to „50% szansy, że z (w tym przypadku) 34 miliardów ciągów istnieje co najmniej jedna para duplikatów ". Potrzebujesz bazy danych 1.6 sept illion - 1.6e21 - wpisów w bazie danych, aby szansa na próbę wynosiła 50%.
Tin Wizard

Odpowiedzi:

1541

Algorytm

Aby wygenerować losowy ciąg, połącz znaki losowe z zestawu dopuszczalnych symboli, aż ciąg osiągnie pożądaną długość.

Realizacja

Oto dość prosty i bardzo elastyczny kod do generowania losowych identyfikatorów. Przeczytaj poniższe informacje, aby uzyskać ważne uwagi dotyczące aplikacji.

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

}

Przykłady użycia

Utwórz niepewny generator dla 8-znakowych identyfikatorów:

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

Utwórz bezpieczny generator identyfikatorów sesji:

RandomString session = new RandomString();

Utwórz generator z łatwymi do odczytania kodami do drukowania. Ciągi są dłuższe niż pełne ciągi alfanumeryczne, aby zrekompensować użycie mniejszej liczby symboli:

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

Użyj jako identyfikatorów sesji

Generowanie identyfikatorów sesji, które prawdopodobnie będą unikalne, nie jest wystarczająco dobre lub możesz po prostu użyć prostego licznika. Atakujący przejmują sesje, gdy używane są przewidywalne identyfikatory.

Istnieje napięcie między długością a bezpieczeństwem. Krótsze identyfikatory są łatwiejsze do odgadnięcia, ponieważ jest mniej możliwości. Ale dłuższe identyfikatory zużywają więcej pamięci i przepustowości. Większy zestaw symboli pomaga, ale może powodować problemy z kodowaniem, jeśli identyfikatory są zawarte w adresach URL lub ponownie wprowadzone ręcznie.

Podstawowym źródłem losowości lub entropii dla identyfikatorów sesji powinien być generator liczb losowych przeznaczony do kryptografii. Jednak inicjowanie tych generatorów może czasami być drogie obliczeniowo lub powolne, więc należy dołożyć starań, aby ponownie z nich korzystać, jeśli to możliwe.

Użyj jako identyfikatorów obiektów

Nie każda aplikacja wymaga bezpieczeństwa. Losowe przypisywanie może być skutecznym sposobem dla wielu podmiotów do generowania identyfikatorów we wspólnej przestrzeni bez jakiejkolwiek koordynacji lub partycjonowania. Koordynacja może być powolna, szczególnie w środowisku klastrowym lub rozproszonym, a podział przestrzeni powoduje problemy, gdy jednostki kończą się udziałami, które są zbyt małe lub zbyt duże.

Identyfikatory generowane bez podejmowania działań, które czynią je nieprzewidywalnymi, powinny być chronione innymi środkami, jeśli osoba atakująca może je wyświetlić i manipulować, jak to ma miejsce w większości aplikacji internetowych. Powinien istnieć oddzielny system autoryzacji, który chroni obiekty, których identyfikator może odgadnąć osoba atakująca bez pozwolenia na dostęp.

Należy również zachować ostrożność, aby używać identyfikatorów wystarczająco długich, aby kolizje były mało prawdopodobne, biorąc pod uwagę przewidywaną całkowitą liczbę identyfikatorów. Nazywa się to „paradoksem urodzinowym”. Prawdopodobieństwo kolizji, p , wynosi w przybliżeniu n 2 / (2q x ), gdzie n jest liczbą faktycznie wygenerowanych identyfikatorów, q jest liczbą różnych symboli w alfabecie, a x jest długością identyfikatorów. Powinna to być bardzo mała liczba, na przykład 2–50 lub mniejsza.

Wypracowanie tego pokazuje, że szansa kolizji między 500k 15-znakowymi identyfikatorami wynosi około 2–52 , co jest prawdopodobnie mniej prawdopodobne niż niewykryte błędy promieni kosmicznych itp.

Porównanie z UUID

Zgodnie z ich specyfikacją identyfikatory UUID nie są zaprojektowane jako nieprzewidywalne i nie powinny być używane jako identyfikatory sesji.

Identyfikatory UUID w standardowym formacie zajmują dużo miejsca: 36 znaków dla zaledwie 122 bitów entropii. (Nie wszystkie bity „losowego” UUID są wybierane losowo.) Losowo wybrany ciąg alfanumeryczny zawiera więcej entropii w zaledwie 21 znakach.

UUID nie są elastyczne; mają znormalizowaną strukturę i układ. To jest ich główna cnota, a także ich główna słabość. Podczas współpracy z podmiotem zewnętrznym pomocna może być standaryzacja oferowana przez UUID. Do użytku wyłącznie wewnętrznego mogą być nieefektywne.

erickson
źródło
6
Jeśli potrzebujesz spacji, możesz przyczepić się .replaceAll("\\d", " ");do końca return new BigInteger(130, random).toString(32);linii, aby wykonać zamianę wyrażeń regularnych. Zastępuje wszystkie cyfry spacjami. Działa świetnie dla mnie: używam tego jako substytutu frontowego Lorem Ipsum
weisjohn
4
@weisjohn To dobry pomysł. Możesz zrobić coś podobnego z drugą metodą, usuwając cyfry symbolsi używając spacji; możesz kontrolować średnią długość „słowa”, zmieniając liczbę spacji w symbolach (więcej wystąpień w przypadku krótszych słów). Aby naprawdę przesadzić z fałszywym rozwiązaniem tekstowym, możesz użyć łańcucha Markowa!
erickson,
4
Te identyfikatory są losowo wybierane z przestrzeni o określonym rozmiarze. Mogą mieć 1 postać. Jeśli chcesz mieć stałą długość, możesz użyć drugiego rozwiązania z SecureRandominstancją przypisaną do randomzmiennej.
erickson,
15
Dlaczego .toString (32) zamiast .toString (36)?
ejain
17
@ejain, ponieważ 32 = 2 ^ 5; każdy znak będzie reprezentował dokładnie 5 bitów, a 130 bitów można równomiernie podzielić na znaki.
erickson
817

Java zapewnia sposób wykonywania tego bezpośrednio. Jeśli nie chcesz myślników, łatwo je rozebrać. Po prostu użyjuuid.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;
    }
}

Wynik:

uuid = 2d7428a6-b58c-4008-8575-f05549f16316
Steve McLeod
źródło
33
Uwaga: to rozwiązanie generuje tylko losowy ciąg znaków szesnastkowych. Co może być w porządku w niektórych przypadkach.
Dave
5
Klasa UUID jest przydatna. Nie są jednak tak zwarte, jak identyfikatory wygenerowane przez moje odpowiedzi. Może to być problem na przykład w adresach URL. Zależy od twoich potrzeb.
erickson,
6
@Ruggs - Celem są ciągi alfanumeryczne. W jaki sposób poszerza to wyjście do dowolnych możliwych bajtów?
erickson,
72
Według RFC4122 używanie UUID jako tokenów jest złym pomysłem: Nie zakładaj, że UUID są trudne do odgadnięcia; nie powinny być wykorzystywane na przykład jako funkcje bezpieczeństwa (identyfikatory, których zwykłe posiadanie zapewnia dostęp). Przewidywalne źródło liczb losowych pogorszy sytuację. ietf.org/rfc/rfc4122.txt
Somatik
34
UUID.randomUUID().toString().replaceAll("-", "");tworzy ciąg alfanumeryczny, zgodnie z żądaniem.
Numid
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();
}
maks
źródło
61
+1, najprostsze rozwiązanie do generowania losowego ciągu o określonej długości (oprócz użycia RandomStringUtils z Commons Lang).
Jonik,
12
Rozważ użycie SecureRandomzamiast Randomklasy. Jeśli hasła są generowane na serwerze, może być podatny na ataki czasowe.
foens
8
Dodałbym także małe litery: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";i kilka innych dozwolonych znaków.
ACV
1
Dlaczego nie wprowadzić static Random rnd = new Random();metody?
Micro
4
@MicroR Czy istnieje dobry powód, aby utworzyć Randomobiekt w każdej wywołaniu metody? Nie wydaje mi się
kasiomolina
484

Jeśli chcesz używać klas Apache, możesz użyć org.apache.commons.text.RandomStringGenerator(commons-text).

Przykład:

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

Ponieważ commons-lang 3.6 RandomStringUtilsjest przestarzały.

numéro6
źródło
22
Przejrzałem właśnie wspomnianej klasy z Apache Commons Lang 3.3.1biblioteki - i to przy użyciu tylko java.util.Randomzapewnić losowe sekwencje, więc produkuje niebezpieczne sekwencje .
Yuriy Nakonechnyy
16
Upewnij się, że używasz SecureRandom podczas korzystania z 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
NIE UŻYWAJ. To tworzy niebezpieczne sekwencje !
Patrick Favre,
109

Możesz do tego użyć biblioteki Apache: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();
manish_s
źródło
18
@kamil, spojrzałem na kod źródłowy RandomStringUtils i używa on instancji java.util.Random utworzonej bez argumentów. Dokumentacja java.util.Random mówi, że używa aktualnego czasu systemowego, jeśli nie podano źródła. Oznacza to, że nie można go użyć do identyfikatorów / kluczy sesji, ponieważ osoba atakująca może łatwo przewidzieć, jakie są wygenerowane identyfikatory sesji w danym momencie.
Inshallah
36
@Inshallah: Jesteś (niepotrzebnie) nadinżynieryjny w systemie. Chociaż zgadzam się, że wykorzystuje on czas jako ziarno, atakujący musi mieć dostęp do następujących danych, aby faktycznie uzyskać to, czego chce 1. Czas do dokładnej milisekundy, kiedy kod został zaszczepiony 2. Liczba wywołań, które do tej pory miały miejsce 3. Atomowość dla jego własnego połączenia (tak, że dotychczasowa liczba połączeń jest taka sama) Jeśli twój atakujący ma wszystkie trzy z tych rzeczy, masz znacznie większy problem pod ręką ...
Ajeet Ganga
3
stopień zależności: compile 'commons-lang:commons-lang:2.6'
you00
4
@Ajeet to nie jest prawda. Możesz wyprowadzić stan generatora liczb losowych z jego wyjścia. Jeśli osoba atakująca może wygenerować kilka tysięcy wywołań w celu wygenerowania losowych tokenów API, będzie w stanie przewidzieć wszystkie przyszłe tokeny API.
Thomas Grainger,
3
@AjeetGanga Nie ma nic wspólnego z inżynierią. Jeśli chcesz utworzyć identyfikatory sesji, potrzebujesz kryptograficznego pseudolosowego generatora. Każda próba wykorzystująca czas jako ziarno jest przewidywalna i bardzo niepewna dla danych, które powinny być nieprzewidywalne. Po prostu użyj SecureRandomi jesteś dobry.
Patrick Favre,
105

W jednej linii:

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

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

anonimowych
źródło
9
Ale tylko 6 liter :(
Moshe Revah,
2
Pomogło mi również, ale tylko cyfry szesnastkowe :(
noquery
@Zippoxer, można by to przyznać kilka razy =)
daniel.bavrin
7
Przykład PO pokazał następujący ciąg znaków jako przykład, AEYGF7K0DM1Xktóry nie jest szesnastkowy. Martwi mnie, jak często ludzie mylą alfanumeryczny z szesnastkowym. To nie to samo.
hfontanez
6
Jest to o wiele mniej losowe, niż powinno się podawać długość łańcucha, ponieważ Math.random()daje wartość doublemiędzy 0 a 1, więc część wykładnicza jest w większości nieużywana. Użyj random.nextLonglosowego longzamiast tego brzydkiego hacka.
maaartinus
80

Można to łatwo osiągnąć bez żadnych zewnętrznych bibliotek.

1. Kryptograficzne pseudolosowe generowanie danych

Najpierw potrzebujesz kryptograficznego PRNG. Java ma SecureRandomdo tego i zwykle używa najlepszego źródła entropii na komputerze (np /dev/random.). Przeczytaj więcej tutaj.

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

Uwaga: SecureRandom jest najwolniejszym, ale najbezpieczniejszym sposobem w Javie generowania losowych bajtów. Nie polecam jednak rozważania wydajności tutaj, ponieważ zwykle nie ma ona rzeczywistego wpływu na twoją aplikację, chyba że musisz wygenerować miliony tokenów na sekundę.

2. Wymagana przestrzeń możliwych wartości

Następnie musisz zdecydować, „jak wyjątkowy” musi być Twój token. Jedynym celem rozważania entropii jest upewnienie się, że system jest w stanie oprzeć się atakom z użyciem siły brutalnej: przestrzeń możliwych wartości musi być tak duża, aby każdy atakujący mógł wypróbować jedynie znikomą część wartości w nie-śmiesznym czasie 1 . Unikalne identyfikatory, takie jak losowy, UUIDmają 122-bitową entropię (tj. 2 ^ 122 = 5,3x10 ^ 36) - szansa na kolizję to „* (...), aby istniała jedna szansa na miliard duplikacji, wersja 103 trylionów Należy wygenerować 4 UUID 2 ”. Wybieramy 128 bitów, ponieważ mieści się dokładnie w 16 bajtach i jest postrzegane jako wysoce wystarczająceza wyjątkowość w zasadzie dla wszystkich, ale najbardziej ekstremalnych przypadków użycia i nie trzeba myśleć o duplikatach. Oto prosta tabela porównawcza entropii zawierająca prostą analizę problemu urodzinowego .

porównanie rozmiarów żetonów

Dla prostych wymagań 8 lub 12 bajtów może wystarczyć, ale przy 16 bajtach jesteś po „bezpiecznej stronie”.

I w zasadzie to wszystko. Ostatnią rzeczą jest zastanowienie się nad kodowaniem, aby mogło być reprezentowane jako tekst do wydruku (czytaj, a String).

3. Kodowanie binarne na tekstowe

Typowe kodowania obejmują:

  • Base64każda postać koduje 6 bitów, co daje narzut 33%. Na szczęście istnieją standardowe implementacje w Javie 8+ i Androidzie . W starszej Javie możesz używać dowolnej z wielu bibliotek stron trzecich . Jeśli chcesz, aby tokeny być url bezpiecznego użytkowania url-safe wersja RFC4648 (które zwykle są obsługiwane przez większość wdrożeń). Przykład kodowania 16 bajtów z dopełnieniem:XfJhfv3C0P6ag7y9VQxSbw==

  • Base32każda postać koduje 5 bitów, co powoduje narzut 40%. To wykorzysta A-Zi 2-7sprawi, że będzie wystarczająco wydajna pod względem miejsca, a jednocześnie nie będzie rozróżniać wielkości liter alfanumerycznie. W JDK nie ma standardowej implementacji . Przykład kodowania 16 bajtów bez wypełniania:WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16(hex) każdy znak koduje 4 bity wymagające 2 znaków na bajt (tj. 16 bajtów tworzy ciąg o długości 32). Dlatego hex jest mniej efektywny przestrzennie niż, Base32ale jest bezpieczny w użyciu w większości przypadków (url), ponieważ używa tylko 0-9i Ado F. Przykład kodujący 16 bajtów: 4fa3dd0f57cb3bf331441ed285b27735. Zobacz dyskusję SO na temat konwersji na hex tutaj.

Dodatkowe kodowania, takie jak Base85 i egzotyczny Base122 istnieją z lepszą / gorszą wydajnością przestrzeni. Możesz stworzyć swoje własne kodowanie (tak jak większość odpowiedzi w tym wątku), ale odradzam go, jeśli nie masz bardzo specyficznych wymagań. Zobacz więcej schematów kodowania w artykule w Wikipedii.

4. Podsumowanie i przykład

  • Posługiwać się SecureRandom
  • Użyj co najmniej 16 bajtów (2 ^ 128) możliwych wartości
  • Koduj zgodnie ze swoimi wymaganiami (zwykle hexlub base32jeśli potrzebujesz, aby był alfanumeryczny)

Nie rób

  • ... użyj kodowania domowego naparu: łatwiejsze w utrzymaniu i czytelne dla innych, jeśli zobaczą, jakiego standardowego kodowania używasz zamiast dziwnego dla pętli tworzących znaki na raz.
  • ... użyj UUID: nie ma gwarancji losowości; marnujesz 6 bitów entropii i masz pełną reprezentację ciągu

Przykład: 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

Przykład: Generator tokenów Base64 (bezpieczny 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

Przykład: Java CLI Tool

Jeśli chcesz mieć gotowe narzędzie cli, możesz użyć kości: https://github.com/patrickfav/dice

Przykład: Powiązany problem - Chroń swoje obecne identyfikatory

Jeśli masz już identyfikator, którego możesz użyć (np. Syntetyczny longw swojej jednostce), ale nie chcesz publikować wartości wewnętrznej , możesz użyć tej biblioteki do jej zaszyfrowania i zaciemnienia: https://github.com/patrickfav / id-mask

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
//example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);
patrickf
źródło
3
Ta odpowiedź jest kompletna i działa bez dodawania jakiejkolwiek zależności. Jeśli chcesz uniknąć możliwych znaków minus na wyjściu, możesz zapobiec ujemnym BigIntegers za pomocą parametru konstruktora: BigInteger(1, token)zamiast BigInteger(token).
francoisr
Tanks @francoisr dla podpowiedzi, edytowałem przykład kodu
Patrick Favre
import java.security.SecureRandom;i import java.math.BigInteger;są potrzebne, aby przykład działał, ale działa świetnie!
anothermh
Dobra odpowiedź, ale / dev / random to metoda blokowania, która powoduje powolność do momentu blokowania, jeśli entropia jest zbyt niska. Lepszą i nieblokującą metodą jest / dev / urandom. Można to skonfigurować za pomocą <jre> /lib/security/java.security i ustawić securerandom.source = file: / dev /./ urandom
Muzammil
@Muzammil Zobacz tersesystems.com/blog/2015/12/17/… (również link w odpowiedzi) - new SecureRandom()zastosowania/dev/urandom
Patrick Favre
42

korzystanie z Dolara powinno być proste, ponieważ:

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

wyprowadza coś takiego:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7
DFA
źródło
czy można używać SecureRandom z funkcją losowego?
iwein
34

Oto w Javie:

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

Oto przykładowy przebieg:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy
Apokalipsa
źródło
4
Spowoduje to powstanie niepewnych sekwencji, tj. Sekwencji, które można łatwo odgadnąć.
Yuriy Nakonechnyy
8
Całe to podwójnie zainfekowane losowe generowanie int jest podzielone przez projekt, wolne i nieczytelne. Użyj Random#nextIntlub nextLong. Przełącz w SecureRandomrazie potrzeby.
maaartinus
31

Zaskakujące, że nikt tutaj tego nie zasugerował, ale:

import java.util.UUID

UUID.randomUUID().toString();

Łatwy.

Zaletą tego jest to, że identyfikatory UUID są ładne i długie, a ich zderzenie jest prawie niemożliwe.

Wikipedia ma dobre wytłumaczenie:

„... dopiero po wygenerowaniu 1 miliarda UUID co sekundę przez następne 100 lat prawdopodobieństwo utworzenia tylko jednego duplikatu wyniesie około 50%”.

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

Pierwsze 4 bity to typ wersji i 2 dla wariantu, więc otrzymujesz 122 bity losowo. Więc jeśli chcesz , możesz obciąć od końca, aby zmniejszyć rozmiar UUID. Nie jest to zalecane, ale wciąż masz dużo losowości, co wystarczy, aby twoje 500k rekordów było łatwe.

Michael Allen
źródło
39
Ktoś to zasugerował około rok przed tobą.
erickson,
31

Krótkie i łatwe rozwiązanie, ale wykorzystuje tylko małe litery i cyfry:

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

Rozmiar wynosi około 12 cyfr do podstawy 36 i nie można go w ten sposób dalej poprawiać. Oczywiście możesz dołączyć wiele instancji.

użytkowników nieznanych
źródło
11
Pamiętaj tylko, że istnieje 50% szans na znak minus przed wynikiem! Tak więc można użyć zawijania r.nextLong () w Math.abs (), jeśli nie chcesz znaku minus: Long.toString(Math.abs(r.nextLong()), 36);
Ray Hulha
5
@RayHulha: Jeśli nie chcesz znaku minus, powinieneś go odciąć, ponieważ, co zaskakujące, Math.abs zwraca wartość ujemną dla Long.MIN_VALUE.
użytkownik nieznany
Interesujące Math.abs zwraca negatywne. Więcej tutaj: bmaurer.blogspot.co.nz/2006/10/...
Phil
1
Problem z absrozwiązuje się za pomocą operatora bitowego, aby usunąć najbardziej znaczący bit. Będzie to działać dla wszystkich wartości.
Radiodef,
1
@Radiodef Tak właśnie powiedział @userunkown. Przypuszczam, że ty też możesz to zrobić << 1 >>> 1.
shmosel
15

Alternatywą w Javie 8 jest:

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();
}
Howard Lovatt
źródło
3
To świetnie - ale jeśli chcesz zachować alfanumeryczne (0-9, az, AZ), zobacz tutaj rationaljava.com/2015/06/…
Dan.
12

Korzystanie z UUID jest niepewne, ponieważ części UUID wcale nie są losowe. Procedura @erickson jest bardzo schludna, ale nie tworzy ciągów o tej samej długości. Poniższy fragment kodu powinien wystarczyć:

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

Dlaczego wybór length*5. Załóżmy prosty przypadek losowego ciągu o długości 1, a więc jednego losowego znaku. Aby uzyskać losowy znak zawierający wszystkie cyfry 0–9 i znaki az, potrzebowalibyśmy losowej liczby od 0 do 35, aby uzyskać jeden z każdego znaku. BigIntegerzapewnia konstruktor do generowania liczby losowej, równomiernie rozmieszczonej w całym zakresie 0 to (2^numBits - 1). Niestety 35 nie jest liczbą, która może być odebrana przez 2 ^ numBits - 1. Mamy więc dwie opcje: albo iść z 2^5-1=31albo 2^6-1=63. Gdybyśmy zdecydowali 2^6, otrzymalibyśmy dużo „niepotrzebnych” / „dłuższych” liczb. Dlatego 2^5jest lepszą opcją, nawet jeśli stracimy 4 znaki (wz). Aby teraz wygenerować ciąg o określonej długości, możemy po prostu użyć2^(length*numBits)-1numer. Ostatni problem, jeśli chcemy ciąg o określonej długości, losowo może wygenerować małą liczbę, więc długość nie zostanie spełniona, więc musimy uzupełnić ciąg do wymaganej długości przed zerowaniem.

Kristian Kraljic
źródło
czy mógłbyś lepiej wyjaśnić 5?
Julian Suarez,
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;
}
Rina
źródło
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);
    }
}

Więc to po prostu dodaje hasło do łańcucha i ... tak działa dobrze, sprawdź to ... bardzo proste. napisałem to

cmpbah
źródło
Pozwoliłem sobie na drobne modyfikacje. Dlaczego + 0często to dodajesz ? Dlaczego dzielisz deklarację miejsca i inicjalizacji? Jaka jest zaleta indeksów 1,2,3,4 zamiast 0,1,2,3? Co najważniejsze: wziąłeś losową wartość i porównałeś ją z czterokrotnie nową wartością, która zawsze może być niezgodna, bez większej losowości. Ale możesz wycofać się.
użytkownik nieznany
8

Znalazłem to rozwiązanie, które generuje losowy ciąg zakodowany w postaci szesnastkowej. Dostarczony test jednostkowy wydaje się być zgodny z moim podstawowym zastosowaniem. Chociaż jest to nieco bardziej skomplikowane niż niektóre inne udzielone odpowiedzi.

/**
 * 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);
        }
    }
}
Todd
źródło
Nie sądzę, że niesprawiedliwe jest odrzucanie zduplikowanych tokenów, które opiera się wyłącznie na prawdopodobieństwie.
Thom Wiggers
8
  1. Zmień ciąg znaków zgodnie z własnymi wymaganiami.

  2. Sznurek jest niezmienny. Tutaj StringBuilder.appendjest bardziej wydajny niż ciąg konkatenacji.


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();
    }
rozbrykany
źródło
3
Nie dodaje to niczego, czego dziesiątki podanych wcześniej odpowiedzi nie obejmowały. Tworzenie nowego Randomwystąpienia w każdej iteracji pętli jest nieefektywne.
erickson
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;
    }
}
Jameskittu
źródło
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);
        }
    }
}
Suganya
źródło
7

Nie podoba mi się żadna z tych odpowiedzi dotyczących „prostego” rozwiązania: S.

Wybrałbym prostą;), czystą Javę, jedną linijkę (entropia opiera się na losowej długości łańcucha i podanym zestawie znaków):

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
    }
}

lub (nieco bardziej czytelny stary sposób)

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
    }
}

Ale z drugiej strony możesz także skorzystać z UUID, który ma całkiem dobrą entropię ( https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions ):

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

Mam nadzieję, że to pomaga.

Patrik Bego
źródło
6

Wspominasz o „prostym”, ale na wypadek, gdyby ktokolwiek szukał czegoś, co spełnia bardziej rygorystyczne wymagania bezpieczeństwa, możesz rzucić okiem na jpwgen . jpwgen jest wzorowany na pwgen w Uniksie i jest bardzo konfigurowalny.

michaelok
źródło
Dzięki, naprawiłem to. Więc przynajmniej jest źródło, a link jest prawidłowy. Z drugiej strony, nie wygląda na to, by był od jakiegoś czasu aktualizowany, choć widzę, że pwgen został ostatnio zaktualizowany dość niedawno.
michaelok
4

Możesz użyć klasy UUID z komunikatem getLeastSinentantBits (), aby uzyskać 64-bitowe dane losowe, a następnie przekonwertować je na liczbę podstawową 36 (tj. Ciąg składający się z 0-9, AZ):

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

Daje to ciąg znaków o długości do 13 znaków. Używamy Math.abs (), aby upewnić się, że nie wkrada się znak minus.

neuhaus
źródło
2
Dlaczego, do diabła, miałbyś używać UUID, aby uzyskać losowe bity? Dlaczego nie po prostu użyć random.nextLong()? A może nawet Double.doubleToLongBits(Math.random())?
erickson
4

Możesz użyć następującego kodu, jeśli obowiązkowe hasło zawiera cyfry alfabetyczne:

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

}
Prasobh.K
źródło
4

Oto kod jednowierszowy autorstwa AbacusUtil

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

Random nie oznacza, że ​​musi być wyjątkowy. aby uzyskać unikatowe ciągi znaków, używając:

N.uuid() // e.g.: "e812e749-cf4c-4959-8ee1-57829a69a80f". length is 36.
N.guid() // e.g.: "0678ce04e18945559ba82ddeccaabfcd". length is 32 without '-'
user_3380739
źródło
3

Oto rozwiązanie Scala:

(for (i <- 0 until rnd.nextInt(64)) yield { 
  ('0' + rnd.nextInt(64)).asInstanceOf[Char] 
}) mkString("")
Ugo Matrangolo
źródło
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;
}
duggu
źródło
2
Ta konkatenacja ciągów jest niepotrzebnie nieefektywna. A szalone wcięcia sprawiają, że kod jest prawie nieczytelny. Jest to to samo, co pomysł Jamiego, ale źle wykonane.
erickson
3

Myślę, że jest to najmniejsze rozwiązanie tutaj lub prawie jedno z najmniejszych:

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

Kod działa dobrze. Jeśli używasz tej metody, zalecam użycie więcej niż 10 znaków. Zderzenie ma miejsce przy 5 znakach / 30362 iteracjach. Zajęło to 9 sekund.

FileInputStream
źródło
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;
    }
Prasad Parab
źródło
1
Bardzo miłe! Ale powinno być lengthzamiast chars.lengthw pętli for: for (int i = 0; i < length; i++)
Spalacz
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);
}
Vin Ferothas
źródło
3
Jest to prawie to samo, co odpowiedź Steve'a McLeoda udzielona dwa lata wcześniej.
erickson