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"
.
java
string
random
alphanumeric
Todd
źródło
źródło
Long.toHexString(Double.doubleToLongBits(Math.random()));
UUID.randomUUID().toString();
RandomStringUtils.randomAlphanumeric(12);
Odpowiedzi:
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.
Przykłady użycia
Utwórz niepewny generator dla 8-znakowych identyfikatorów:
Utwórz bezpieczny generator identyfikatorów sesji:
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:
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.
źródło
.replaceAll("\\d", " ");
do końcareturn 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 Ipsumsymbols
i 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!SecureRandom
instancją przypisaną dorandom
zmiennej.Java zapewnia sposób wykonywania tego bezpośrednio. Jeśli nie chcesz myślników, łatwo je rozebrać. Po prostu użyj
uuid.replace("-", "")
Wynik:
źródło
UUID.randomUUID().toString().replaceAll("-", "");
tworzy ciąg alfanumeryczny, zgodnie z żądaniem.źródło
SecureRandom
zamiastRandom
klasy. Jeśli hasła są generowane na serwerze, może być podatny na ataki czasowe.AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
i kilka innych dozwolonych znaków.static Random rnd = new Random();
metody?Random
obiekt w każdej wywołaniu metody? Nie wydaje mi sięJeśli chcesz używać klas Apache, możesz użyć
org.apache.commons.text.RandomStringGenerator
(commons-text).Przykład:
Ponieważ commons-lang 3.6
RandomStringUtils
jest przestarzały.źródło
Apache Commons Lang 3.3.1
biblioteki - i to przy użyciu tylkojava.util.Random
zapewnić losowe sekwencje, więc produkuje niebezpieczne sekwencje .public static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random)
Możesz do tego użyć biblioteki Apache: RandomStringUtils
źródło
compile 'commons-lang:commons-lang:2.6'
SecureRandom
i jesteś dobry.W jednej linii:
źródło
AEYGF7K0DM1X
który nie jest szesnastkowy. Martwi mnie, jak często ludzie mylą alfanumeryczny z szesnastkowym. To nie to samo.Math.random()
daje wartośćdouble
między 0 a 1, więc część wykładnicza jest w większości nieużywana. Użyjrandom.nextLong
losowegolong
zamiast tego brzydkiego hacka.Można to łatwo osiągnąć bez żadnych zewnętrznych bibliotek.
1. Kryptograficzne pseudolosowe generowanie danych
Najpierw potrzebujesz kryptograficznego PRNG. Java ma
SecureRandom
do tego i zwykle używa najlepszego źródła entropii na komputerze (np/dev/random
.). Przeczytaj więcej tutaj.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,
UUID
mają 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 .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ą:
Base64
każ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==
Base32
każda postać koduje 5 bitów, co powoduje narzut 40%. To wykorzystaA-Z
i2-7
sprawi, ż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ż,Base32
ale jest bezpieczny w użyciu w większości przypadków (url), ponieważ używa tylko0-9
iA
doF
. 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
SecureRandom
hex
lubbase32
jeśli potrzebujesz, aby był alfanumeryczny)Nie rób
Przykład: Hex Token Generator
Przykład: Generator tokenów Base64 (bezpieczny URL)
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
long
w 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źródło
BigInteger
s za pomocą parametru konstruktora:BigInteger(1, token)
zamiastBigInteger(token)
.import java.security.SecureRandom;
iimport java.math.BigInteger;
są potrzebne, aby przykład działał, ale działa świetnie!new SecureRandom()
zastosowania/dev/urandom
korzystanie z Dolara powinno być proste, ponieważ:
wyprowadza coś takiego:
źródło
Oto w Javie:
Oto przykładowy przebieg:
źródło
Random#nextInt
lubnextLong
. Przełącz wSecureRandom
razie potrzeby.Zaskakujące, że nikt tutaj tego nie zasugerował, ale:
Ł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:
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.
źródło
Krótkie i łatwe rozwiązanie, ale wykorzystuje tylko małe litery i cyfry:
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.
źródło
Long.toString(Math.abs(r.nextLong()), 36);
abs
rozwiązuje się za pomocą operatora bitowego, aby usunąć najbardziej znaczący bit. Będzie to działać dla wszystkich wartości.<< 1 >>> 1
.Alternatywą w Javie 8 jest:
źródło
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ć:
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.BigInteger
zapewnia konstruktor do generowania liczby losowej, równomiernie rozmieszczonej w całym zakresie0 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ść z2^5-1=31
albo2^6-1=63
. Gdybyśmy zdecydowali2^6
, otrzymalibyśmy dużo „niepotrzebnych” / „dłuższych” liczb. Dlatego2^5
jest 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)-1
numer. 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.źródło
źródło
Więc to po prostu dodaje hasło do łańcucha i ... tak działa dobrze, sprawdź to ... bardzo proste. napisałem to
źródło
+ 0
czę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ę.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.
źródło
Zmień ciąg znaków zgodnie z własnymi wymaganiami.
Sznurek jest niezmienny. Tutaj
StringBuilder.append
jest bardziej wydajny niż ciąg konkatenacji.źródło
Random
wystąpienia w każdej iteracji pętli jest nieefektywne.źródło
źródło
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):
lub (nieco bardziej czytelny stary sposób)
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 ):
Mam nadzieję, że to pomaga.
źródło
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.
źródło
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):
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.
źródło
random.nextLong()
? A może nawetDouble.doubleToLongBits(Math.random())
?Możesz użyć następującego kodu, jeśli obowiązkowe hasło zawiera cyfry alfabetyczne:
źródło
Oto kod jednowierszowy autorstwa AbacusUtil
Random nie oznacza, że musi być wyjątkowy. aby uzyskać unikatowe ciągi znaków, używając:
źródło
Oto rozwiązanie Scala:
źródło
przy użyciu biblioteki Apache można to zrobić w jednym wierszu
tutaj jest dokument http://commons.apache.org/lang/api-2.3/org/apache/commons/lang/RandomStringUtils.html
źródło
źródło
Myślę, że jest to najmniejsze rozwiązanie tutaj lub prawie jedno z najmniejszych:
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.
źródło
źródło
length
zamiastchars.length
w pętli for:for (int i = 0; i < length; i++)
źródło