Como você cria uma string aleatória adequada para um ID de sessão no PostgreSQL?

101

Eu gostaria de fazer uma string aleatória para usar na verificação de sessão usando PostgreSQL. Eu sei que posso obter um número aleatório com SELECT random(), então tentei SELECT md5(random()), mas não funcionou. Como posso fazer isso?

gersh
fonte
Outra solução pode ser encontrada aqui stackoverflow.com/a/13675441/398670
Craig Ringer
7
Editei o título para que as respostas existentes ainda façam sentido perfeitamente, e a resposta de Evan trazendo as coisas um pouco mais modernas também se encaixa. Não quero bloquear esta pergunta antiga para uma disputa de conteúdo - então, vamos fazer quaisquer edições adicionais que acomodem todas as respostas, por favor.
Tim Post
1
Legal, vamos ver se @gersh pode esclarecer essa questão porque há um desacordo legítimo quanto à sua intenção original. Se sua intenção original é o que presumo que seja, muitas dessas respostas precisam ser ajustadas, votadas contra ele ou retratadas. E, talvez, uma nova questão sobre a geração de strings para fins de teste (ou semelhantes) deva ser levantada (onde random()ness não é necessário). Se não for o que presumo, então minha resposta deve ser direcionada para a pergunta refinada.
Evan Carroll
5
@EvanCarroll - gersh foi visto pela última vez em 21 de novembro de 2015.
BSMP
5
Para qualquer um que vier a esta pergunta no ano> 2017, considere a resposta de Evan stackoverflow.com/a/41608000/190234 , pois usa os métodos que não estavam disponíveis quando a pergunta foi originalmente feita e respondida.
Marcin Raczkowski

Respostas:

83

Eu sugeriria esta solução simples:

Esta é uma função bastante simples que retorna uma string aleatória do comprimento fornecido:

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 o uso:

select random_string(15);

Exemplo de saída:

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
fonte
6
Esta solução usa os valores nas extremidades da matriz chars - 0 e z - com a metade da freqüência do resto. Para uma distribuição mais uniforme dos caracteres, substituí chars[1+random()*(array_length(chars, 1)-1)]porchars[ceil(61 * random())]
PreciousBodilyFluids
random()é chamado lengthvezes (como em muitas das outras soluções). Existe uma maneira mais eficiente de escolher entre 62 caracteres de cada vez? Como isso funciona em comparação com md5()?
ma11hew28 de
Encontrei outra solução que usa ORDER BY random(). O que é mais rápido?
ma11hew28 de
1
Vale a pena notar que random pode usar erand48 que não é um CSPRNG, provavelmente é melhor usar apenas pgcrypto.
Yaur
2
Boa resposta, exceto que ele não usa um gerador de número aleatório seguro e, portanto, não é tão bom para IDs de sessão. Consulte: stackoverflow.com/questions/9816114/…
sudo
240

Você pode corrigir sua tentativa inicial desta forma:

SELECT md5(random()::text);

Muito mais simples do que algumas das outras sugestões. :-)

Peter Eisentraut
fonte
16
Observe que isso retorna strings sobre o "alfabeto de dígitos hexadecimais" {0..9, a..f} apenas. Pode não ser suficiente - depende do que você deseja fazer com eles.
Laryx Decidua
qual é o comprimento da string retornada? Existe uma maneira de fazer com que ele retorne uma string mais longa?
andrewrk
8
Quando representado em hexadecimal, o comprimento de uma string MD5 é sempre de 32 caracteres. Se você quiser uma string de comprimento 64, poderá concatenar 2 strings MD5: SELECT concat(md5(random()::text), md5(random()::text)); E se quiser algum lugar no meio (50 caracteres, por exemplo), você pode pegar uma substring disso: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell
2
Não é uma solução muito boa para ids de sessão, sem muita aleatoriedade. A resposta também tem 6 anos. Verifique isso para um método totalmente diferente usandogen_random_uuid() : mais rápido, mais aleatoriedade, mais eficientemente armazenado no banco de dados.
Evan Carroll
@Evan se você quiser mais 'aleatoriedade' sem uma extensão, você pode SELECT md5(random()::text||random()::text);, ouSELECT md5(random()::text||random()::text||random()::text);
31

Com base na solução de Marcin, você poderia fazer isso para usar um alfabeto arbitrário (neste caso, todos os 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
fonte
Lento, não tão aleatório ou eficiente para armazenar. Não é uma solução muito boa para ids de sessão, sem muita aleatoriedade. A resposta também tem 6 anos. Check out this for a totally different method using gen_random_uuid(): mais rápido, mais aleatório, mais eficientemente armazenado no banco de dados.
Evan Carroll
23

Você pode obter 128 bits aleatórios de um UUID. Este é o método para realizar o trabalho no PostgreSQL moderno.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

Pode valer a pena ler a documentação no UUID também

O tipo de dados uuid armazena Identificadores Únicos Universais (UUID) conforme definido pela RFC 4122, ISO / IEC 9834-8: 2005 e padrões relacionados. (Alguns sistemas se referem a este tipo de dados como um identificador globalmente exclusivo, ou GUID, em vez disso.) Este identificador é uma quantidade de 128 bits gerada por um algoritmo escolhido para tornar muito improvável que o mesmo identificador seja gerado por outra pessoa no universo conhecido usando o mesmo algoritmo. Portanto, para sistemas distribuídos, esses identificadores fornecem uma melhor garantia de exclusividade do que os geradores de sequência, que são exclusivos apenas em um único banco de dados.

Quão rara é uma colisão com UUID ou adivinhada? Supondo que sejam aleatórios,

Cerca de 100 trilhões de UUIDs da versão 4 precisariam ser gerados para ter uma chance de 1 em um bilhão de uma única duplicata ("colisão"). A chance de uma colisão aumenta para 50% somente depois que 261 UUIDs (2,3 x 10 ^ 18 ou 2,3 ​​quintilhões) foram gerados. Relacionando esses números aos bancos de dados e considerando a questão de se a probabilidade de uma colisão de UUID da Versão 4 é insignificante, considere um arquivo contendo 2,3 quintilhões de UUIDs da Versão 4, com 50% de chance de conter uma colisão de UUID. Teria 36 exabytes de tamanho, assumindo nenhum outro dado ou sobrecarga, milhares de vezes maior do que os maiores bancos de dados existentes atualmente, que são da ordem de petabytes. À taxa de 1 bilhão de UUIDs gerados por segundo, levaria 73 anos para gerar os UUIDs para o arquivo. Também exigiria cerca de 3. 6 milhões de unidades de disco rígido de 10 terabytes ou cartuchos de fita para armazená-lo, supondo que não haja backups ou redundância. Ler o arquivo em uma taxa de transferência típica de "disco para buffer" de 1 gigabit por segundo exigiria mais de 3.000 anos para um único processador. Como a taxa de erro de leitura irrecuperável das unidades é de 1 bit por 1018 bits lidos, na melhor das hipóteses, enquanto o arquivo conteria cerca de 1020 bits, apenas ler o arquivo uma vez de ponta a ponta resultaria, pelo menos, em cerca de 100 vezes mais erros. leia UUIDs do que duplicatas. Erros de armazenamento, rede, energia e outros erros de hardware e software seriam, sem dúvida, milhares de vezes mais frequentes do que os problemas de duplicação de UUID. a taxa de transferência de 1 gigabit por segundo exigiria mais de 3.000 anos para um único processador. Como a taxa de erro de leitura irrecuperável das unidades é de 1 bit por 1018 bits lidos, na melhor das hipóteses, enquanto o arquivo conteria cerca de 1020 bits, apenas ler o arquivo uma vez de ponta a ponta resultaria, pelo menos, em cerca de 100 vezes mais erros. leia UUIDs do que duplicatas. Erros de armazenamento, rede, energia e outros erros de hardware e software seriam, sem dúvida, milhares de vezes mais frequentes do que os problemas de duplicação de UUID. a taxa de transferência de 1 gigabit por segundo exigiria mais de 3.000 anos para um único processador. Como a taxa de erro de leitura irrecuperável das unidades é de 1 bit por 1018 bits lidos, na melhor das hipóteses, enquanto o arquivo conteria cerca de 1020 bits, apenas ler o arquivo uma vez de ponta a ponta resultaria, pelo menos, em cerca de 100 vezes mais erros. leia UUIDs do que duplicatas. Erros de armazenamento, rede, energia e outros erros de hardware e software seriam, sem dúvida, milhares de vezes mais frequentes do que os problemas de duplicação de UUID.

fonte: wikipedia

Em suma,

  • UUID é padronizado.
  • gen_random_uuid()é 128 bits aleatórios armazenados em 128 bits (2 ** 128 combinações). 0-desperdício.
  • random() gera apenas 52 bits aleatórios no PostgreSQL (2 ** 52 combinações).
  • md5()armazenado como UUID tem 128 bits, mas só pode ser tão aleatório quanto sua entrada ( 52 bits se estiver em usorandom() )
  • md5()armazenado como texto tem 288 bits, mas só pode ser tão aleatório quanto sua entrada ( 52 bits se estiver usandorandom() ) - mais de duas vezes o tamanho de um UUID e uma fração da aleatoriedade)
  • md5() como hash, pode ser tão otimizado que não faz muita coisa efetivamente.
  • UUID é altamente eficiente para armazenamento: PostgreSQL fornece um tipo que tem exatamente 128 bits. Diferente de texte varchar, etc, que armazenam como um varlenaque tem sobrecarga para o comprimento da string.
  • O UUID bacana do PostgreSQL vem com alguns operadores, castings e recursos padrão.
Evan Carroll
fonte
3
Parcialmente incorreto: um UUID aleatório gerado corretamente tem apenas 122 bits aleatórios, já que 4 bits são usados ​​para a versão e 2 bits para a variante: en.wikipedia.org/wiki/…
Olivier Grégoire
2
Se a fonte não faz o que está escrito lá, então não é um UUID e não deve ser chamada como tal pelo PostgreSQL.
Olivier Grégoire de
16

Eu estava brincando com o PostgreSQL recentemente e acho que encontrei uma solução um pouco melhor, usando apenas métodos PostgreSQL embutidos - sem pl / pgsql. A única limitação é que atualmente gera apenas strings UPCASE, ou números, ou strings em 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

O segundo argumento do generate_seriesmétodo determina o comprimento da string.

Marcin Raczkowski
fonte
8
Eu gosto disso, mas descobri que quando usei uma instrução UPDATE, todas as linhas foram definidas para a mesma senha aleatória em vez de senhas exclusivas. Resolvi isso adicionando o ID da chave primária à fórmula. Eu adiciono ao valor aleatório e subtraio novamente. A aleatoriedade não é alterada, mas o PostgreSQL é enganado para recalcular os valores de cada linha. Aqui está um exemplo, usando um nome de chave primária de "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg
A solução apresentada por @MarkStosberg funcionou como ele disse, mas não como eu esperava; os dados produzidos não correspondiam ao padrão pretendido (apenas letras maiúsculas ou apenas dígitos). Fixei por modulação aritmética o resultado aleatório: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo
4
Não. Você está respondendo a 'Como faço para gerar id de sessão aleatória ' e não 'Como faço para gerar string aleatória '. Você mudou o significado da pergunta (e do título), com base em duas palavras na descrição. Você está respondendo a uma pergunta diferente. e continue abusando do seu poder de moderação para mudar o significado da pergunta.
Marcin Raczkowski
13

Por favor, use string_agg!

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

Estou usando isso com MD5 para gerar um UUID também. Eu só quero um valor aleatório com mais bits do que um random ()inteiro.

Andrew Wolfe
fonte
Suponho que poderia apenas concatenar random()até obter o número de bits que desejo. Ah bem.
Andrew Wolfe,
11

Embora não esteja ativo por padrão, você pode ativar uma das extensões principais:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Então sua declaração se torna uma chamada simples para gen_salt () que gera uma string aleatória:

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

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

O número inicial é um identificador hash. Vários algoritmos estão disponíveis, cada um com seu próprio identificador:

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

Mais informações sobre extensões:


EDITAR

Conforme indicado por Evan Carrol, a partir da v9.4 você pode usar gen_random_uuid()

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

Caverna Jefferey
fonte
Os sais gerados parecem muito sequenciais para serem realmente aleatórios, não é?
Le Droid de
1
Você está se referindo ao $1$? Esse é um identificador de tipo hash (md5 == 1), o resto é o valor aleatório.
Caverna Jefferey de
Sim, essa foi minha interpretação errada, obrigado pela precisão.
Le Droid de
6

Não acho que você esteja procurando uma string aleatória por si só. O que você precisa para a verificação da sessão é uma string com garantia de exclusividade. Você armazena informações de verificação de sessão para auditoria? Nesse caso, você precisa que a string seja exclusiva entre as sessões. Eu conheço duas abordagens bastante simples:

  1. Use uma sequência. Bom para uso em um único banco de dados.
  2. Use um UUID. Universalmente único, muito bom também em ambientes distribuídos.

Os UUIDs têm a garantia de serem exclusivos em virtude de seu algoritmo de geração; efetivamente, é extremamente improvável que você gere dois números idênticos em qualquer máquina, a qualquer momento, nunca (observe que isso é muito mais forte do que em strings aleatórias, que têm uma periodicidade muito menor do que UUIDs).

Você precisa carregar a extensão uuid-ossp para usar UUIDs. Uma vez instalado, chame qualquer uma das funções uuid_generate_vXXX () disponíveis em suas chamadas SELECT, INSERT ou UPDATE. O tipo uuid é um numeral de 16 bytes, mas também tem uma representação de string.

Patrick
fonte
Parece um conselho potencialmente perigoso. Quando se trata de chaves de sessão, você deseja exclusividade e aleatoriedade que sejam criptograficamente aleatórias o suficiente para impedir qualquer chance razoável de adivinhá-las. Os algoritmos usados ​​pelos UUIDs garantem a exclusividade por mecanismos não aleatórios (principalmente), o que representa uma ameaça à segurança.
jmar777
6
@ jmar777 Todo o propósito dos UUIDs é que eles são difíceis de adivinhar e altamente aleatórios. Exceto pela versão v1, eles têm uma periodicidade muito alta; A v4 é totalmente aleatória de 128 bits. Eles estão sendo usados ​​em todas as transações bancárias online que você faz. Se eles são bons o suficiente para isso, eles são bons o suficiente para praticamente qualquer outra coisa.
Patrick de
1
Bem, o que você sabe. Não percebi que isso havia sido abordado na Versão 4 . Obrigado por me corrigir!
jmar777
@Patrick Small nit, V4 UUIDs são 122 bits aleatórios, não 128.;)
Jesse
5

O parâmetro INTEGER define o comprimento da string. Garantido para cobrir todos os 62 caracteres alfanuméricos com a mesma probabilidade (ao contrário de algumas outras soluções flutuando na 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
fonte
Lento, não tão aleatório ou eficiente para armazenar. Não é uma solução muito boa para ids de sessão, sem muita aleatoriedade. A resposta também tem 6 anos. Check out this for a totally different method using gen_random_uuid(): mais rápido, mais aleatório, mais eficientemente armazenado no banco de dados.
Evan Carroll
3
@EvanCarroll: com toda a justiça, gen_random_uuid()apareceu na versão 9.4, pelo que eu posso dizer, que foi lançada em 2014-12-18, mais de um ano após a resposta que você votou negativamente. Detalhes adicionais: a resposta tem apenas 3 anos e meio :-) Mas você está certo, agora que temos gen_random_uuid(), é isso que deve ser usado. Portanto, irei aprovar sua resposta.
Laryx Decidua
5

@Kavius ​​recomendou usar pgcrypto, mas em vez de gen_salt, e quanto gen_random_bytes? E que tal em sha512vez de md5?

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

Docs:

F.25.5. Funções de dados aleatórios

gen_random_bytes (número inteiro de contagem) retorna bytea

Retorna contagem de bytes aleatórios criptograficamente fortes. No máximo 1024 bytes podem ser extraídos por vez. Isso evita o esgotamento do conjunto do gerador de aleatoriedade.

Jared Beck
fonte
4

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

user516487
fonte
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
user457226
fonte
Eu corrijo-o para remover a barra e o sinal de adição que às vezes aparecem no resultado e também para gerar um resultado em maiúsculas selecione superior (substituir (substituir (substring (codificar (decodificar (md5 (random ()) :: texto), 'hex ') || decodificar (md5 (random () :: texto),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt