Come si crea una stringa casuale adatta per un ID di sessione in PostgreSQL?


101

Vorrei creare una stringa casuale da utilizzare nella verifica della sessione utilizzando PostgreSQL. So che posso ottenere un numero casuale con SELECT random(), quindi ho provato SELECT md5(random()), ma non funziona. Come posso fare questo?


Un'altra soluzione può essere trovata qui stackoverflow.com/a/13675441/398670
Craig Ringer

7
Ho modificato il titolo in modo che le risposte esistenti abbiano ancora perfettamente senso, e anche la risposta di Evan porta le cose un po 'più moderne. Non voglio bloccare questa vecchia domanda per una controversia sui contenuti, quindi apportiamo ulteriori modifiche per soddisfare tutte le risposte, per favore.
Tim Post

1
Bene, vediamo se @gersh può chiarire questa domanda perché c'è un legittimo disaccordo sulla sua intenzione originale. Se la sua intenzione originale è quella che presumo fosse, molte di queste risposte devono essere modificate, sottovalutate o ritirate. E forse dovrebbe essere sollevata una nuova domanda sulla generazione di stringhe a scopo di test (o simili) (dove random()ness non è necessario). Se non è quello che presumo, la mia risposta deve essere invece adattata alla domanda raffinata.
Evan Carroll

5
@EvanCarroll - gersh è stato visto l'ultima volta il 21 novembre 2015.
BSMP il

5
Per chiunque si avvicini a questa domanda nell'anno> 2017, considera la risposta di Evan stackoverflow.com/a/41608000/190234 poiché utilizza i metodi che non erano disponibili quando la questio n è stata inizialmente chiesta e ha risposto.
Marcin Raczkowski

Risposte:


84

Suggerirei questa semplice soluzione:

Questa è una funzione abbastanza semplice che restituisce una stringa casuale della lunghezza specificata:

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;

E l'utilizzo:

select random_string(15);

Output di esempio:

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)

6
Questa soluzione utilizza i valori alle due estremità dell'array chars - 0 ez - la metà delle volte del resto. Per una distribuzione più uniforme di personaggi, ho sostituito chars[1+random()*(array_length(chars, 1)-1)]conchars[ceil(61 * random())]
PreciousBodilyFluids

random()viene chiamato lengthvolte (come in molte altre soluzioni). Esiste un modo più efficiente per scegliere ogni volta tra 62 caratteri? Come funziona rispetto a md5()?
ma11hew28

Ho trovato un'altra soluzione che utilizza ORDER BY random(). Quale è più veloce?
ma11hew28

1
Vale la pena notare che random può usare erand48 che non è un CSPRNG, probabilmente stai meglio usando pgcrypto.
Yaur

2
Buona risposta tranne che non utilizza un generatore di numeri casuali sicuro e quindi non è così buono per gli ID di sessione. Vedi: stackoverflow.com/questions/9816114/…
sudo

240

Puoi correggere il tuo tentativo iniziale in questo modo:

SELECT md5(random()::text);

Molto più semplice di alcuni degli altri suggerimenti. :-)


16
Tieni presente che restituisce stringhe solo su "alfabeto cifre esadecimali" {0..9, a..f}. Potrebbe non essere sufficiente, dipende da cosa vuoi fare con loro.
Laryx Decidua

qual è la lunghezza della stringa restituita? C'è un modo per far sì che restituisca una stringa più lunga?
andrewrk

8
Quando rappresentata in esadecimale, la lunghezza di una stringa MD5 è sempre di 32 caratteri. Se volessi una stringa di lunghezza 64, potresti concatenare 2 stringhe MD5: SELECT concat(md5(random()::text), md5(random()::text)); e se volessi da qualche parte nel mezzo (50 caratteri per esempio), potresti prendere una sottostringa di questo: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell

2
Non è una buona soluzione per gli ID di sessione, non molta casualità. La risposta è anche di 6 anni. Dai un'occhiata a questo per un metodo completamente diverso che utilizzagen_random_uuid() : più veloce, più casualità, archiviato in modo più efficiente nel database.
Evan Carroll

@Evan se vuoi più 'casualità' senza un'estensione puoi SELECT md5(random()::text||random()::text);, oSELECT md5(random()::text||random()::text||random()::text);

31

Basandosi sulla soluzione di Marcin, potresti farlo per utilizzare un alfabeto arbitrario (in questo caso, tutti i 62 caratteri alfanumerici ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');

Lento, non così casuale o efficiente da memorizzare. Non è una buona soluzione per gli ID di sessione, non molta casualità. La risposta è anche di 6 anni. Check out this for a totally different method using gen_random_uuid(): più veloce, più casualità, più efficiente memorizzato nel database.
Evan Carroll

23

Puoi ottenere 128 bit casuali da un UUID. Questo è il metodo per portare a termine il lavoro nel moderno PostgreSQL.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

Potrebbe valere la pena leggere anche i documenti su UUID

Il tipo di dati uuid memorizza gli identificatori univoci universali (UUID) come definito da RFC 4122, ISO / IEC 9834-8: 2005 e standard correlati. (Alcuni sistemi si riferiscono a questo tipo di dati come identificatore univoco globale, o GUID, invece.) Questo identificatore è una quantità di 128 bit generata da un algoritmo scelto per rendere molto improbabile che lo stesso identificatore venga generato da qualcun altro nell'universo conosciuto utilizzando lo stesso algoritmo. Pertanto, per i sistemi distribuiti, questi identificatori forniscono una migliore garanzia di unicità rispetto ai generatori di sequenze, che sono univoci solo all'interno di un singolo database.

Quanto è raro una collisione con l'UUID o ipotizzabile? Supponendo che siano casuali,

Dovrebbero essere generati circa 100 trilioni di UUID versione 4 per avere una probabilità su un miliardo di un singolo duplicato ("collisione"). La possibilità di una collisione sale al 50% solo dopo che sono stati generati 261 UUID (2,3 x 10 ^ 18 o 2,3 quintilioni). Relazionando questi numeri ai database e considerando la questione se la probabilità di una collisione UUID versione 4 sia trascurabile, si consideri un file contenente 2,3 quintilioni di UUID versione 4, con una probabilità del 50% di contenere una collisione UUID. Sarebbe una dimensione di 36 exabyte, presupponendo nessun altro dato o overhead, migliaia di volte più grande dei database più grandi attualmente esistenti, che sono dell'ordine dei petabyte. Alla velocità di 1 miliardo di UUID generati al secondo, ci vorrebbero 73 anni per generare gli UUID per il file. Richiederebbe anche circa 3. 6 milioni di dischi rigidi da 10 terabyte o cartucce a nastro per archiviarlo, presupponendo che non vi siano backup o ridondanza. La lettura del file a una tipica velocità di trasferimento "da disco a buffer" di 1 gigabit al secondo richiederebbe oltre 3000 anni per un singolo processore. Poiché il tasso di errore di lettura irrecuperabile delle unità è di 1 bit per 1018 bit letti, nella migliore delle ipotesi, mentre il file conterrebbe circa 1020 bit, la sola lettura del file una volta dall'inizio alla fine risulterebbe, almeno, in circa 100 volte più errori. leggere gli UUID rispetto ai duplicati. Gli errori di archiviazione, rete, alimentazione e altri hardware e software sarebbero senza dubbio migliaia di volte più frequenti dei problemi di duplicazione degli UUID. la velocità di trasferimento di 1 gigabit al secondo richiederebbe oltre 3000 anni per un singolo processore. Poiché il tasso di errore di lettura irrecuperabile delle unità è di 1 bit per 1018 bit letti, nella migliore delle ipotesi, mentre il file conterrebbe circa 1020 bit, la sola lettura del file una volta dall'inizio alla fine risulterebbe, almeno, in circa 100 volte più errori. leggere gli UUID rispetto ai duplicati. Gli errori di archiviazione, rete, alimentazione e altri hardware e software sarebbero senza dubbio migliaia di volte più frequenti dei problemi di duplicazione degli UUID. la velocità di trasferimento di 1 gigabit al secondo richiederebbe oltre 3000 anni per un singolo processore. Poiché il tasso di errore di lettura irrecuperabile delle unità è di 1 bit per 1018 bit letti, nella migliore delle ipotesi, mentre il file conterrebbe circa 1020 bit, la sola lettura del file una volta dall'inizio alla fine risulterebbe, almeno, in circa 100 volte più errori. leggere gli UUID rispetto ai duplicati. Gli errori di archiviazione, rete, alimentazione e altri hardware e software sarebbero senza dubbio migliaia di volte più frequenti dei problemi di duplicazione degli UUID.

fonte: wikipedia

In sintesi,

  • L'UUID è standardizzato.
  • gen_random_uuid()è 128 bit di casuale memorizzato in 128 bit (2 ** 128 combinazioni). 0-rifiuti.
  • random() genera solo 52 bit di random in PostgreSQL (2 ** 52 combinazioni).
  • md5()memorizzato come UUID è di 128 bit, ma può essere casuale solo come il suo input ( 52 bit se si utilizzarandom() )
  • md5()memorizzato come testo è di 288 bit, ma può solo essere casuale come il suo input ( 52 bit se si utilizzarandom() ) - più del doppio della dimensione di un UUID e una frazione della casualità)
  • md5() come hash, può essere così ottimizzato da non fare molto in modo efficace.
  • L'UUID è altamente efficiente per l'archiviazione: PostgreSQL fornisce un tipo che è esattamente 128 bit. A differenza di texte varchar, ecc. Che memorizzano come un varlenache ha un sovraccarico per la lunghezza della stringa.
  • L'elegante UUID di PostgreSQL viene fornito con alcuni operatori, casting e funzionalità predefiniti.

3
Parzialmente errato: un UUID casuale generato correttamente ha solo 122 bit casuali poiché vengono utilizzati 4 bit per la versione e 2 bit per la variante: en.wikipedia.org/wiki/…
Olivier Grégoire

2
Se il sorgente non fa ciò che è scritto lì, allora non è un UUID e non dovrebbe essere chiamato come tale da PostgreSQL.
Olivier Grégoire

16

Recentemente stavo giocando con PostgreSQL e penso di aver trovato una soluzione un po 'migliore, usando solo metodi PostgreSQL incorporati - no pl / pgsql. L'unica limitazione è che attualmente genera solo stringhe UPCASE, numeri o stringhe minuscole.

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

Il secondo argomento del generate_seriesmetodo determina la lunghezza della stringa.


8
Mi piace, ma ho trovato quando l'ho usato un'istruzione UPDATE, tutte le righe erano impostate con la stessa password casuale invece di password univoche. Ho risolto questo problema aggiungendo l'ID della chiave primaria nella formula. Lo aggiungo al valore casuale e lo sottraggo di nuovo. La casualità non viene modificata, ma PostgreSQL viene indotto a ricalcolare i valori per ogni riga. Ecco un esempio, utilizzando il nome di una chiave primaria "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg

La soluzione, che @MarkStosberg ha presentato, ha funzionato come aveva detto, ma non come mi aspettavo; i dati prodotti non corrispondevano al modello preteso (solo lettere maiuscole o solo cifre). Ho risolto aritmeticamente modulando il risultato casuale: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo

4
No. Stai rispondendo "Come faccio a generare un ID di sessione casuale " non "Come faccio a generare una stringa casuale ". Hai cambiato il significato della domanda (e del titolo), in base a due parole nella descrizione. Stai rispondendo a una domanda diversa. e continua ad abusare del tuo potere di moderazione per cambiare il significato della domanda.
Marcin Raczkowski

13

Per favore usa string_agg!

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

Lo sto usando con MD5 per generare anche un UUID. Voglio solo un valore casuale con più bit di un random ()numero intero.


Suppongo che potrei semplicemente concatenare random()fino a ottenere il numero di bit che voglio. Oh bene.
Andrew Wolfe

11

Sebbene non sia attivo per impostazione predefinita, puoi attivare una delle estensioni principali:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Quindi la tua istruzione diventa una semplice chiamata a gen_salt () che genera una stringa casuale:

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

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

Il numero iniziale è un identificatore hash. Sono disponibili diversi algoritmi ciascuno con il proprio identificatore:

  • md5: $ 1 $
  • bf: $ 2a $ 06 $
  • des: nessun identificatore
  • xdes: _J9 ..

Ulteriori informazioni sulle estensioni:


MODIFICARE

Come indicato da Evan Carrol, a partire dalla v9.4 puoi usare gen_random_uuid()

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


I sali generati sembrano troppo sequenziali per essere davvero casuali, non è vero?
Le Droid

1
Ti riferisci al $1$? Questo è un identificatore di tipo hash (md5 == 1), il resto è il valore randomizzato.
Jefferey Cave

Sì, quella era la mia interpretazione errata, grazie per la precisione.
Le Droid

6

Non penso che tu stia cercando una stringa casuale di per sé. Ciò di cui hai bisogno per la verifica della sessione è una stringa garantita come univoca. Memorizzate le informazioni di verifica della sessione per il controllo? In tal caso è necessario che la stringa sia univoca tra le sessioni. Conosco due approcci piuttosto semplici:

  1. Usa una sequenza. Buono per l'uso su un singolo database.
  2. Usa un UUID. Universalmente unico, così buono anche su ambienti distribuiti.

Gli UUID sono garantiti come unici in virtù del loro algoritmo di generazione; effettivamente è estremamente improbabile che tu generi due numeri identici su qualsiasi macchina, in qualsiasi momento, mai (nota che questo è molto più forte che su stringhe casuali, che hanno una periodicità molto più piccola degli UUID).

È necessario caricare l'estensione uuid-ossp per utilizzare gli UUID. Una volta installato, chiama una delle funzioni uuid_generate_vXXX () disponibili nelle tue chiamate SELECT, INSERT o UPDATE. Il tipo uuid è un numero di 16 byte, ma ha anche una rappresentazione di stringa.


Questo sembra un consiglio potenzialmente pericoloso. Quando si tratta di chiavi di sessione, si desidera unicità e casualità sufficientemente casuali dal punto di vista crittografico da precludere qualsiasi ragionevole possibilità di indovinarlo. Gli algoritmi utilizzati dagli UUID garantiscono l'unicità mediante meccanismi non casuali (principalmente), che rappresentano una minaccia per la sicurezza.
jmar777

6
@ jmar777 L'intero scopo degli UUID è che sono difficili da indovinare e altamente casuali. Ad eccezione della versione v1 hanno una periodicità molto alta; v4 è completamente casuale a 128 bit. Vengono utilizzati in ogni transazione bancaria online che fai. Se sono abbastanza buoni per questo, sono abbastanza buoni per praticamente qualsiasi altra cosa.
Patrick

1
Ebbene, cosa ne sai. Non mi rendevo conto che era stato risolto nella versione 4 . Grazie per avermi corretto!
jmar777

@Patrick Small nit, gli UUID V4 sono 122 bit casuali, non 128.;)
Jesse

5

Il parametro INTEGER definisce la lunghezza della stringa. Garantito per coprire tutti i 62 caratteri alfanici con la stessa probabilità (a differenza di altre soluzioni che fluttuano su Internet).

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;

Lento, non così casuale o efficiente da memorizzare. Non è una buona soluzione per gli ID di sessione, non molta casualità. La risposta è anche di 6 anni. Check out this for a totally different method using gen_random_uuid(): più veloce, più casualità, più efficiente memorizzato nel database.
Evan Carroll

3
@EvanCarroll: in tutta franchezza, è gen_random_uuid()apparso nella versione 9.4, per quanto ne so, che è stata rilasciata il 18/12/2014, più di un anno dopo la risposta che hai votato in modo negativo. Ulteriore nitpick: la risposta è di soli 3 anni e mezzo :-) Ma hai ragione, ora che abbiamo gen_random_uuid(), questo è ciò che dovrebbe essere usato. Quindi voterò positivamente la tua risposta.
Laryx Decidua

5

@Kavius ​​consiglia di utilizzare pgcrypto, ma invece di gen_saltche cosa gen_random_bytes? E che ne dici sha512invece di md5?

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

Documenti:

F.25.5. Funzioni di dati casuali

gen_random_bytes (count integer) restituisce bytea

Restituisce il conteggio dei byte casuali crittograficamente forti. È possibile estrarre al massimo 1024 byte alla volta. Questo per evitare di svuotare il pool del generatore di casualità.



2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')

Lo modifico per rimuovere la barra e il segno più che a volte appaiono nel risultato e anche per generare un risultato in maiuscolo selezionare upper (replace (replace (substring (encode (decode (md5 (random () :: text)), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.