¿Cómo se crea una cadena aleatoria que sea adecuada para una ID de sesión en PostgreSQL?

101

Me gustaría hacer una cadena aleatoria para usar en la verificación de la sesión usando PostgreSQL. Sé que puedo obtener un número aleatorio con SELECT random(), así que lo intenté SELECT md5(random()), pero no funciona. ¿Cómo puedo hacer esto?

gersh
fuente
Se puede encontrar otra solución aquí stackoverflow.com/a/13675441/398670
Craig Ringer
7
He editado el título para que las respuestas existentes todavía tengan mucho sentido, y la respuesta de Evan, que trae las cosas un poco más modernas, también encaja. No quiero bloquear esta antigua pregunta por una disputa de contenido, así que hagamos modificaciones adicionales que se ajusten a todas las respuestas, por favor.
Tim Post
1
Genial, veamos si @gersh puede aclarar esta pregunta porque existe un desacuerdo legítimo en cuanto a su intención original. Si su intención original es la que supongo que era, muchas de estas respuestas deben ajustarse, rechazarse o retirarse. Y, tal vez, debería plantearse una nueva pregunta sobre la generación de cadenas con fines de prueba (o similares) (donde random()no sea necesario). Si no es lo que supongo, entonces mi respuesta debe adaptarse a la pregunta refinada.
Evan Carroll
5
@EvanCarroll - gersh fue visto por última vez el 21 de noviembre de 2015.
BSMP
5
Para cualquiera que responda a esta pregunta en el año> 2017, considere la respuesta de Evan stackoverflow.com/a/41608000/190234, ya que utiliza los métodos que no estaban disponibles cuando la pregunta se preguntó y respondió originalmente.
Marcin Raczkowski

Respuestas:

83

Sugeriría esta sencilla solución:

Esta es una función bastante simple que devuelve una cadena aleatoria de la longitud dada:

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;

Y el uso:

select random_string(15);

Salida de ejemplo:

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
fuente
6
Esta solución utiliza los valores en cada extremo de la matriz de caracteres (0 yz) con la mitad de frecuencia que el resto. Para una distribución más uniforme de los personajes, reemplacé chars[1+random()*(array_length(chars, 1)-1)]porchars[ceil(61 * random())]
PreciousBodilyFluids
random()se llama lengthveces (como en muchas de las otras soluciones). ¿Existe una forma más eficaz de elegir entre 62 caracteres cada vez? ¿Cómo funciona esto en comparación con md5()?
ma11hew28
Encontré otra solución que usa ORDER BY random(). ¿Cual es mas rápido?
ma11hew28
1
Vale la pena señalar que random puede usar erand48, que no es un CSPRNG, probablemente sea mejor usar pgcrypto.
Yaur
2
Buena respuesta, excepto que no utiliza un generador de números aleatorios seguro y, por lo tanto, no es tan bueno para los ID de sesión. Ver: stackoverflow.com/questions/9816114/…
sudo
240

Puede arreglar su intento inicial de esta manera:

SELECT md5(random()::text);

Mucho más simple que algunas de las otras sugerencias. :-)

Peter Eisentraut
fuente
16
Tenga en cuenta que esto devuelve cadenas sobre el "alfabeto de dígitos hexadecimales" {0..9, a..f} únicamente. Puede que no sea suficiente, depende de lo que quieras hacer con ellos.
Laryx Decidua
¿Cuál es la longitud de la cadena devuelta? ¿Hay alguna forma de hacer que devuelva una cadena más larga?
andrewrk
8
Cuando se representa en hexadecimal, la longitud de una cadena MD5 es siempre de 32 caracteres. Si quisiera una cadena de longitud 64, podría concatenar 2 cadenas MD5: SELECT concat(md5(random()::text), md5(random()::text)); y si quisiera en algún lugar en el medio (50 caracteres, por ejemplo), podría tomar una subcadena de eso: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell
2
No es una muy buena solución para los identificadores de sesión, no hay mucha aleatoriedad. La respuesta también tiene 6 años. Mira esto para ver un método totalmente diferente usandogen_random_uuid() : más rápido, más aleatoriedad, almacenado de manera más eficiente en la base de datos.
Evan Carroll
@Evan, si desea más 'aleatoriedad' sin una extensión, puede SELECT md5(random()::text||random()::text);, oSELECT md5(random()::text||random()::text||random()::text);
31

Basándose en la solución de Marcin, podría hacer esto para usar un alfabeto arbitrario (en este caso, los 62 caracteres alfanuméricos ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');
grourk
fuente
Lento, no tan aleatorio ni tan eficiente de almacenar. No es una muy buena solución para los identificadores de sesión, no hay mucha aleatoriedad. La respuesta también tiene 6 años. Check out this for a totally different method using gen_random_uuid(): más rápido, más aleatorio, almacenado de manera más eficiente en la base de datos.
Evan Carroll
23

Puede obtener 128 bits aleatorios de un UUID. Este es el método para hacer el trabajo en PostgreSQL moderno.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

También puede valer la pena leer los documentos sobre UUID

El tipo de datos uuid almacena identificadores únicos universales (UUID) como se define en RFC 4122, ISO / IEC 9834-8: 2005 y estándares relacionados. (Algunos sistemas se refieren a este tipo de datos como un identificador único global, o GUID, en su lugar). Este identificador es una cantidad de 128 bits que se genera mediante un algoritmo elegido para que sea muy poco probable que alguien más genere el mismo identificador. en el universo conocido utilizando el mismo algoritmo. Por lo tanto, para los sistemas distribuidos, estos identificadores brindan una mejor garantía de unicidad que los generadores de secuencias, que son únicos dentro de una sola base de datos.

¿Qué tan rara es una colisión con UUID o adivinable? Asumiendo que son aleatorios

Se necesitarían generar alrededor de 100 billones de UUID de la versión 4 para tener una probabilidad de 1 entre mil millones de un solo duplicado ("colisión"). La probabilidad de una colisión aumenta al 50% solo después de que se hayan generado 261 UUID (2,3 x 10 ^ 18 o 2,3 quintillones). Relacionando estos números con las bases de datos, y considerando la cuestión de si la probabilidad de una colisión de UUID de la versión 4 es insignificante, considere un archivo que contenga 2,3 trillones de UUID de la versión 4, con un 50% de probabilidad de contener una colisión de UUID. Sería de 36 exabytes de tamaño, asumiendo que no hay otros datos o gastos generales, miles de veces más grandes que las bases de datos más grandes que existen actualmente, que son del orden de los petabytes. A una tasa de mil millones de UUID generados por segundo, se necesitarían 73 años para generar los UUID para el archivo. También requeriría alrededor de 3. 6 millones de discos duros de 10 terabytes o cartuchos de cinta para almacenarlos, suponiendo que no haya copias de seguridad ni redundancia. Leer el archivo a una tasa de transferencia típica de "disco a búfer" de 1 gigabit por segundo requeriría más de 3000 años para un solo procesador. Dado que la tasa de error de lectura irrecuperable de las unidades es de 1 bit por cada 1018 bits leídos, en el mejor de los casos, mientras que el archivo contendría aproximadamente 1020 bits, solo leer el archivo una vez de un extremo a otro resultaría, al menos, en aproximadamente 100 veces más errores. leer UUID que los duplicados. Los errores de almacenamiento, red, alimentación y otros errores de hardware y software serían sin duda miles de veces más frecuentes que los problemas de duplicación de UUID. La tasa de transferencia de 1 gigabit por segundo requeriría más de 3000 años para un solo procesador. Dado que la tasa de error de lectura irrecuperable de las unidades es de 1 bit por cada 1018 bits leídos, en el mejor de los casos, mientras que el archivo contendría aproximadamente 1020 bits, solo leer el archivo una vez de un extremo a otro resultaría, al menos, en aproximadamente 100 veces más errores. leer UUID que los duplicados. Los errores de almacenamiento, red, alimentación y otros errores de hardware y software serían sin duda miles de veces más frecuentes que los problemas de duplicación de UUID. La tasa de transferencia de 1 gigabit por segundo requeriría más de 3000 años para un solo procesador. Dado que la tasa de error de lectura irrecuperable de las unidades es de 1 bit por cada 1018 bits leídos, en el mejor de los casos, mientras que el archivo contendría aproximadamente 1020 bits, solo leer el archivo una vez de un extremo a otro resultaría, al menos, en aproximadamente 100 veces más errores. leer UUID que los duplicados. Los errores de almacenamiento, red, alimentación y otros errores de hardware y software serían sin duda miles de veces más frecuentes que los problemas de duplicación de UUID.

fuente: wikipedia

En resumen,

  • UUID está estandarizado.
  • gen_random_uuid()son 128 bits aleatorios almacenados en 128 bits (2 ** 128 combinaciones). 0-desperdicio.
  • random() solo genera 52 bits aleatorios en PostgreSQL (2 ** 52 combinaciones).
  • md5()almacenado como UUID es de 128 bits, pero solo puede ser tan aleatorio como su entrada ( 52 bits si se usarandom() )
  • md5()almacenado como texto tiene 288 bits, pero solo puede ser tan aleatorio como su entrada ( 52 bits si se usarandom() ), más del doble del tamaño de un UUID y una fracción de la aleatoriedad)
  • md5() como hash, se puede optimizar tanto que no hace mucho.
  • UUID es muy eficiente para el almacenamiento: PostgreSQL proporciona un tipo de exactamente 128 bits. A diferencia de texty varchar, etc., que se almacenan como varlenaque tienen gastos generales para la longitud de la cadena.
  • El UUID ingenioso de PostgreSQL viene con algunos operadores, conversiones y características predeterminados.
Evan Carroll
fuente
3
Parcialmente incorrecto: un UUID aleatorio generado correctamente tiene solo 122 bits aleatorios, ya que se utilizan 4 bits para la versión y 2 bits para la variante: en.wikipedia.org/wiki/…
Olivier Grégoire
2
Si la fuente no hace lo que está escrito allí, entonces no es un UUID y PostgreSQL no debería llamarlo como tal.
Olivier Grégoire
16

Estuve jugando con PostgreSQL recientemente, y creo que encontré una solución un poco mejor, usando solo métodos incorporados de PostgreSQL, sin pl / pgsql. La única limitación es que actualmente genera solo cadenas UPCASE, o números, o cadenas en minúsculas.

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

El segundo argumento del generate_seriesmétodo dicta la longitud de la cadena.

Marcin Raczkowski
fuente
8
Me gusta esto, pero descubrí que cuando lo usé en una declaración UPDATE, todas las filas se establecieron con la misma contraseña aleatoria en lugar de contraseñas únicas. Resolví esto agregando el ID de la clave principal en la fórmula. Lo agrego al valor aleatorio y lo resto nuevamente. La aleatoriedad no cambia, pero se engaña a PostgreSQL para que vuelva a calcular los valores de cada fila. Aquí tienes un ejemplo, usando un nombre de clave principal de "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg
La solución, que presentó @MarkStosberg, funcionó como él dijo, pero no como esperaba; los datos producidos no coincidían con el patrón pretendido (solo mayúsculas y minúsculas o solo dígitos). Arreglé por modulación aritmética el resultado aleatorio: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo
4
No. Estás respondiendo al '¿Cómo puedo generar una identificación de sesión aleatoria? ', No a '¿Cómo puedo generar una cadena aleatoria? '. Ha cambiado el significado de la pregunta (y el título), según dos palabras de la descripción. Estás respondiendo una pregunta diferente. y siga abusando de su poder de moderación para cambiar el significado de la pregunta.
Marcin Raczkowski
13

¡Utilice string_agg!

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

Estoy usando esto con MD5 para generar un UUID también. Solo quiero un valor aleatorio con más bits que un random ()entero.

Andrew Wolfe
fuente
Supongo que podría concatenar random()hasta obtener la cantidad de bits que quiero. Oh bien.
Andrew Wolfe
11

Si bien no está activo de forma predeterminada, puede activar una de las extensiones principales:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Entonces su declaración se convierte en una simple llamada a gen_salt () que genera una cadena aleatoria:

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

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

El número inicial es un identificador de hash. Hay varios algoritmos disponibles, cada uno con su propio identificador:

  • md5: $ 1 $
  • bf: $ 2a $ 06 $
  • des: sin identificador
  • xdes: _J9 ..

Más información sobre extensiones:


EDITAR

Como lo indica Evan Carrol, a partir de la v9.4 puede usar gen_random_uuid()

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

Cueva Jefferey
fuente
Las sales generadas parecen demasiado secuenciales para ser realmente aleatorias, ¿no es así?
Le Droid
1
¿Te refieres al $1$? Ese es un identificador de tipo hash (md5 == 1), el resto es el valor aleatorio.
Jefferey Cave
Sí, esa fue mi interpretación errónea, gracias por la precisión.
Le Droid
6

No creo que esté buscando una cadena aleatoria per se. Lo que necesitaría para la verificación de la sesión es una cadena que esté garantizada como única. ¿Almacena información de verificación de sesión para auditar? En ese caso, necesita que la cadena sea única entre sesiones. Conozco dos enfoques bastante simples:

  1. Usa una secuencia. Bueno para usar en una sola base de datos.
  2. Utilice un UUID. Universalmente único, muy bueno también en entornos distribuidos.

Se garantiza que los UUID son únicos en virtud de su algoritmo de generación; de hecho, es extremadamente improbable que genere dos números idénticos en cualquier máquina, en cualquier momento (tenga en cuenta que esto es mucho más fuerte que en cadenas aleatorias, que tienen una periodicidad mucho menor que los UUID).

Debe cargar la extensión uuid-ossp para usar UUID. Una vez instalado, llame a cualquiera de las funciones uuid_generate_vXXX () disponibles en sus llamadas SELECT, INSERT o UPDATE. El tipo uuid es un número de 16 bytes, pero también tiene una representación de cadena.

Patricio
fuente
Este parece un consejo potencialmente peligroso. Cuando se trata de claves de sesión, desea que la singularidad y la aleatoriedad sean lo suficientemente aleatorias criptográficamente como para excluir cualquier posibilidad razonable de adivinarlas. Los algoritmos utilizados por los UUID garantizan la unicidad mediante mecanismos no aleatorios (en su mayoría), lo que representa una amenaza para la seguridad.
jmar777
6
@ jmar777 El propósito de los UUID es que son difíciles de adivinar y muy aleatorios. A excepción de la versión v1, tienen una periodicidad muy alta; v4 es completamente aleatorio de 128 bits. Se utilizan en todas las transacciones bancarias en línea que realiza. Si son lo suficientemente buenos para eso, son lo suficientemente buenos para casi cualquier otra cosa.
Patrick
1
Bueno, qué sabes. No me di cuenta de que se había abordado en la Versión 4 . ¡Gracias por corregirme!
jmar777
@Patrick Small nit, los UUID V4 son 122 bits aleatorios, no 128;)
Jesse
5

El parámetro INTEGER define la longitud de la cadena. Garantizado para cubrir los 62 caracteres alfanuméricos con la misma probabilidad (a diferencia de otras soluciones que flotan en 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
fuente
Lento, no tan aleatorio ni tan eficiente de almacenar. No es una muy buena solución para los identificadores de sesión, no hay mucha aleatoriedad. La respuesta también tiene 6 años. Check out this for a totally different method using gen_random_uuid(): más rápido, más aleatorio, almacenado de manera más eficiente en la base de datos.
Evan Carroll
3
@EvanCarroll: para ser justos, gen_random_uuid()apareció en la versión 9.4, hasta donde yo sé, que fue lanzada el 18/12/2014, más de un año después de la respuesta que votó en contra. Detalles adicionales: la respuesta tiene solo 3 1/2 años :-) Pero tienes razón, ahora que lo tenemos gen_random_uuid(), esto es lo que debería usarse. Por lo tanto, votaré a favor de su respuesta.
Laryx Decidua
5

@Kavius ​​recomendó usar pgcrypto, pero en lugar de gen_salt, ¿de qué gen_random_bytes? ¿Y qué tal en sha512lugar de md5?

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

Documentos:

F.25.5. Funciones de datos aleatorios

gen_random_bytes (número entero) devuelve bytea

Devuelve el recuento de bytes aleatorios criptográficamente fuertes. Se pueden extraer como máximo 1024 bytes a la vez. Esto es para evitar agotar el grupo de generadores de aleatoriedad.

Jared Beck
fuente
4

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

usuario516487
fuente
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
usuario457226
fuente
Lo modifico para eliminar la barra inclinada y el signo más que a veces aparece en el resultado y también para generar un resultado en mayúsculas, seleccione superior (reemplace (reemplace (subcadena (codificar (decodificar (md5 (aleatorio ()) :: texto), 'hex ') || decodificar (md5 (azar () :: texto),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt