Bagaimana Anda membuat string acak yang cocok untuk ID sesi di PostgreSQL?

101

Saya ingin membuat string acak untuk digunakan dalam verifikasi sesi menggunakan PostgreSQL. Saya tahu saya bisa mendapatkan nomor acak dengan SELECT random(), jadi saya mencoba SELECT md5(random()), tetapi tidak berhasil. Bagaimana saya bisa melakukan ini?

gersh
sumber
Solusi lain dapat ditemukan di sini stackoverflow.com/a/13675441/398670
Craig Ringer
7
Saya telah mengedit judulnya sehingga jawaban yang ada masih masuk akal, dan jawaban Evan membawa hal-hal yang sedikit lebih modern juga cocok. Saya tidak ingin mengunci pertanyaan kuno ini untuk sengketa konten - jadi mari kita lakukan pengeditan tambahan yang mengakomodasi semua jawaban.
Pos Tim
1
Keren, mari kita lihat apakah @gersh dapat mengklarifikasi pertanyaan ini karena ada ketidaksepakatan yang sah mengenai niat aslinya. Jika niat aslinya adalah seperti yang saya asumsikan, banyak dari jawaban ini perlu disesuaikan, diturunkan suara atau dicabut. Dan, mungkin pertanyaan baru tentang menghasilkan string untuk tujuan pengujian (atau sejenisnya) harus diajukan (di mana random()ness tidak diperlukan). Jika bukan itu yang saya asumsikan, maka jawaban saya harus disesuaikan dengan pertanyaan yang lebih halus.
Evan Carroll
5
@Evanroll - gersh terakhir terlihat Nov 21 2015.
BSMP
5
Bagi siapa pun yang datang ke pertanyaan ini di tahun> 2017 pertimbangkan jawaban Evan stackoverflow.com/a/41608000/190234 karena menggunakan metode yang tidak tersedia ketika pertanyaan awalnya diminta dan dijawab.
Marcin Raczkowski

Jawaban:

84

Saya akan menyarankan solusi sederhana ini:

Ini adalah fungsi yang cukup sederhana yang mengembalikan string acak dengan panjang yang diberikan:

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;

Dan penggunaannya:

select random_string(15);

Contoh keluaran:

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
sumber
6
Solusi ini menggunakan nilai di kedua ujung larik karakter - 0 dan z - setengahnya sesering yang lain. Untuk distribusi karakter yang lebih merata, saya menggantinya chars[1+random()*(array_length(chars, 1)-1)]denganchars[ceil(61 * random())]
PreciousBodilyFluids
random()mendapat panggilan lengthkali (seperti di banyak solusi lainnya). Adakah cara yang lebih efisien untuk memilih dari 62 karakter setiap kali? Bagaimana kinerjanya dibandingkan md5()?
ma11hew28
Saya menemukan solusi lain yang menggunakan ORDER BY random(). Mana yang lebih cepat?
ma11hew28
1
Perlu dicatat bahwa acak dapat menggunakan erand48 yang bukan CSPRNG, Anda mungkin lebih baik hanya menggunakan pgcrypto.
Yaur
2
Jawaban yang bagus kecuali bahwa itu tidak menggunakan generator nomor acak yang aman dan oleh karena itu tidak begitu baik untuk ID sesi. Lihat: stackoverflow.com/questions/9816114/…
sudo
240

Anda dapat memperbaiki upaya awal Anda seperti ini:

SELECT md5(random()::text);

Jauh lebih sederhana daripada beberapa saran lainnya. :-)

Peter Eisentraut
sumber
16
Perhatikan bahwa ini mengembalikan string di atas "alfabet digit hex" hanya {0..9, a..f}. Mungkin tidak cukup - tergantung pada apa yang ingin Anda lakukan dengan mereka.
Laring Decidua
berapa panjang string yang dikembalikan? Apakah ada cara untuk membuatnya mengembalikan string yang lebih panjang?
andrewrk
8
Jika direpresentasikan dalam heksadesimal, panjang string MD5 selalu 32 karakter. Jika Anda menginginkan string dengan panjang 64, Anda dapat menggabungkan 2 string MD5: SELECT concat(md5(random()::text), md5(random()::text)); Dan jika Anda ingin berada di tengah-tengah (50 karakter misalnya), Anda dapat mengambil substringnya: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Jimmie Tyrrell
2
Bukan solusi yang sangat baik untuk id sesi, tidak banyak keacakan. Jawabannya juga berumur 6 tahun. Lihat ini untuk metode yang sama sekali berbeda menggunakangen_random_uuid() : lebih cepat, lebih keacakan, lebih efisien disimpan dalam database.
Evan Carroll
@ Evan jika Anda ingin lebih banyak 'keacakan' tanpa ekstensi yang Anda bisa SELECT md5(random()::text||random()::text);, atauSELECT md5(random()::text||random()::text||random()::text);
31

Berdasarkan solusi Marcin, Anda dapat melakukan ini untuk menggunakan alfabet arbitrer (dalam hal ini, semua 62 karakter alfanumerik ASCII):

SELECT array_to_string(array 
       ( 
              select substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', trunc(random() * 62)::integer + 1, 1)
              FROM   generate_series(1, 12)), '');
grourk
sumber
Lambat, tidak acak, atau seefisien penyimpanan. Bukan solusi yang sangat baik untuk id sesi, tidak banyak keacakan. Jawabannya juga berumur 6 tahun. Check out this for a totally different method using gen_random_uuid(): lebih cepat, lebih acak, lebih efisien disimpan dalam database.
Evan Carroll
23

Anda bisa mendapatkan 128 bit acak dari UUID. Ini adalah metode untuk menyelesaikan pekerjaan di PostgreSQL modern.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

Mungkin ada baiknya membaca dokumen tentang UUID juga

Tipe data uuid menyimpan Universal Unique Identifiers (UUID) seperti yang didefinisikan oleh RFC 4122, ISO / IEC 9834-8: 2005 , dan standar terkait. (Beberapa sistem merujuk ke tipe data ini sebagai pengenal unik global, atau GUID, sebagai gantinya.) Pengenal ini adalah kuantitas 128-bit yang dihasilkan oleh algoritme yang dipilih untuk membuatnya sangat tidak mungkin pengenal yang sama akan dibuat oleh orang lain di alam semesta yang diketahui menggunakan algoritma yang sama. Oleh karena itu, untuk sistem terdistribusi, pengenal ini memberikan jaminan keunikan yang lebih baik daripada generator urutan, yang hanya unik dalam satu database.

Seberapa jarang tabrakan dengan UUID, atau dapat ditebak? Dengan asumsi mereka acak,

Sekitar 100 triliun versi 4 UUID perlu dibuat agar memiliki peluang 1 banding 1 untuk satu duplikat ("tabrakan"). Kemungkinan satu tabrakan naik menjadi 50% hanya setelah 261 UUID (2,3 x 10 ^ 18 atau 2,3 ​​triliun) telah dibuat. Menghubungkan angka-angka ini ke database, dan mempertimbangkan masalah apakah kemungkinan tabrakan UUID Versi 4 dapat diabaikan, pertimbangkan file yang berisi 2,3 triliun UUID Versi 4, dengan kemungkinan 50% berisi satu benturan UUID. Ini akan berukuran 36 exabyte, dengan asumsi tidak ada data atau overhead lain, ribuan kali lebih besar dari database terbesar yang ada saat ini, yang berada di urutan petabyte. Dengan kecepatan 1 miliar UUID yang dihasilkan per detik, dibutuhkan waktu 73 tahun untuk menghasilkan UUID untuk file tersebut. Itu juga akan membutuhkan sekitar 3. 6 juta hard drive 10-terabyte atau tape cartridge untuk menyimpannya, dengan asumsi tidak ada backup atau redundansi. Membaca file pada kecepatan transfer "disk-ke-buffer" khas 1 gigabit per detik akan membutuhkan lebih dari 3000 tahun untuk satu prosesor. Karena tingkat kesalahan pembacaan drive yang tidak dapat dipulihkan adalah 1 bit per 1018 bit yang dibaca, paling banter, sementara file akan berisi sekitar 1020 bit, hanya membaca file sekali dari ujung ke ujung akan menghasilkan, setidaknya, sekitar 100 kali lebih banyak kesalahan membaca UUID daripada duplikat. Penyimpanan, jaringan, daya, dan kesalahan perangkat keras dan perangkat lunak lainnya tidak diragukan lagi akan ribuan kali lebih sering daripada masalah duplikasi UUID. kecepatan transfer 1 gigabit per detik akan membutuhkan lebih dari 3000 tahun untuk satu prosesor. Karena tingkat kesalahan pembacaan drive yang tidak dapat dipulihkan adalah 1 bit per 1018 bit yang dibaca, paling banter, sementara file akan berisi sekitar 1020 bit, hanya membaca file sekali dari ujung ke ujung akan menghasilkan, setidaknya, sekitar 100 kali lebih banyak kesalahan membaca UUID daripada duplikat. Penyimpanan, jaringan, daya, dan kesalahan perangkat keras dan perangkat lunak lainnya tidak diragukan lagi akan ribuan kali lebih sering daripada masalah duplikasi UUID. kecepatan transfer 1 gigabit per detik akan membutuhkan lebih dari 3000 tahun untuk satu prosesor. Karena tingkat kesalahan pembacaan drive yang tidak dapat dipulihkan adalah 1 bit per 1018 bit yang dibaca, paling banter, sementara file akan berisi sekitar 1020 bit, hanya membaca file sekali dari ujung ke ujung akan menghasilkan, setidaknya, sekitar 100 kali lebih banyak kesalahan membaca UUID daripada duplikat. Penyimpanan, jaringan, daya, dan kesalahan perangkat keras dan perangkat lunak lainnya tidak diragukan lagi akan ribuan kali lebih sering daripada masalah duplikasi UUID.

sumber: wikipedia

Singkatnya,

  • UUID distandarisasi.
  • gen_random_uuid()adalah 128 bit acak yang disimpan dalam 128 bit (2 ** 128 kombinasi). 0-limbah.
  • random() hanya menghasilkan 52 bit acak di PostgreSQL (2 ** 52 kombinasi).
  • md5()disimpan karena UUID adalah 128 bit, tetapi hanya dapat seacak inputnya ( 52 bit jika menggunakanrandom() )
  • md5()disimpan sebagai teks adalah 288 bit, tetapi hanya dapat seacak inputnya ( 52 bit jika menggunakanrandom() ) - lebih dari dua kali ukuran UUID dan sebagian kecil dari keacakan)
  • md5() sebagai hash, bisa sangat dioptimalkan sehingga tidak efektif banyak.
  • UUID sangat efisien untuk penyimpanan: PostgreSQL menyediakan tipe yang persis 128 bit. Tidak seperti textdan varchar, dll yang menyimpan sebagai varlenayang memiliki overhead untuk panjang string.
  • UUID bagus PostgreSQL hadir dengan beberapa operator default, casting, dan fitur.
Evan Carroll
sumber
3
Sebagian tidak benar: UUID acak yang dibuat dengan benar hanya memiliki 122 bit acak karena 4 bit digunakan untuk versi dan 2 bit untuk varian: en.wikipedia.org/wiki/…
Olivier Grégoire
2
Jika sumber tidak melakukan apa yang tertulis di sana, maka itu bukan UUID dan tidak boleh dipanggil seperti itu oleh PostgreSQL.
Olivier Grégoire
16

Saya baru-baru ini bermain dengan PostgreSQL, dan saya rasa saya telah menemukan solusi yang sedikit lebih baik, hanya menggunakan metode PostgreSQL bawaan - tanpa pl / pgsql. Satu-satunya batasan adalah saat ini hanya menghasilkan string UPCASE, atau angka, atau string huruf kecil.

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

Argumen kedua untuk generate_seriesmetode menentukan panjang string.

Marcin Raczkowski
sumber
8
Saya suka ini, tetapi ditemukan ketika saya menggunakannya pernyataan UPDATE, semua baris disetel ke kata sandi acak yang sama, bukan kata sandi unik. Saya menyelesaikan ini dengan menambahkan ID kunci utama ke dalam rumus. Saya menambahkannya ke nilai acak dan menguranginya lagi. Keacakan tidak berubah, tetapi PostgreSQL ditipu untuk menghitung ulang nilai untuk setiap baris. Berikut ini contohnya, menggunakan nama kunci utama "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg
Solusi, yang disajikan @MarkStosberg, bekerja seperti yang dia katakan, tetapi tidak seperti yang saya harapkan; data yang dihasilkan tidak sesuai dengan pola pura-pura (hanya huruf besar atau hanya angka). Saya memperbaiki dengan aritmatika yang memodulasi hasil acak: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo
4
Tidak. Anda menjawab 'Bagaimana cara membuat id sesi acak ' bukan 'Bagaimana cara membuat string acak '. Anda telah mengubah arti quesiton (dan judul), berdasarkan dua kata dalam deskripsi. Anda menjawab pertanyaan yang berbeda. dan tetap menyalahgunakan kekuatan moderasi Anda untuk mengubah arti pertanyaan.
Marcin Raczkowski
13

Silakan gunakan string_agg!

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

Saya menggunakan ini dengan MD5 untuk menghasilkan UUID juga. Saya hanya ingin nilai acak dengan lebih banyak bit daripada random ()integer.

Andrew Wolfe
sumber
Saya kira saya bisa menggabungkan random()sampai saya mendapatkan jumlah bit yang saya inginkan. Baiklah.
Andrew Wolfe
11

Meskipun tidak aktif secara default, Anda dapat mengaktifkan salah satu ekstensi inti:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Kemudian pernyataan Anda menjadi panggilan sederhana ke gen_salt () yang menghasilkan string acak:

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

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

Nomor terdepan adalah pengenal hash. Beberapa algoritme tersedia, masing-masing dengan pengenalnya sendiri:

  • md5: $ 1 $
  • bf: $ 2a $ 06 $
  • des: tidak ada pengenal
  • xdes: _J9 ..

Informasi lebih lanjut tentang ekstensi:


EDIT

Seperti yang ditunjukkan oleh Evan Carrol, mulai v9.4 Anda dapat menggunakan gen_random_uuid()

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

Gua Jefferey
sumber
Garam yang dihasilkan tampak terlalu berurutan untuk menjadi acak, bukan?
Le Droid
1
Apakah Anda mengacu pada $1$? Itu adalah pengenal tipe hash (md5 == 1), sisanya adalah nilai acak.
Gua Jefferey
Ya, itu adalah interpretasi saya yang salah, terima kasih atas ketepatannya.
Le Droid
6

Saya tidak berpikir bahwa Anda mencari string acak itu sendiri. Apa yang Anda perlukan untuk verifikasi sesi adalah string yang dijamin unik. Apakah Anda menyimpan informasi verifikasi sesi untuk audit? Dalam hal ini Anda perlu string menjadi unik di antara sesi. Saya tahu dua pendekatan yang agak sederhana:

  1. Gunakan urutan. Baik untuk digunakan pada satu database.
  2. Gunakan UUID. Unik secara universal, sangat bagus pada lingkungan terdistribusi juga.

UUID dijamin unik berdasarkan algoritma mereka untuk pembuatan; secara efektif sangat tidak mungkin bahwa Anda akan menghasilkan dua nomor identik pada mesin manapun, kapanpun, selamanya (perhatikan bahwa ini jauh lebih kuat daripada pada string acak, yang memiliki periodisitas yang jauh lebih kecil daripada UUID).

Anda perlu memuat ekstensi uuid-ossp untuk menggunakan UUID. Setelah terinstal, panggil salah satu fungsi uuid_generate_vXXX () yang tersedia di panggilan SELECT, INSERT atau UPDATE Anda. Tipe uuid adalah angka 16-byte, tetapi juga memiliki representasi string.

Patrick
sumber
Ini sepertinya nasihat yang berpotensi berbahaya. Ketika berbicara tentang kunci sesi, Anda menginginkan keunikan dan keacakan yang cukup acak secara kriptografis untuk mencegah kemungkinan yang masuk akal untuk menebaknya. Algoritme yang digunakan oleh UUID menjamin keunikan melalui mekanisme non-acak (kebanyakan), yang menimbulkan ancaman keamanan.
jmar777
6
@ jmar777 Seluruh tujuan UUID adalah bahwa mereka sulit ditebak dan sangat acak. Kecuali untuk versi v1 mereka memiliki periode yang sangat tinggi; v4 sepenuhnya acak 128-bit. Mereka digunakan dalam setiap transaksi perbankan online yang Anda lakukan. Jika mereka cukup baik untuk itu, mereka cukup baik untuk banyak hal lainnya.
Patrick
1
Nah, apa yang kamu tahu. Saya tidak menyadari bahwa itu telah dibahas dalam Versi 4 . Terima kasih telah mengoreksi saya!
jmar777
@Patrick Small nit, V4 UUIDs 122 bit acak, bukan 128.;)
Jesse
5

Parameter INTEGER menentukan panjang string. Dijamin untuk mencakup semua 62 karakter alfanum dengan probabilitas yang sama (tidak seperti beberapa solusi lain yang beredar di 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;
Laring Decidua
sumber
Lambat, tidak acak, atau seefisien penyimpanan. Bukan solusi yang sangat baik untuk id sesi, tidak banyak keacakan. Jawabannya juga berumur 6 tahun. Check out this for a totally different method using gen_random_uuid(): lebih cepat, lebih acak, lebih efisien disimpan dalam database.
Evan Carroll
3
@EvanCarroll: dalam semua keadilan, gen_random_uuid()sejauh yang saya tahu , muncul di Versi 9.4, yang dirilis pada 2014-12-18, lebih dari setahun setelah jawaban Anda menurunkan suara. Nitpick tambahan: jawabannya hanya 3 1/2 tahun :-) Tapi Anda benar, sekarang kami punya gen_random_uuid(), inilah yang harus digunakan. Karenanya saya akan memberi suara positif pada jawaban Anda.
Laring Decidua
5

@Kavius ​​merekomendasikan penggunaan pgcrypto, tetapi daripada gen_salt, bagaimana gen_random_bytes? Dan bagaimana kalau sha512bukan md5?

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

Dokumen:

F.25.5. Fungsi Data Acak

gen_random_bytes (count integer) mengembalikan bytea

Mengembalikan jumlah byte acak yang kuat secara kriptografis. Paling banyak 1024 byte dapat diekstraksi dalam satu waktu. Ini untuk menghindari terkurasnya kumpulan generator keacakan.

Jared Beck
sumber
4

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

pengguna516487
sumber
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
pengguna457226
sumber
Saya mengubahnya untuk menghapus garis miring dan tanda plus yang kadang-kadang muncul di hasil dan juga untuk menghasilkan hasil huruf besar pilih atas (ganti (ganti (substring (encode (decode (md5 (random () :: teks), 'hex) ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt