Jak utworzyć losowy ciąg, który będzie pasował do identyfikatora sesji w PostgreSQL?

101

Chciałbym utworzyć losowy ciąg znaków do weryfikacji sesji przy użyciu PostgreSQL. Wiem, że mogę uzyskać losową liczbę SELECT random(), więc próbowałem SELECT md5(random()), ale to nie działa. W jaki sposób mogę to zrobić?

gersh
źródło
Inne rozwiązanie można znaleźć tutaj stackoverflow.com/a/13675441/398670
Craig Ringer
7
Zredagowałem tytuł, aby istniejące odpowiedzi nadal miały sens, a odpowiedź Evana, która wnosi coś nieco nowocześniejszego, pasuje również. Nie chcę blokować tego odwiecznego pytania w sporze dotyczącym treści - dokonajmy więc dodatkowych zmian uwzględniających wszystkie odpowiedzi.
Tim Post
1
Fajnie, zobaczmy, czy @gersh może wyjaśnić to pytanie, ponieważ istnieje uzasadniony spór co do jego pierwotnego zamiaru. Jeśli jego pierwotny zamiar jest taki, jak zakładam, wiele z tych odpowiedzi należy skorygować, zanegować lub wycofać. I być może powinno zostać postawione nowe pytanie dotyczące generowania ciągów do celów testowych (lub tym podobnych) (gdzie random()nie jest to konieczne). Jeśli nie jest to, co przypuszczam, moja odpowiedź musi być skierowana na bardziej wyrafinowane pytanie.
Evan Carroll,
5
@EvanCarroll - gersh był ostatnio widziany 21 listopada 2015 r.
BSMP
5
Każdy, kto ma ochotę odpowiedzieć na to pytanie w roku> 2017, powinien rozważyć odpowiedź Evana stackoverflow.com/a/41608000/190234, ponieważ wykorzystuje ona metody, które nie były dostępne, gdy pierwotnie zadano pytanie i udzielono na nie odpowiedzi.
Marcin Raczkowski

Odpowiedzi:

84

Proponuję takie proste rozwiązanie:

To dość prosta funkcja, która zwraca losowy ciąg o podanej długości:

Create or replace function random_string(length integer) returns text as
$$
declare
  chars text[] := '{0,1,2,3,4,5,6,7,8,9,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,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}';
  result text := '';
  i integer := 0;
begin
  if length < 0 then
    raise exception 'Given length cannot be less than 0';
  end if;
  for i in 1..length loop
    result := result || chars[1+random()*(array_length(chars, 1)-1)];
  end loop;
  return result;
end;
$$ language plpgsql;

I użycie:

select random_string(15);

Przykładowe dane wyjściowe:

select random_string(15) from generate_series(1,15);

  random_string
-----------------
 5emZKMYUB9C2vT6
 3i4JfnKraWduR0J
 R5xEfIZEllNynJR
 tMAxfql0iMWMIxM
 aPSYd7pDLcyibl2
 3fPDd54P5llb84Z
 VeywDb53oQfn9GZ
 BJGaXtfaIkN4NV8
 w1mvxzX33NTiBby
 knI1Opt4QDonHCJ
 P9KC5IBcLE0owBQ
 vvEEwc4qfV4VJLg
 ckpwwuG8YbMYQJi
 rFf6TchXTO3XsLs
 axdQvaLBitm6SDP
(15 rows)
Szymon Lipiński
źródło
6
W tym rozwiązaniu wartości na każdym końcu tablicy znaków - 0 iz - są używane o połowę rzadziej niż pozostałe. Aby uzyskać bardziej równomierne rozmieszczenie postaci, zastąpiłem chars[1+random()*(array_length(chars, 1)-1)]jechars[ceil(61 * random())]
PreciousBodilyFluids
random()jest wywoływany lengthrazy (jak w wielu innych rozwiązaniach). Czy za każdym razem istnieje bardziej efektywny sposób wyboru spośród 62 znaków? Jak to działa w porównaniu z md5()?
ma11hew28,
Znalazłem inne rozwiązanie, które wykorzystuje ORDER BY random(). Który jest szybszy?
ma11hew28,
1
Warto zauważyć, że random może używać erand48, który nie jest CSPRNG, prawdopodobnie lepiej będzie po prostu użyć pgcrypto.
Yaur,
2
Dobra odpowiedź, z tym wyjątkiem, że nie używa bezpiecznego generatora liczb losowych i dlatego nie jest tak dobra dla identyfikatorów sesji. Zobacz: stackoverflow.com/questions/9816114/…
sudo
240

Możesz naprawić swoją początkową próbę w następujący sposób:

SELECT md5(random()::text);

Znacznie prostsze niż niektóre inne sugestie. :-)

Peter Eisentraut
źródło
16
Zwróć uwagę, że zwraca to tylko ciągi znaków nad „alfabetem cyfr szesnastkowych” {0..9, a..f}. Może nie wystarczyć - zależy od tego, co chcesz z nimi zrobić.
Laryx Decidua,
jaka jest długość zwracanego ciągu? Czy istnieje sposób, aby zwracał dłuższy ciąg?
andrewrk
8
W przypadku przedstawienia szesnastkowego długość łańcucha MD5 wynosi zawsze 32 znaki. Jeśli chcesz ciąg o długości 64, możesz połączyć 2 struny MD5: SELECT concat(md5(random()::text), md5(random()::text)); A jeśli chcesz gdzieś pośrodku (na przykład 50 znaków), możesz wziąć podciąg z tego: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell
2
Niezbyt dobre rozwiązanie dla identyfikatorów sesji, mało przypadkowości. Odpowiedź ma również 6 lat. Sprawdź to, aby uzyskać zupełnie inną metodę, używającgen_random_uuid() : szybszej, większej losowości, wydajniejszego przechowywania w bazie danych.
Evan Carroll,
@Evan, jeśli chcesz mieć więcej `` losowości '' bez rozszerzenia, możesz SELECT md5(random()::text||random()::text);lubSELECT md5(random()::text||random()::text||random()::text);
31

Opierając się na rozwiązaniu Marcina, możesz to zrobić, aby użyć dowolnego alfabetu (w tym przypadku wszystkich 62 znaków alfanumerycznych ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');
żałować
źródło
Wolne, nie tak przypadkowe lub tak wydajne w przechowywaniu. Niezbyt dobre rozwiązanie dla identyfikatorów sesji, mało przypadkowości. Odpowiedź ma również 6 lat. Check out this for a totally different method using gen_random_uuid(): szybciej, więcej losowości, efektywniej przechowywane w bazie danych.
Evan Carroll,
23

Możesz uzyskać 128 bitów losowych z UUID. To jest metoda na wykonanie zadania w nowoczesnym PostgreSQL.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

           gen_random_uuid            
--------------------------------------
 202ed325-b8b1-477f-8494-02475973a28f

Może być wart czytania docs na zbyt UUID

Typ danych uuid przechowuje unikalne identyfikatory uniwersalne (UUID), zgodnie z definicją zawartą w RFC 4122, ISO / IEC 9834-8: 2005 i powiązanych normach. (Niektóre systemy odnoszą się do tego typu danych jako do globalnego unikalnego identyfikatora lub zamiast tego GUID). Identyfikator ten to 128-bitowa liczba generowana przez algorytm wybrany w taki sposób, aby bardzo mało prawdopodobne było, że ten sam identyfikator zostanie wygenerowany przez kogokolwiek innego. w znanym wszechświecie przy użyciu tego samego algorytmu. Dlatego w przypadku systemów rozproszonych identyfikatory te zapewniają lepszą gwarancję niepowtarzalności niż generatory sekwencji, które są unikalne tylko w ramach jednej bazy danych.

Jak rzadkie jest zderzenie z UUID lub możliwe do przewidzenia? Zakładając, że są przypadkowe,

Aby mieć 1 na miliard szans na pojedynczy duplikat („kolizja”), trzeba by wygenerować około 100 bilionów identyfikatorów UUID w wersji 4. Szansa na jedną kolizję wzrasta do 50% dopiero po wygenerowaniu 261 UUID (2,3 x 10 ^ 18 lub 2,3 biliarda). Odnosząc te liczby do baz danych i biorąc pod uwagę kwestię, czy prawdopodobieństwo kolizji UUID wersji 4 jest znikome, rozważ plik zawierający 2,3 biliarda UUID wersji 4, z 50% szansą, że będzie zawierał jedną kolizję UUID. Miałoby 36 eksabajtów, zakładając brak innych danych lub narzutów, tysiące razy większe niż największe obecnie istniejące bazy danych, które są rzędu petabajtów. Przy szybkości 1 miliarda identyfikatorów UUID generowanych na sekundę wygenerowanie identyfikatorów UUID dla pliku zajęłoby 73 lata. Wymagałoby to również około 3. 6 milionów 10-terabajtowych dysków twardych lub kaset z taśmami do ich przechowywania, przy założeniu braku kopii zapasowych lub nadmiarowości. Odczytanie pliku z typową szybkością transferu „dysk do bufora” wynoszącą 1 gigabit na sekundę wymagałoby ponad 3000 lat dla pojedynczego procesora. Ponieważ współczynnik nieodwracalnych błędów odczytu dysków wynosi w najlepszym przypadku 1 bit na 1018 bitów odczytu, podczas gdy plik zawierałby około 1020 bitów, zwykłe odczytanie pliku raz od końca do końca spowodowałoby co najmniej około 100 razy więcej błędów. czytaj UUID niż duplikaty. Błędy pamięci masowej, sieci, zasilania i inne błędy sprzętu i oprogramowania byłyby niewątpliwie tysiące razy częstsze niż problemy z powielaniem identyfikatorów UUID. szybkość transferu 1 gigabita na sekundę wymagałaby ponad 3000 lat dla pojedynczego procesora. Ponieważ współczynnik nieodwracalnych błędów odczytu dysków wynosi w najlepszym przypadku 1 bit na 1018 bitów odczytu, podczas gdy plik zawierałby około 1020 bitów, zwykłe odczytanie pliku raz od końca do końca spowodowałoby co najmniej około 100 razy więcej błędów. czytaj UUID niż duplikaty. Błędy pamięci masowej, sieci, zasilania i inne błędy sprzętu i oprogramowania byłyby niewątpliwie tysiące razy częstsze niż problemy z powielaniem identyfikatorów UUID. szybkość transferu 1 gigabita na sekundę wymagałaby ponad 3000 lat dla pojedynczego procesora. Ponieważ współczynnik nieodwracalnych błędów odczytu dysków wynosi w najlepszym przypadku 1 bit na 1018 bitów odczytu, podczas gdy plik zawierałby około 1020 bitów, zwykłe odczytanie pliku raz od końca do końca spowodowałoby co najmniej około 100 razy więcej błędów. czytaj UUID niż duplikaty. Błędy pamięci masowej, sieci, zasilania i inne błędy sprzętu i oprogramowania byłyby niewątpliwie tysiące razy częstsze niż problemy z powielaniem identyfikatorów UUID.

źródło: wikipedia

W podsumowaniu,

  • UUID jest znormalizowany.
  • gen_random_uuid()to 128 bitów losowych przechowywanych w 128 bitach (2 ** 128 kombinacji). 0-odpadów.
  • random() generuje tylko 52 bity losowe w PostgreSQL (2 ** 52 kombinacje).
  • md5()przechowywany jako UUID ma 128 bitów, ale może być tylko tak losowy, jak jego wejście ( 52 bity, jeśli jest używanyrandom() )
  • md5()przechowywany jako tekst ma 288 bitów, ale może być tylko tak losowy, jak jego wejście ( 52 bity, jeśli jest używanyrandom() ) - ponad dwukrotnie większy niż UUID i ułamek losowości)
  • md5() jako skrót może być tak zoptymalizowany, że nie robi wiele efektywnie.
  • Identyfikator UUID jest bardzo wydajny w przypadku przechowywania: PostgreSQL zapewnia typ o dokładnie 128 bitach. W przeciwieństwie do texti varcharitp., Które są przechowywane jako wartość, varlenaktóra ma narzut na długość ciągu.
  • Sprytny UUID PostgreSQL zawiera kilka domyślnych operatorów, rzutowania i funkcji.
Evan Carroll
źródło
3
Częściowo niepoprawny: prawidłowo wygenerowany losowy identyfikator UUID ma tylko 122 losowe bity, ponieważ 4 bity są używane w wersji i 2 bity w wariancie: en.wikipedia.org/wiki/…
Olivier Grégoire
2
Jeśli źródło nie robi tego, co tam jest napisane, nie jest to UUID i nie powinno być tak nazywane przez PostgreSQL.
Olivier Grégoire
16

Ostatnio bawiłem się PostgreSQL i myślę, że znalazłem trochę lepsze rozwiązanie, używając tylko wbudowanych metod PostgreSQL - bez pl / pgsql. Jedynym ograniczeniem jest to, że obecnie generuje tylko łańcuchy UPCASE, liczby lub łańcuchy z małych liter.

template1=> SELECT array_to_string(ARRAY(SELECT chr((65 + round(random() * 25)) :: integer) FROM generate_series(1,12)), '');
 array_to_string
-----------------
 TFBEGODDVTDM

template1=> SELECT array_to_string(ARRAY(SELECT chr((48 + round(random() * 9)) :: integer) FROM generate_series(1,12)), '');
 array_to_string
-----------------
 868778103681

Drugi argument generate_seriesmetody określa długość ciągu.

Marcin Raczkowski
źródło
8
Podoba mi się to, ale stwierdziłem, że kiedy użyłem go w instrukcji UPDATE, wszystkie wiersze były ustawione na to samo losowe hasło zamiast unikalnych haseł. Rozwiązałem to, dodając identyfikator klucza podstawowego do formuły. Dodaję go do wartości losowej i ponownie odejmuję. Losowość nie jest zmieniana, ale PostgreSQL jest zmuszony do ponownego obliczenia wartości dla każdego wiersza. Oto przykład z użyciem nazwy klucza podstawowego „my_id”: array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg,
Rozwiązanie, które przedstawił @MarkStosberg, działało tak, jak powiedział, ale nie tak, jak się spodziewałem; wygenerowane dane nie pasowały do ​​udawanego wzorca (tylko wielkość liter lub tylko cyfry). Poprawiłem przez arytmetyczne modulowanie losowego wyniku: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo
4
Nie. Odpowiadasz na pytanie „Jak wygenerować losowy identyfikator sesji ”, a nie „Jak wygenerować losowy ciąg ”. Zmieniłeś znaczenie quesitonu (i tytułu) na podstawie dwóch słów w opisie. Odpowiadasz na inne pytanie. i nadużywaj swojej władzy umiaru, aby zmienić znaczenie pytania.
Marcin Raczkowski
13

Proszę użyć string_agg!

SELECT string_agg (substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', ceil (random() * 62)::integer, 1), '')
FROM   generate_series(1, 45);

Używam tego z MD5 do generowania UUID również. Chcę tylko losowej wartości z większą liczbą bitów niż random ()liczba całkowita.

Andrew Wolfe
źródło
Przypuszczam, że mógłbym po prostu random()połączyć, dopóki nie uzyskam żądanej liczby bitów. No cóż.
Andrew Wolfe
11

Chociaż domyślnie nie jest aktywne, możesz aktywować jedno z podstawowych rozszerzeń:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Wtedy twoja instrukcja staje się prostym wywołaniem gen_salt (), które generuje losowy ciąg:

select gen_salt('md5') from generate_series(1,4);

 gen_salt
-----------
$1$M.QRlF4U
$1$cv7bNJDM
$1$av34779p
$1$ZQkrCXHD

Wiodąca liczba to identyfikator skrótu. Dostępnych jest kilka algorytmów, każdy z własnym identyfikatorem:

  • md5: 1 $
  • bf: 2 $ a 06 $
  • des: brak identyfikatora
  • xdes: _J9 ..

Więcej informacji o rozszerzeniach:


EDYTOWAĆ

Jak wskazał Evan Carrol, od wersji 9.4 można używać gen_random_uuid()

http://www.postgresql.org/docs/9.4/static/pgcrypto.html

Jaskinia Jefferey
źródło
Wygenerowane sole wydają się zbyt sekwencyjne, aby były naprawdę przypadkowe, prawda?
Le Droid
1
Czy odnosisz się do $1$? To jest identyfikator typu skrótu (md5 == 1), reszta to wartość losowa.
Jaskinia Jefferey
Tak, to była moja błędna interpretacja, dzięki za precyzję.
Le Droid
6

Nie sądzę, że szukasz przypadkowego ciągu jako takiego. To, czego potrzebujesz do weryfikacji sesji, to ciąg, który gwarantuje unikalność. Czy przechowujesz informacje dotyczące weryfikacji sesji na potrzeby audytu? W takim przypadku ciąg musi być unikalny między sesjami. Znam dwa, raczej proste podejścia:

  1. Użyj sekwencji. Dobry do użytku w pojedynczej bazie danych.
  2. Użyj UUID. Uniwersalne rozwiązanie, które sprawdza się również w środowiskach rozproszonych.

UUID są zagwarantowane być unikalny ze względu na ich algorytm do wytwarzania; skutecznie jest to niezwykle mało prawdopodobne, że wygenerujesz dwie identyczne liczby na dowolnej maszynie, kiedykolwiek, kiedykolwiek (zwróć uwagę, że jest to znacznie silniejsze niż w przypadku ciągów losowych, które mają znacznie mniejszą częstotliwość niż UUID).

Aby używać identyfikatorów UUID, musisz załadować rozszerzenie uuid-ossp. Po zainstalowaniu wywołaj dowolną z dostępnych funkcji uuid_generate_vXXX () w wywołaniach SELECT, INSERT lub UPDATE. Typ uuid to 16-bajtowa liczba, ale ma również reprezentację w postaci ciągu.

Patrick
źródło
Wydaje się, że to potencjalnie niebezpieczna rada. Jeśli chodzi o klucze sesji, potrzebujesz unikalności i losowości, która jest kryptograficznie losowa na tyle, aby wykluczyć jakąkolwiek rozsądną szansę na ich odgadnięcie. Algorytmy używane przez UUID gwarantują unikalność poprzez mechanizmy nielosowe (w większości), co stanowi zagrożenie dla bezpieczeństwa.
jmar777
6
@ jmar777 Celem UUID jest to, że są trudne do odgadnięcia i wysoce przypadkowe. Poza wersją v1 mają bardzo wysoką okresowość; v4 jest w pełni 128-bitowym losowym rozwiązaniem. Są używane w każdej transakcji bankowej online. Jeśli są do tego wystarczająco dobrzy, są wystarczająco dobrzy, by zrobić wszystko inne.
Patrick,
1
Cóż, co wiesz. Nie zdawałem sobie sprawy, że został rozwiązany w wersji 4 . Dzięki za poprawienie mnie!
jmar777
@Patrick Small nit, UUID V4 to 122 bity losowe, a nie 128.;)
Jesse
5

Parametr INTEGER określa długość łańcucha. Gwarantowane pokrycie wszystkich 62 znaków alfanumerycznych z jednakowym prawdopodobieństwem (w przeciwieństwie do niektórych innych rozwiązań krążących po Internecie).

CREATE OR REPLACE FUNCTION random_string(INTEGER)
RETURNS TEXT AS
$BODY$
SELECT array_to_string(
    ARRAY (
        SELECT substring(
            '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
            FROM (ceil(random()*62))::int FOR 1
        )
        FROM generate_series(1, $1)
    ), 
    ''
)
$BODY$
LANGUAGE sql VOLATILE;
Laryx Decidua
źródło
Wolne, nie tak przypadkowe lub tak wydajne w przechowywaniu. Niezbyt dobre rozwiązanie dla identyfikatorów sesji, mało przypadkowości. Odpowiedź ma również 6 lat. Check out this for a totally different method using gen_random_uuid(): szybciej, więcej losowości, efektywniej przechowywane w bazie danych.
Evan Carroll,
3
@EvanCarroll: z całą uczciwością gen_random_uuid()pojawił się w wersji 9.4, o ile wiem, która została wydana 2014-12-18, ponad rok po udzielonej przez Ciebie odpowiedzi. Dodatkowy czubek: odpowiedź ma dopiero 3 i pół roku :-) Ale masz rację, skoro już mamy gen_random_uuid(), to powinno być używane. Dlatego głosuję za twoją odpowiedzią.
Laryx Decidua
5

@Kavius ​​zaleca używanie pgcrypto, ale zamiast tego gen_salt, co z tym gen_random_bytes? A może sha512zamiast tego md5?

create extension if not exists pgcrypto;
select digest(gen_random_bytes(1024), 'sha512');

Dokumenty:

F.25.5. Funkcje danych losowych

gen_random_bytes (count integer) zwraca bajty

Zwraca liczbę losowych bajtów silnych pod względem kryptograficznym. Jednocześnie można wyodrębnić maksymalnie 1024 bajty. Ma to na celu uniknięcie opróżnienia puli generatora losowości.

Jared Beck
źródło
4

select * from md5(to_char(random(), '0.9999999999999999'));

user516487
źródło
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
user457226
źródło
Poprawiam to, aby usunąć ukośnik i znak plus, który czasami pojawia się w wyniku, a także aby wygenerować wynik z wielkiej litery, wybierz górny (replace (replace (podciąg (encode (decode (md5 (random ()) :: text)), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt