Comment créer une chaîne aléatoire adaptée à un ID de session dans PostgreSQL?

101

Je voudrais créer une chaîne aléatoire à utiliser dans la vérification de session à l'aide de PostgreSQL. Je sais que je peux obtenir un nombre aléatoire avec SELECT random(), alors j'ai essayé SELECT md5(random()), mais cela ne fonctionne pas. Comment puis-je faire ceci?

gersh
la source
Une autre solution peut être trouvée ici stackoverflow.com/a/13675441/398670
Craig Ringer
7
J'ai édité le titre pour que les réponses existantes soient toujours parfaitement logiques, et la réponse d'Evan apportant des choses un peu plus modernes convient également. Je ne veux pas verrouiller cette question séculaire pour un litige de contenu - alors apportons des modifications supplémentaires pour toutes les réponses, s'il vous plaît.
Tim Post
1
Cool, voyons si @gersh peut clarifier cette question car il y a un désaccord légitime quant à son intention initiale. Si son intention initiale est ce que je suppose, beaucoup de ces réponses doivent être ajustées, rejetées ou retirées. Et, peut-être qu'une nouvelle question sur la génération de chaînes à des fins de test (ou autre) devrait être soulevée (là où cela random()n'est pas nécessaire). Si ce n'est pas ce que je suppose, ma réponse doit plutôt être adaptée à la question raffinée.
Evan Carroll
5
@EvanCarroll - gersh a été vu pour la dernière fois le 21 novembre 2015.
BSMP
5
Pour quiconque vient à cette question en année> 2017, considérez la réponse d'Evan stackoverflow.com/a/41608000/190234 car il utilise les méthodes qui n'étaient pas disponibles lorsque la question a été posée et répondue à l'origine.
Marcin Raczkowski

Réponses:

84

Je suggérerais cette solution simple:

C'est une fonction assez simple qui renvoie une chaîne aléatoire de la longueur donnée:

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;

Et l'utilisation:

select random_string(15);

Exemple de sortie:

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
la source
6
Cette solution utilise les valeurs à chaque extrémité du tableau chars - 0 et z - moitié moins souvent que le reste. Pour une distribution plus uniforme des caractères, j'ai remplacé chars[1+random()*(array_length(chars, 1)-1)]parchars[ceil(61 * random())]
PreciousBodilyFluids
random()est appelé lengthfois (comme dans la plupart des autres solutions). Existe-t-il un moyen plus efficace de choisir entre 62 caractères à chaque fois? Comment cela fonctionne-t-il par rapport à md5()?
ma11hew28
J'ai trouvé une autre solution qui utilise ORDER BY random(). Lequel est plus vite?
ma11hew28
1
Il est intéressant de noter que random peut utiliser erand48 qui n'est pas un CSPRNG, vous feriez probablement mieux d'utiliser simplement pgcrypto.
Yaur
2
Bonne réponse sauf qu'il n'utilise pas de générateur de nombres aléatoires sécurisé et n'est donc pas si bon pour les identifiants de session. Voir: stackoverflow.com/questions/9816114/…
sudo
240

Vous pouvez corriger votre tentative initiale comme ceci:

SELECT md5(random()::text);

Beaucoup plus simple que certaines des autres suggestions. :-)

Peter Eisentraut
la source
16
Notez que cela renvoie des chaînes sur "l'alphabet de chiffres hexadécimaux" {0..9, a..f} uniquement. Peut ne pas être suffisant - cela dépend de ce que vous voulez en faire.
Laryx Decidua
quelle est la longueur de la chaîne retournée? Existe-t-il un moyen de lui faire renvoyer une chaîne plus longue?
andrewrk
8
Lorsqu'elle est représentée en hexadécimal, la longueur d'une chaîne MD5 est toujours de 32 caractères. Si vous vouliez une chaîne d'une longueur de 64, vous pourriez concaténer 2 chaînes MD5: SELECT concat(md5(random()::text), md5(random()::text)); Et si vous vouliez quelque part au milieu (50 caractères par exemple), vous pourriez prendre une sous-chaîne de celle-ci: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell
2
Pas une très bonne solution pour les identifiants de session, pas beaucoup d'aléatoire. La réponse a également 6 ans. Découvrez ceci pour une méthode totalement différente utilisantgen_random_uuid() : plus rapide, plus aléatoire, plus efficacement stockée dans la base de données.
Evan Carroll
@Evan si vous voulez plus de `` aléatoire '' sans extension, vous pouvez SELECT md5(random()::text||random()::text);, ouSELECT md5(random()::text||random()::text||random()::text);
31

En vous basant sur la solution de Marcin, vous pouvez le faire pour utiliser un alphabet arbitraire (dans ce cas, les 62 caractères alphanumériques ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');
grourk
la source
Lent, pas aussi aléatoire, ni aussi efficace à stocker. Pas une très bonne solution pour les identifiants de session, pas beaucoup d'aléatoire. La réponse a également 6 ans. Check out this for a totally different method using gen_random_uuid(): plus rapide, plus aléatoire, plus efficacement stocké dans la base de données.
Evan Carroll
23

Vous pouvez obtenir 128 bits aléatoires à partir d'un UUID. C'est la méthode pour faire le travail dans PostgreSQL moderne.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

Cela vaut peut-être la peine de lire la documentation sur l'UUID aussi

Le type de données uuid stocke les identifiants universellement uniques (UUID) tels que définis par RFC 4122, ISO / CEI 9834-8: 2005 et les normes associées. (Certains systèmes se réfèrent à ce type de données comme un identifiant global unique, ou GUID, à la place.) Cet identifiant est une quantité de 128 bits qui est générée par un algorithme choisi pour rendre très improbable que le même identifiant soit généré par quelqu'un d'autre dans l'univers connu en utilisant le même algorithme. Par conséquent, pour les systèmes distribués, ces identifiants offrent une meilleure garantie d'unicité que les générateurs de séquence, qui ne sont uniques qu'au sein d'une seule base de données.

Dans quelle mesure une collision avec l'UUID est-elle rare ou devinable? En supposant qu'ils sont aléatoires,

Environ 100 trillions d'UUID version 4 devraient être générés pour avoir 1 chance sur un milliard d'un seul doublon («collision»). La probabilité d'une collision augmente à 50% seulement après que 261 UUID (2,3 x 10 ^ 18 ou 2,3 ​​quintillions) ont été générés. En associant ces nombres aux bases de données et en considérant la question de savoir si la probabilité d'une collision d'UUID de la version 4 est négligeable, considérez un fichier contenant 2,3 quintillions d'UUID de la version 4, avec une probabilité de 50% de contenir une collision d'UUID. Ce serait une taille de 36 exaoctets, en supposant qu'aucune autre donnée ou surcharge, des milliers de fois plus grandes que les plus grandes bases de données actuellement existantes, qui sont de l'ordre du pétaoctet. Au taux de 1 milliard d'UUID générés par seconde, il faudrait 73 ans pour générer les UUID du fichier. Il en faudrait également environ 3. 6 millions de disques durs ou de cartouches de bande de 10 téraoctets pour le stocker, sans sauvegarde ni redondance. La lecture du fichier à un taux de transfert "disque à tampon" typique de 1 gigabit par seconde nécessiterait plus de 3000 ans pour un seul processeur. Étant donné que le taux d'erreur de lecture irrécupérable des lecteurs est de 1 bit pour 1018 bits de lecture, au mieux, alors que le fichier contiendrait environ 1020 bits, le simple fait de lire le fichier une fois de bout en bout entraînerait, au moins, environ 100 fois plus de problèmes. lire les UUID que les doublons. Les erreurs de stockage, de réseau, d'alimentation et autres erreurs matérielles et logicielles seraient sans aucun doute des milliers de fois plus fréquentes que les problèmes de duplication d'UUID. un taux de transfert de 1 gigabit par seconde nécessiterait plus de 3000 ans pour un seul processeur. Étant donné que le taux d'erreur de lecture irrécupérable des lecteurs est de 1 bit pour 1018 bits de lecture, au mieux, alors que le fichier contiendrait environ 1020 bits, le simple fait de lire le fichier une fois de bout en bout entraînerait, au moins, environ 100 fois plus de problèmes. lire les UUID que les doublons. Les erreurs de stockage, de réseau, d'alimentation et autres erreurs matérielles et logicielles seraient sans aucun doute des milliers de fois plus fréquentes que les problèmes de duplication d'UUID. un taux de transfert de 1 gigabit par seconde nécessiterait plus de 3000 ans pour un seul processeur. Étant donné que le taux d'erreur de lecture irrécupérable des lecteurs est de 1 bit pour 1018 bits de lecture, au mieux, alors que le fichier contiendrait environ 1020 bits, le simple fait de lire le fichier une fois de bout en bout entraînerait, au moins, environ 100 fois plus de problèmes. lire les UUID que les doublons. Les erreurs de stockage, de réseau, d'alimentation et autres erreurs matérielles et logicielles seraient sans aucun doute des milliers de fois plus fréquentes que les problèmes de duplication d'UUID.

source: wikipedia

En résumé,

  • L'UUID est normalisé.
  • gen_random_uuid()correspond à 128 bits d'aléa stockés sur 128 bits (2 ** 128 combinaisons). 0-déchet.
  • random() ne génère que 52 bits d'aléa dans PostgreSQL (2 ** 52 combinaisons).
  • md5()stocké comme UUID est de 128 bits, mais il ne peut être aussi aléatoire que son entrée ( 52 bits si vous utilisezrandom() )
  • md5()stocké sous forme de texte est de 288 bits, mais il ne peut être aussi aléatoire que son entrée ( 52 bits si vous utilisezrandom() ) - plus de deux fois la taille d'un UUID et une fraction du caractère aléatoire)
  • md5() en tant que hachage, peut être tellement optimisé qu'il ne fait pas grand chose.
  • L'UUID est très efficace pour le stockage: PostgreSQL fournit un type qui est exactement de 128 bits. Contrairement à textet varchar, etc. qui stockent comme un varlenaqui a une surcharge pour la longueur de la chaîne.
  • L'UUID astucieux de PostgreSQL est fourni avec des opérateurs, des castings et des fonctionnalités par défaut.
Evan Carroll
la source
3
En partie incorrect: un UUID aléatoire correctement généré ne comporte que 122 bits aléatoires puisque 4 bits sont utilisés pour la version et 2 bits pour la variante: en.wikipedia.org/wiki/...
Olivier Grégoire
2
Si la source ne fait pas ce qui y est écrit, alors ce n'est pas un UUID et ne devrait pas être appelé en tant que tel par PostgreSQL.
Olivier Grégoire
16

Je jouais avec PostgreSQL récemment, et je pense avoir trouvé une solution un peu meilleure, en utilisant uniquement des méthodes PostgreSQL intégrées - pas de pl / pgsql. La seule limitation est qu'il ne génère actuellement que des chaînes UPCASE, des nombres ou des chaînes en minuscules.

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

Le deuxième argument de la generate_seriesméthode dicte la longueur de la chaîne.

Marcin Raczkowski
la source
8
J'aime cela, mais j'ai trouvé que lorsque je l'ai utilisé une instruction UPDATE, toutes les lignes étaient définies sur le même mot de passe aléatoire au lieu de mots de passe uniques. J'ai résolu ce problème en ajoutant l'ID de clé primaire dans la formule. Je l'ajoute à la valeur aléatoire et je la soustrais à nouveau. Le caractère aléatoire n'est pas modifié, mais PostgreSQL est amené à recalculer les valeurs de chaque ligne. Voici un exemple, utilisant un nom de clé primaire "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg
La solution présentée par @MarkStosberg a fonctionné comme il l'a dit, mais pas comme je m'y attendais; les données produites ne correspondaient pas au modèle prétendu (juste la casse des lettres ou juste des chiffres). J'ai corrigé par arithmétique modulant le résultat aléatoire: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo
4
Non. Vous répondez à la question «Comment générer un identifiant de session aléatoire » et non «Comment générer une chaîne aléatoire ». Vous avez changé la signification de la question (et du titre), basée sur deux mots dans la description. Vous répondez à une question différente. et continuez à abuser de votre pouvoir de modération pour changer la signification de la question.
Marcin Raczkowski
13

Veuillez utiliser string_agg!

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

J'utilise ceci avec MD5 pour générer également un UUID. Je veux juste une valeur aléatoire avec plus de bits qu'un random ()entier.

Andrew Wolfe
la source
Je suppose que je pourrais simplement concaténer random()jusqu'à ce que j'obtienne le nombre de bits que je veux. Tant pis.
Andrew Wolfe
11

Bien qu'il ne soit pas actif par défaut, vous pouvez activer l'une des extensions principales:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Ensuite, votre instruction devient un simple appel à gen_salt () qui génère une chaîne aléatoire:

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

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

Le premier nombre est un identifiant de hachage. Plusieurs algorithmes sont disponibles chacun avec leur propre identifiant:

  • md5: 1 $
  • bf: 2 $ a 06 $
  • des: pas d'identifiant
  • xdes: _J9 ..

Plus d'informations sur les extensions:


ÉDITER

Comme indiqué par Evan Carrol, à partir de la v9.4, vous pouvez utiliser gen_random_uuid()

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

Grotte de Jefferey
la source
Les sels générés semblent trop séquentiels pour être vraiment aléatoires, n'est-ce pas?
Le Droid
1
Faites-vous référence au $1$? C'est un identifiant de type de hachage (md5 == 1), le reste est la valeur aléatoire.
Jefferey Cave
Oui, c'était mon interprétation erronée, merci pour la précision.
Le Droid
6

Je ne pense pas que vous recherchiez une chaîne aléatoire en soi. Ce dont vous auriez besoin pour la vérification de session, c'est une chaîne qui est garantie d'être unique. Stockez-vous les informations de vérification de session pour l'audit? Dans ce cas, vous avez besoin que la chaîne soit unique entre les sessions. Je connais deux approches assez simples:

  1. Utilisez une séquence. Bon pour une utilisation sur une seule base de données.
  2. Utilisez un UUID. Universellement unique, si bien sur les environnements distribués aussi.

Les UUID sont garantis uniques en vertu de leur algorithme de génération; en fait, il est extrêmement improbable que vous génériez deux nombres identiques sur n'importe quelle machine, à tout moment, jamais (notez que c'est beaucoup plus fort que sur des chaînes aléatoires, qui ont une périodicité beaucoup plus petite que les UUID).

Vous devez charger l'extension uuid-ossp pour utiliser les UUID. Une fois installé, appelez l'une des fonctions uuid_generate_vXXX () disponibles dans vos appels SELECT, INSERT ou UPDATE. Le type uuid est un nombre de 16 octets, mais il a également une représentation sous forme de chaîne.

Patrick
la source
Cela semble être un conseil potentiellement dangereux. En ce qui concerne les clés de session, vous voulez un caractère unique et aléatoire suffisamment aléatoire sur le plan cryptographique pour exclure toute chance raisonnable de le deviner. Les algorithmes utilisés par les UUID garantissent l'unicité par des mécanismes non aléatoires (principalement), ce qui constitue une menace pour la sécurité.
jmar777
6
@ jmar777 Tout le but des UUID est qu'ils sont difficiles à deviner et hautement aléatoires. À l'exception de la version v1, ils ont une périodicité très élevée; La v4 est entièrement aléatoire de 128 bits. Ils sont utilisés dans toutes les transactions bancaires en ligne que vous effectuez. S'ils sont assez bons pour cela, ils sont assez bons pour à peu près tout le reste.
Patrick
1
Bien, que sait-tu. Je ne savais pas que cela avait été abordé dans la version 4 . Merci de m'avoir corrigé!
jmar777
@Patrick Small nit, les UUID V4 sont 122 bits aléatoires, pas 128.;)
Jesse
5

Le paramètre INTEGER définit la longueur de la chaîne. Garanti pour couvrir les 62 caractères alphanum avec une probabilité égale (contrairement à certaines autres solutions flottant sur 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;
Laryx Decidua
la source
Lent, pas aussi aléatoire, ni aussi efficace à stocker. Pas une très bonne solution pour les identifiants de session, pas beaucoup d'aléatoire. La réponse a également 6 ans. Check out this for a totally different method using gen_random_uuid(): plus rapide, plus aléatoire, plus efficacement stocké dans la base de données.
Evan Carroll
3
@EvanCarroll: en toute honnêteté, gen_random_uuid()est apparu dans la version 9.4 pour autant que je sache, qui a été publiée le 18/12/2014, plus d'un an après la réponse que vous avez déclinée. Petit bout supplémentaire: la réponse n'a que 3 ans et demi :-) Mais vous avez raison, maintenant que nous l'avons gen_random_uuid(), c'est ce qu'il faut utiliser. Par conséquent, je vais voter pour votre réponse.
Laryx Decidua
5

@Kavius ​​a recommandé d'utiliser pgcrypto, mais au lieu de gen_salt, qu'en est-il gen_random_bytes? Et que diriez-vous sha512au lieu de md5?

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

Documents:

F.25.5. Fonctions de données aléatoires

gen_random_bytes (count integer) renvoie bytea

Renvoie le nombre d'octets aléatoires cryptographiquement forts. Au plus 1024 octets peuvent être extraits à la fois. Ceci permet d'éviter de vider le pool du générateur de aléas.

Jared Beck
la source
4

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

user516487
la source
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
utilisateur457226
la source
Je le modifie pour supprimer la barre oblique et le signe plus qui apparaissent parfois dans le résultat et aussi pour générer un résultat en majuscule select upper (replace (replace (substring (encode (decode (md5 (random () :: text), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt