Як створити випадкову рядок, який підходить для ідентифікатора сесії в PostgreSQL?


101

Я хотів би зробити випадкову рядок для використання в перевірці сеансу за допомогою PostgreSQL. Я знаю, що можу отримати випадкове число SELECT random(), тому я спробував SELECT md5(random()), але це не працює. Як я можу це зробити?


Ще одне рішення можна знайти тут stackoverflow.com/a/13675441/398670
Крейг Рінгер

7
Я відредагував заголовок так, що наявні відповіді все ще мають абсолютно хороший сенс, а відповідь Евана приносить і речі трохи більш сучасні. Я не хочу закривати це вікове запитання для змістовної суперечки - тому давайте внесемо будь-які додаткові зміни, що містять усі відповіді, будь ласка.
Тім Пост

1
Класно, давайте подивимось, чи може @gersh уточнити це питання, оскільки існує законна незгода щодо його первісного наміру. Якщо його первісний намір є таким, яким я припускаю, це було, багато з цих відповідей потребують коригування, скасування чи відкликання. І, можливо, слід поставити нове запитання щодо генерування рядків для тестування (або подібне) (там, де random()не потрібно). Якщо це не те, що я припускаю, то мою відповідь потрібно надати замість уточненого питання.
Еван Керролл

5
@EvanCarroll - gersh востаннє бачились 21 листопада 2015 року
BSMP

5
Для всіх, хто приходить на це питання у 2017 році, розглянемо відповідь Евана stackoverflow.com/a/41608000/190234, оскільки він використовує методи, недоступні, коли запитання було спочатку задано і відповіли.
Marcin Raczkowski

Відповіді:


84

Я б запропонував таке просте рішення:

Це досить проста функція, яка повертає випадкову рядок заданої довжини:

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;

І використання:

select random_string(15);

Приклад виводу:

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
Це рішення використовує значення на будь-якому кінці масиву символів - 0 і z - наполовину частіше, ніж решта. Для більш рівномірного розподілу персонажів я замінив chars[1+random()*(array_length(chars, 1)-1)]наchars[ceil(61 * random())]
PreciousBodilyFluids

random()називається lengthчасом (як у багатьох інших рішеннях). Чи є більш ефективний спосіб вибирати 62 символи кожен раз? Як це відбувається в порівнянні з md5()?
ma11hew28

Я знайшов інше рішення, яке використовує ORDER BY random(). Що швидше?
ma11hew28

1
Варто відзначити, що випадково може використовуватися erand48, яка не є CSPRNG, вам, ймовірно, краще просто використовувати pgcrypto.
Яур

2
Хороша відповідь, за винятком того, що він не використовує захищений генератор випадкових чисел, і тому не настільки гарний для ідентифікаторів сеансу. Дивіться: stackoverflow.com/questions/9816114/…
судо

240

Ви можете виправити свою первинну спробу так:

SELECT md5(random()::text);

Набагато простіше, ніж деякі інші пропозиції. :-)


16
Зауважте, що це повертає рядки лише за "шістнадцятковими цифрами алфавіту" {0..9, a..f}. Може бути недостатньою - залежить від того, що ви хочете зробити з ними.
Ларикс Декідуа

яка довжина повернутого рядка? Чи є спосіб змусити його повернути довший рядок?
andrewrk

8
Якщо це зображено в шістнадцятковій формі, довжина рядка MD5 завжди становить 32 символи. Якщо ви хочете рядок довжиною 64, ви могли б поєднати 2 рядки MD5: SELECT concat(md5(random()::text), md5(random()::text)); І якщо ви хочете десь посередині (наприклад, 50 символів), ви можете взяти підрядку: SELECT substr(concat(md5(random()::text), md5(random()::text)), 0, 50);
Джиммі Тіррелл

2
Не дуже вдале рішення для ідентифікаторів сеансу, не так багато випадкових випадків. Відповідь також 6 років. Перевірте це для абсолютно іншого методу, використовуючиgen_random_uuid() : швидше, більше випадковості, ефективніше зберігається в базі даних.
Еван Керролл

@Evan, якщо ви хочете більше "випадковості" без розширення, ви можете SELECT md5(random()::text||random()::text);, абоSELECT md5(random()::text||random()::text||random()::text);

31

Спираючись на рішення Марсіна, ви можете зробити це для використання довільного алфавіту (у цьому випадку всі 62 буквено-цифрові символи ASCII):

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

Повільний, не настільки випадковий або настільки ефективний для зберігання. Не дуже вдале рішення для ідентифікаторів сеансу, не так багато випадкових випадків. Відповідь також 6 років. Check out this for a totally different method using gen_random_uuid(): швидше, більше випадковості, ефективніше зберігається в базі даних.
Еван Керролл

23

Ви можете отримати 128 біт випадкових випадків від UUID. Це метод зробити роботу в сучасному PostgreSQL.

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

Можливо, варто також прочитати документи на UUID

Тип даних uuid зберігає універсально унікальні ідентифікатори (UUID), визначені RFC 4122, ISO / IEC 9834-8: 2005 та відповідними стандартами. (Деякі системи називають цей тип даних як глобальний унікальний ідентифікатор, або GUID, замість цього.) Цей ідентифікатор - це 128-розрядна кількість, яка генерується алгоритмом, вибраним, щоб зробити дуже малоймовірним, що той самий ідентифікатор буде генерований будь-ким іншим у відомому Всесвіті, використовуючи той самий алгоритм. Тому для розподілених систем ці ідентифікатори дають кращу гарантію унікальності, ніж генератори послідовностей, які є унікальними лише в одній базі даних.

Наскільки рідкісним є зіткнення з UUID або можливим? Якщо припустити, що вони випадкові,

Близько 100 трлн. Версій 4 UUID повинні бути створені, щоб мати шанс 1 на мільярд одного дубліката ("зіткнення"). Шанс одного зіткнення зростає до 50% лише після створення 261 UUID (2,3 х 10 ^ 18 або 2,3 квінтільйона). Відносячи ці номери до баз даних та розглядаючи питання про те, чи є ймовірність зіткнення UUID версії 4 незначною, розглянемо файл, що містить 2.3 квінтиліона UUID версії 4, з 50% шансом вмістити одне зіткнення UUID. Він буде розміром 36 екзабайтів, якщо не мати інших даних або накладних витрат, у тисячі разів більших, ніж найбільші бази даних, що існують зараз, на порядок петабайт. При швидкості 1 мільярда UUID, що генерується в секунду, на створення UUID для файлу знадобиться 73 роки. Також знадобиться близько 3. 6 мільйонів 10-терабайтних жорстких дисків або стрічкових картриджів для їх зберігання, не передбачаючи резервного копіювання або надмірності. Читання файлу з типовою швидкістю передачі «диск-буфер» 1 гігабіт в секунду вимагатиме більше 3000 років для одного процесора. Оскільки коефіцієнт помилок читання дисків, що не можна отримати, становить 1 біт на 1018 біт прочитаного, в кращому випадку, тоді як файл міститиме близько 1020 біт, просто зчитування файлу один раз від кінця до кінця призведе, щонайменше, приблизно в 100 разів більше неправильно- читати UUID, ніж дублікати. Зберігання, мережа, живлення та інші апаратні та програмні помилки, безсумнівно, були б у тисячі разів частішими, ніж проблеми з дублюванням UUID. Швидкість передачі 1 гігабіт в секунду вимагала б більше 3000 років для одного процесора. Оскільки коефіцієнт помилок читання дисків, що не можна отримати, становить 1 біт на 1018 біт прочитаного, в кращому випадку, тоді як файл міститиме близько 1020 біт, просто зчитування файлу один раз від кінця до кінця призведе, щонайменше, приблизно в 100 разів більше неправильно- читати UUID, ніж дублікати. Зберігання, мережа, живлення та інші апаратні та програмні помилки, безсумнівно, були б у тисячі разів частішими, ніж проблеми з дублюванням UUID. Швидкість передачі 1 гігабіт в секунду вимагала б більше 3000 років для одного процесора. Оскільки коефіцієнт помилок читання дисків, що не можна отримати, становить 1 біт на 1018 біт прочитаного, в кращому випадку, тоді як файл міститиме близько 1020 біт, просто зчитування файлу один раз від кінця до кінця призведе, щонайменше, приблизно в 100 разів більше неправильно- читати UUID, ніж дублікати. Зберігання, мережа, живлення та інші апаратні та програмні помилки, безсумнівно, були б у тисячі разів частішими, ніж проблеми з дублюванням UUID.

джерело: wikipedia

Підсумовуючи це,

  • UUID є стандартизованим.
  • gen_random_uuid()- це 128 біт випадкових даних, що зберігаються в 128 бітах (2 ** 128 комбінацій). 0-відходи.
  • random() генерує лише 52 біти випадкових випадків у PostgreSQL (2 ** 52 комбінації).
  • md5()зберігається як UUID - 128 біт, але він може бути настільки ж випадковим, як і його вхід ( 52 біти при використанніrandom() )
  • md5()текст зберігається як 288 біт, але він може бути лише випадковим, ніж його вхід ( 52 біти при використанніrandom() ) - більше ніж удвічі більший розмір UUID і частка випадковості)
  • md5() як хеш, може бути настільки оптимізований, що він ефективно не робить багато.
  • UUID є високоефективним для зберігання: PostgreSQL надає тип, рівний 128 бітам. На відміну від textі varcharт. Д., Які зберігаються як a, varlenaякі мають накладні витрати на довжину рядка.
  • PostgreSQL чудовий UUID постачається з деякими операторами за замовчуванням, кастингами та функціями.

3
Частково неправильно: Правильно генерований випадковий UUID має лише 122 випадкових біта, оскільки для версії використовуються 4 біти, а варіант - 2 біти: en.wikipedia.org/wiki/…
Олів'є Грегоар

2
Якщо джерело не робить те, що там написано, це не UUID і не повинно називатися PostgreSQL як таке.
Олів'є Грегоар

16

Нещодавно я грав з PostgreSQL, і, думаю, знайшов трохи краще рішення, використовуючи лише вбудовані методи PostgreSQL - без pl / pgsql. Єдине обмеження полягає в тому, що він наразі генерує лише рядки UPCASE або числа або рядки з малі регістри.

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_seriesметоду диктує довжину рядка.


8
Мені це подобається, але коли я використовував це оператор UPDATE, усі рядки були встановлені на один і той же випадковий пароль замість унікальних паролів. Я вирішив це, додавши ідентифікатор первинного ключа у формулу. Я додаю його до випадкового значення і знову віднімаю. Випадковість не змінюється, але PostgreSQL обманюється на повторне обчислення значень для кожного рядка. Ось приклад, використовуючи назву первинного ключа "my_id": array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Марк Стосберг

Рішення, яке представив @MarkStosberg, працювало так, як він сказав, але не так, як я очікував; отримані дані не відповідали передбачуваному шаблону (лише букви букв або просто цифри). Я зафіксував арифметичне модулювання випадкового результату: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Нуно Рафаель Фігейредо

4
Ні. Ви відповідаєте на тему "Як створити ідентифікатор випадкового сеансу ", а не "Як створити випадковий рядок ". Ви змінили значення запитання (і заголовка) на основі двох слів в описі. Ви відповідаєте на інше запитання. і продовжуйте зловживати своїми модераторними повноваженнями, щоб змінити значення.
Marcin Raczkowski

13

Будь ласка, використовуйте string_agg!

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

Я використовую це з MD5 для створення також UUID. Я просто хочу, щоб випадкове значення було більше бітів, ніж random ()ціле число.


Я думаю, що я міг би просто об'єднатись, random()поки не отримаю потрібну кількість біт. Що ж, добре.
Ендрю Вулф

11

Хоча за умовчанням він не активний, ви можете активувати одне з основних розширень:

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Тоді ваш вислів стає простим викликом до gen_salt (), який генерує випадкову рядок:

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

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

Основний номер - хеш-ідентифікатор. Доступно кілька алгоритмів, кожен з яких має свій ідентифікатор:

  • md5: $ 1 $
  • bf: $ 2a $ 06 $
  • des: немає ідентифікатора
  • xdes: _J9 ..

Більше інформації про розширення:


EDIT

Як вказав Еван Керрол, з версії 9.4 ви можете використовувати gen_random_uuid()

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


Утворені солі здаються занадто послідовними, щоб бути справді випадковими, чи не так?
Le Droid

1
Ви маєте в виду на $1$? Це ідентифікатор типу хешу (md5 == 1), решта - рандомізоване значення.
Джеффірі Печера

Так, це було моє помилкове тлумачення, дякую за точність.
Le Droid

6

Я не думаю, що ти сам шукаєш випадкову рядок. Що вам знадобиться для перевірки сеансу - це рядок, який гарантовано буде унікальним. Чи зберігаєте ви інформацію про перевірку сеансу для аудиту? У такому випадку вам потрібно, щоб рядок був унікальним між сеансами. Я знаю два досить прості підходи:

  1. Використовуйте послідовність. Підходить для використання в одній базі даних.
  2. Використовуйте UUID. Універсальний унікальний, тому хороший і в розподілених середовищах.

UUID , які гарантовано бути унікальним в силу свого алгоритму генерації; ефективно це вкрай малоймовірно , що ви будете генерувати два однакових номера на будь-якій машині, в будь-який час, коли - небудь (зверніть увагу , що це набагато сильніше , ніж від випадкових рядків, які мають набагато меншу періодичність , ніж UUID , ).

Вам потрібно завантажити розширення uuid-ossp, щоб використовувати UUID. Після встановлення зателефонуйте до будь-якої з доступних функцій uuid_generate_vXXX () у ваших викликах SELECT, INSERT або UPDATE. Тип uuid - це 16-байтне число, але воно також має рядкове подання.


Це здається потенційно небезпечною порадою. Що стосується ключів сеансу, ви хочете, щоб унікальність і випадковість були досить криптографічними, щоб не допустити будь-яких розумних шансів здогадатися про це. Алгоритми, що використовуються UUID, гарантують унікальність не випадкових (в основному) механізмів, що створює загрозу безпеці.
jmar777

6
@ jmar777 Вся мета UUID полягає в тому, що їх важко здогадатися і вельми випадкові. За винятком версії v1, вони мають дуже високу періодичність; v4 повністю 128-бітний випадковий. Вони використовуються у будь-якій операції з онлайн-банкінгу. Якщо вони досить хороші для цього, вони досить добрі для майже нічого іншого.
Patrick

1
Ну, що ти знаєш. Я не усвідомлював, що було розглянуто у версії 4 . Дякуємо, що виправили мене!
jmar777

@Patrick Маленька нітка, V4 UUID - це 122 біти випадкових випадків, а не 128.)
Джессі

5

Параметр INTEGER визначає довжину рядка. Гарантоване охоплення всіх 62 символів букв з однаковою ймовірністю (на відміну від деяких інших рішень, що плавають в Інтернеті).

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;

Повільний, не настільки випадковий або настільки ефективний для зберігання. Не дуже вдале рішення для ідентифікаторів сеансу, не так багато випадкових випадків. Відповідь також 6 років. Check out this for a totally different method using gen_random_uuid(): швидше, більше випадковості, ефективніше зберігається в базі даних.
Еван Керролл

3
@EvanCarroll: якщо чесно, я gen_random_uuid()з'явився у версії 9.4, наскільки я можу сказати, яка була випущена 2014-12-18, більше року після відповіді, яку ви відкликали. Додаткова нитка: відповідь лише 3 1/2 років :-) Але ти маєш рацію, тепер, коли ми маємо gen_random_uuid(), це те, що слід використовувати. Тому я підтримаю вашу відповідь.
Laryx Decidua

5

@Kavius ​​рекомендував використовувати pgcrypto, але замість gen_saltчого gen_random_bytes? А як щодо sha512цього md5?

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

Документи:

F.25.5. Функції випадкових даних

gen_random_bytes (кількість цілих чисел) повертає байт

Повертає підрахунок криптографічно сильних випадкових байтів. За один раз можна витягти 1024 байти. Це дозволяє уникнути зливання пулу генераторів випадковості.



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

Я змінюю його, щоб видалити знак нахилу та нахил плюс, який іноді з'являється в результаті, а також для створення верхнього регістру вибору верхнього (заміна (замінити (замінити підрядку (кодування (декодування (md5 (випадковий (): :: текст)))) ') || декодування (md5 (випадковий () :: текст),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Сеун Метт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.