PostgreSQL'de bir oturum kimliği için uygun olan rastgele bir dizeyi nasıl yaratırsınız?


101

PostgreSQL kullanarak oturum doğrulamada kullanmak için rastgele bir dizge oluşturmak istiyorum. Rastgele bir sayı alabileceğimi biliyorum SELECT random(), bu yüzden denedim SELECT md5(random()), ama bu işe yaramıyor. Bunu nasıl yapabilirim?


Başka bir çözüm burada bulunabilir stackoverflow.com/a/13675441/398670
Craig Ringer

7
Başlığı düzenledim, böylece mevcut cevaplar hala tamamen mantıklı olacak ve Evan'ın cevabı da biraz daha modern bir uyum getiriyor. Bu asırlık soruyu bir içerik anlaşmazlığı nedeniyle kilitlemek istemiyorum - bu yüzden lütfen tüm cevaplara uygun ek düzenlemeler yapalım .
Tim Post

1
Harika, bakalım @gersh bu soruyu açıklığa kavuşturabilir mi çünkü orijinal niyetiyle ilgili meşru bir anlaşmazlık var. İlk niyeti varsaydığım şeyse, bu yanıtların çoğunun ayarlanması, olumsuz oylanması veya geri çekilmesi gerekir. Ve belki de test amaçlı (veya benzeri) dizeler üretmeyle ilgili yeni bir soru sorulmalıdır (burada random()gerek yok). Eğer varsaydığım bu değilse, cevabımın bunun yerine rafine soruya hitap etmesi gerekir.
Evan Carroll

5
@EvanCarroll - gersh en son 21 Kasım 2015'te görüldü.
BSMP

5
Yıl> 2017'de bu soruya gelen herkes için, Evan'ın yanıtını stackoverflow.com/a/41608000/190234 olarak düşünün , çünkü soru başlangıçta sorulduğunda ve yanıtlandığında mevcut olmayan yöntemleri kullanıyor.
Marcin Raczkowski

Yanıtlar:


84

Bu basit çözümü öneririm:

Bu, verilen uzunlukta rastgele bir dizge döndüren oldukça basit bir işlevdir:

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;

Ve kullanım:

select random_string(15);

Örnek çıktı:

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
Bu çözüm, karakter dizisinin her iki ucundaki değerleri - 0 ve z - diğerlerinin yarısı kadar sıklıkla kullanır. Karakter daha eşit dağılımı için, ben yerini chars[1+random()*(array_length(chars, 1)-1)]ilechars[ceil(61 * random())]
PreciousBodilyFluids

random()lengthkez çağrılır (diğer birçok çözümde olduğu gibi). Her seferinde 62 karakter arasından seçim yapmanın daha verimli bir yolu var mı? Bu, ile karşılaştırıldığında nasıl performans gösterir md5()?
ma11hew28

Kullanan başka bir çözüm buldum ORDER BY random(). Hangisi daha hızlı?
ma11hew28

1
Random'ın bir CSPRNG olmayan erand48'i kullanabileceğini belirtmek gerekir, muhtemelen sadece pgcrypto kullanmanız daha iyidir.
Yaur

2
Güvenli bir rasgele sayı üreteci kullanmaması ve bu nedenle oturum kimlikleri için o kadar iyi olmaması dışında iyi yanıt. Bakınız: stackoverflow.com/questions/9816114/…
sudo

240

İlk denemenizi şu şekilde düzeltebilirsiniz:

SELECT md5(random()::text);

Diğer önerilerden çok daha basit. :-)


17
Bunun yalnızca "onaltılık basamak alfabesi" {0..9, a..f} üzerinden dizeler döndürdüğünü unutmayın. Yeterli olmayabilir - onlarla ne yapmak istediğinize bağlıdır.
Laryx Desidua

döndürülen dizenin uzunluğu nedir? Daha uzun bir dize döndürmenin bir yolu var mı?
andrewrk

8
Onaltılık olarak temsil edildiğinde, bir MD5 dizesinin uzunluğu her zaman 32 karakterdir. 64 uzunluğunda bir dizi istiyorsanız, 2 MD5 dizesini birleştirebilirsiniz: SELECT concat(md5(random()::text), md5(random()::text)); Ortada bir yer istiyorsanız (örneğin 50 karakter), bunun bir alt dizesini alabilirsiniz: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell

2
Oturum kimlikleri için pek iyi bir çözüm değil, pek rastgelelik değil. Cevap da 6 yaşında. Şunu kullanarak tamamen farklı bir yöntem için buna göz atıngen_random_uuid() : daha hızlı, daha fazla rastgelelik, veritabanında daha verimli depolanmış.
Evan Carroll

@Evan, bir uzantı olmadan daha fazla 'rastgelelik' istiyorsanız SELECT md5(random()::text||random()::text);, ya daSELECT md5(random()::text||random()::text||random()::text);

31

Marcin'in çözümüne dayanarak, rastgele bir alfabe kullanmak için bunu yapabilirsiniz (bu durumda, 62 ASCII alfanümerik karakterin tümü):

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

Yavaş, rastgele değil veya depolaması kadar verimli. Oturum kimlikleri için pek iyi bir çözüm değil, pek rastgelelik değil. Cevap da 6 yaşında. Check out this for a totally different method using gen_random_uuid(): daha hızlı, daha fazla rastgelelik, daha verimli bir şekilde veritabanında depolanır.
Evan Carroll

23

Bir UUID'den 128 bit rasgele alabilirsiniz. Bu, modern PostgreSQL'de işi halletmenin yöntemidir.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

UUID'deki belgeleri de okumaya değer olabilir

Uuid veri türü, RFC 4122, ISO / IEC 9834-8: 2005 ve ilgili standartlar tarafından tanımlanan Evrensel Benzersiz Tanımlayıcıları (UUID) depolar . (Bazı sistemler bu veri türüne küresel olarak benzersiz bir tanımlayıcı veya bunun yerine GUID olarak atıfta bulunur.) Bu tanımlayıcı, aynı tanımlayıcının başkaları tarafından oluşturulma ihtimalini çok düşük hale getirmek için seçilen bir algoritma tarafından oluşturulan 128 bitlik bir miktardır . aynı algoritmayı kullanarak bilinen evrende. Bu nedenle, dağıtılmış sistemler için bu tanımlayıcılar, yalnızca tek bir veritabanı içinde benzersiz olan dizi oluşturuculardan daha iyi bir benzersizlik garantisi sağlar.

UUID ile çarpışma ne kadar nadir veya tahmin edilebilir? Rastgele olduklarını varsayarak,

Tek bir kopya ("çarpışma") olasılığının milyarda 1 olması için yaklaşık 100 trilyon sürüm 4 UUID'nin oluşturulması gerekir. Bir çarpışma şansı, yalnızca 261 UUID (2.3 x 10 ^ 18 veya 2.3 quintillion) oluşturulduktan sonra% 50'ye yükselir. Bu sayıları veri tabanlarıyla ilişkilendirirken ve Sürüm 4 UUID çarpışmasının olasılığının ihmal edilebilir olup olmadığı konusunu göz önünde bulundurarak, 2.3 kentilyon Sürüm 4 UUID içeren ve% 50 olasılıkla bir UUID çarpışması içeren bir dosya düşünün. Başka hiçbir veri veya ek yük varsayılmadığı takdirde, şu anda var olan ve petabayt düzeyinde olan en büyük veritabanlarından binlerce kat daha büyük olan 36 eksabayt boyutunda olacaktır. Saniyede üretilen 1 milyar UUID oranında, dosya için UUID'lerin oluşturulması 73 yıl sürecektir. Ayrıca yaklaşık 3 gerektirir. Yedekleme veya artıklık olmadığı varsayılarak saklamak için 6 milyon 10 terabaytlık sabit sürücü veya teyp kartuşu. Dosyayı saniyede 1 gigabit'lik tipik bir "diskten tampona" aktarım hızında okumak, tek bir işlemci için 3000 yıldan fazla zaman gerektirir. Sürücülerin kurtarılamaz okuma hatası oranı, okunan 1018 bit başına 1 bit olduğundan, en iyi ihtimalle dosya yaklaşık 1020 bit içerecektir, dosyayı bir uçtan diğer uca okumak, en azından yaklaşık 100 kat daha fazla yanlışla sonuçlanacaktır. kopyalardan daha UUID'leri oku. Depolama, ağ, güç ve diğer donanım ve yazılım hataları şüphesiz UUID çoğaltma sorunlarından binlerce kat daha sık olacaktır. Saniyede 1 gigabit'lik aktarım hızı, tek bir işlemci için 3000 yıldan fazla bir süre gerektirir. Sürücülerin kurtarılamaz okuma hatası oranı, okunan 1018 bit başına 1 bit olduğundan, en iyi ihtimalle dosya yaklaşık 1020 bit içerecektir, sadece dosyayı bir uçtan bir uca okumak, en azından yaklaşık 100 kat daha fazla yanlışla sonuçlanacaktır. UUID'leri kopyalardan daha okuyun. Depolama, ağ, güç ve diğer donanım ve yazılım hataları şüphesiz UUID çoğaltma sorunlarından binlerce kat daha sık olacaktır. Saniyede 1 gigabit'lik aktarım hızı, tek bir işlemci için 3000 yıldan fazla bir süre gerektirir. Sürücülerin kurtarılamaz okuma hatası oranı, okunan 1018 bit başına 1 bit olduğundan, en iyi ihtimalle dosya yaklaşık 1020 bit içerecektir, sadece dosyayı bir uçtan bir uca okumak, en azından yaklaşık 100 kat daha fazla yanlışla sonuçlanacaktır. UUID'leri kopyalardan daha okuyun. Depolama, ağ, güç ve diğer donanım ve yazılım hataları şüphesiz UUID çoğaltma sorunlarından binlerce kat daha sık olacaktır.

kaynak: wikipedia

Özetle,

  • UUID standartlaştırılmıştır.
  • gen_random_uuid()128 bitlik rasgele depolanan 128 bittir (2 ** 128 kombinasyon). 0 atık.
  • random() PostgreSQL'de yalnızca 52 bit rasgele üretir (2 ** 52 kombinasyon).
  • md5()UUID olarak saklanan 128 bittir, ancak yalnızca girişi kadar rastgele olabilir (kullanılıyorsa 52 bit)random() )
  • md5()metin olarak saklanan 288 bittir, ancak yalnızca girdisi kadar rasgele olabilir (kullanılıyorsa 52 bit)random() ) - bir UUID'nin iki katı boyutunda ve rasgeleliğin bir kısmından fazla)
  • md5() bir karma olarak, o kadar optimize edilebilir ki etkili bir şekilde pek bir şey yapmaz.
  • UUID, depolama için oldukça etkilidir: PostgreSQL, tam olarak 128 bitlik bir tür sağlar. Aksine textve varchara, vb gibi depolayanvarlena uzunluğu boyunca ek yükü olan .
  • PostgreSQL şık UUID, bazı varsayılan operatörler, dökümler ve özelliklerle birlikte gelir.

3
Kısmen yanlış: Düzgün oluşturulmuş rastgele bir UUID, sürüm için 4 bit ve varyant için 2 bit kullanıldığından yalnızca 122 rastgele bit içerir: en.wikipedia.org/wiki/…
Olivier Grégoire

2
Kaynak orada yazılanı yapmazsa, bu bir UUID değildir ve PostgreSQL tarafından olduğu gibi çağrılmamalıdır.
Olivier Grégoire

16

Son zamanlarda PostgreSQL ile oynuyordum ve sadece yerleşik PostgreSQL yöntemlerini kullanarak biraz daha iyi bir çözüm bulduğumu düşünüyorum - pl / pgsql yok. Tek sınırlama, şu anda yalnızca UPCASE dizelerini veya sayıları veya küçük harfli dizeleri oluşturmasıdır.

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

generate_seriesYöntemin ikinci argümanı dizenin uzunluğunu belirler.


8
Bunu beğendim, ancak onu bir UPDATE ifadesi kullandığımda, tüm satırlar benzersiz parolalar yerine aynı rastgele parolaya ayarlandı. Bunu, birincil anahtar kimliğini formüle ekleyerek çözdüm. Rastgele değere eklerim ve tekrar çıkarırım. Rastgelelik değişmez, ancak PostgreSQL her satır için değerleri yeniden hesaplamak için kandırılır. "My_id" birincil anahtar adını kullanan bir örnek: array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg

@MarkStosberg'in sunduğu çözüm söylediği gibi çalıştı ama beklediğim gibi olmadı; üretilen veriler sözde modelle eşleşmedi (sadece harf durumu veya sadece rakamlar). Rastgele sonucu aritmetik modüle ederek düzelttim: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo

4
Hayır. ' Rastgele diziyi nasıl oluştururum' değil, 'Nasıl rastgele oturum kimliği oluştururum ' yanıtını veriyorsunuz . Açıklamadaki iki kelimeye göre sorunun (ve başlığın) anlamını değiştirdiniz. Farklı soruyu cevaplıyorsunuz. ve sorunun anlamını değiştirmek için denetleme gücünüzü kötüye kullanmaya devam edin.
Marcin Raczkowski

14

Lütfen kullanın string_agg!

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

Bunu da bir UUID oluşturmak için MD5 ile kullanıyorum. random ()Tam sayıdan daha fazla bit içeren rastgele bir değer istiyorum .


Sanırım random()istediğim bit sayısını elde edene kadar sırayla birleştirebilirim . Oh iyi.
Andrew Wolfe

11

Varsayılan olarak etkin olmasa da, temel uzantılardan birini etkinleştirebilirsiniz:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Sonra ifadeniz, rastgele bir dizge oluşturan gen_salt () 'a basit bir çağrı olur:

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

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

Baştaki sayı bir karma tanımlayıcıdır. Her biri kendi tanımlayıcısına sahip çeşitli algoritmalar mevcuttur:

  • md5: 1 $
  • bf: 2a $ 06 $
  • des: tanımlayıcı yok
  • xdes: _J9 ..

Uzantılar hakkında daha fazla bilgi:


DÜZENLE

Evan Carrol tarafından belirtildiği gibi, v9.4'ten itibaren kullanabilirsiniz gen_random_uuid()

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


Üretilen tuzlar gerçekten rastgele olamayacak kadar sıralı görünüyor, değil mi?
Le Droid

1
Kastediyor musunuz $1$? Bu bir karma tip tanımlayıcıdır (md5 == 1), geri kalanı rastgele hale getirilmiş değerdir.
Jefferey Mağarası

Evet, bu benim hatalı yorumumdu, hassasiyet için teşekkürler.
Le Droid

6

Rastgele bir dizge aradığınızı sanmıyorum. Oturum doğrulaması için ihtiyacınız olan şey, benzersiz olması garanti edilen bir dizedir. Denetim için oturum doğrulama bilgilerini depoluyor musunuz? Bu durumda dizenin oturumlar arasında benzersiz olması gerekir. Oldukça basit iki yaklaşım biliyorum:

  1. Bir sıra kullanın. Tek bir veritabanında kullanım için iyidir.
  2. Bir UUID kullanın. Evrensel olarak benzersiz, dağıtılmış ortamlarda da çok iyi.

UUID'lerin, üretme algoritmaları sayesinde benzersiz olmaları garanti edilir; etkili bir şekilde son derece herhangi bir zamanda, herhangi bir zamanda herhangi bir makinede iki aynı sayı üretmeniz düşük bir ihtimaldir (bunun, UUID'lerden çok daha küçük bir periyodikliğe sahip olan rastgele dizelerden çok daha güçlü olduğuna dikkat edin).

UUID'leri kullanmak için uuid-ossp uzantısını yüklemeniz gerekir. Kurulduktan sonra, SELECT, INSERT veya UPDATE çağrılarınızdaki mevcut uuid_generate_vXXX () işlevlerinden herhangi birini çağırın. Uuid türü 16 baytlık bir sayıdır, ancak aynı zamanda bir dize temsiline de sahiptir.


Bu potansiyel olarak tehlikeli bir tavsiye gibi görünüyor. Oturum anahtarları söz konusu olduğunda , herhangi bir makul tahmin şansını ortadan kaldıracak şekilde kriptografik olarak yeterince rastgele olan benzersizlik ve rastgelelik istersiniz . UUID'ler tarafından kullanılan algoritmalar, bir güvenlik tehdidi oluşturan rastgele olmayan (çoğunlukla) mekanizmalar tarafından benzersizliği garanti eder.
jmar777

6
@ jmar777 UUID'lerin tüm amacı, tahmin edilmelerinin zor ve oldukça rastgele olmalarıdır. V1 versiyonu haricinde çok yüksek bir periyodikliğe sahipler; v4 tamamen 128-bit rasgele. Yaptığınız her çevrimiçi bankacılık işleminde kullanılıyorlar. Bunun için yeterince iyiyse, hemen hemen her şey için yeterince iyidirler.
Patrick

1
Peki ne biliyorsun? Bunun Sürüm 4'te ele alındığını fark etmemiştim . Beni düzelttiğin için teşekkürler!
jmar777

@Patrick Small nit, V4 UUID'ler 122 bit rasgele, 128 değil.;)
Jesse

5

INTEGER parametresi, dizenin uzunluğunu tanımlar. 62 alfanum karakterin tümünü eşit olasılıkla kapsama garantisi (İnternette dolaşan diğer çözümlerin aksine).

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;

Yavaş, rastgele değil veya depolaması kadar verimli. Oturum kimlikleri için pek iyi bir çözüm değil, pek rastgelelik değil. Cevap da 6 yaşında. Check out this for a totally different method using gen_random_uuid(): daha hızlı, daha fazla rastgelelik, daha verimli bir şekilde veritabanında depolanır.
Evan Carroll

3
@EvanCarroll: Doğruyu gen_random_uuid()söylemek gerekirse, olumsuz oy verdiğiniz yanıttan bir yıldan fazla bir süre sonra 2014-12-18'de yayınlanan 9.4 Sürümünde göründüğünü söyleyebilirim. Ek nitpick: cevap sadece 3 1/2 yaşında :-) Ama haklısın, şimdi bizde gen_random_uuid()olduğuna göre, kullanılması gereken bu. Bu nedenle cevabınızı yükselteceğim.
Laryx Decidua

5

@Kavius ​​kullanmanızı tavsiye etti pgcrypto, ama bunun yerine gen_salt, peki ya gen_random_bytes? Ve sha512bunun yerine nasıl olur md5?

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

Dokümanlar:

F.25.5. Rastgele Veri Fonksiyonları

gen_random_bytes (tamsayı saymak) bayt döndürür

Kriptografik olarak güçlü rastgele baytları döndürür. Bir seferde en fazla 1024 bayt çıkarılabilir. Bu, rastgelelik oluşturucu havuzunun boşaltılmasını önlemek içindir.



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

Bazen sonuçta görünen eğik çizgiyi ve artı işaretini kaldıracak ve ayrıca büyük harfli bir sonuç oluşturacak şekilde değiştiriyorum (replace (replace (substring (encode (decode (md5 (random ()) :: text), 'hex) ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.