Как создать случайную строку, подходящую для идентификатора сеанса в PostgreSQL?


101

Я хотел бы создать случайную строку для использования при проверке сеанса с помощью PostgreSQL. Я знаю, что могу получить случайное число SELECT random(), поэтому попробовал SELECT md5(random()), но это не сработало. Как я могу это сделать?


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

7
Я отредактировал заголовок, чтобы существующие ответы по-прежнему имели совершенно разумный смысл, и ответ Эвана также привносит немного более современные подходы. Я не хочу блокировать этот извечный вопрос из-за спора о содержании - поэтому давайте внесем какие-либо дополнительные правки, соответствующие всем ответам, пожалуйста.
Тим Пост

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

5
@EvanCarroll - Герш последний раз видели 21 Nov 2015
БСМП

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
Стоит отметить, что random может использовать erand48, который не является CSPRNG, вам, вероятно, лучше просто использовать pgcrypto.
Yaur

2
Хороший ответ, за исключением того, что он не использует безопасный генератор случайных чисел и, следовательно, не очень хорош для идентификаторов сеансов. См .: stackoverflow.com/questions/9816114/…
sudo

240

Вы можете исправить свою первоначальную попытку следующим образом:

SELECT md5(random()::text);

Намного проще, чем некоторые другие предложения. :-)


17
Обратите внимание, что это возвращает строки только в "шестнадцатеричном алфавите" {0..9, a..f}. Может быть недостаточно - зависит от того, что вы хотите с ними делать.
Laryx Decidua

какова длина возвращаемой строки? Есть ли способ заставить его возвращать более длинную строку?
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 триллионов UUID версии 4, чтобы вероятность одного дубликата («коллизия») составляла 1 из миллиарда. Вероятность одной коллизии возрастает до 50% только после того, как будет сгенерирован 261 UUID (2,3 x 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.

источник: википедия

В итоге,

  • 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 бит. В отличие от textand и varcharт. Д., Которые хранятся как a, у varlenaкоторого есть накладные расходы на длину строки.
  • Отличный UUID PostgreSQL поставляется с некоторыми операторами, приведениями и функциями по умолчанию.

3
Частично неверно: правильно сгенерированный случайный UUID имеет только 122 случайных бита, поскольку 4 бита используются для версии и 2 бита для варианта: en.wikipedia.org/wiki/…
Оливье Грегуар

2
Если источник не выполняет то, что там написано, значит, это не UUID, и PostgreSQL не должен вызывать его как таковой.
Olivier Grégoire

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
Нет. Вы отвечаете на вопрос «Как создать случайный идентификатор сеанса », а не на вопрос «Как создать случайную строку ». Вы изменили значение вопроса (и заголовка) на основе двух слов в описании. Вы отвечаете на другой вопрос. и продолжайте злоупотреблять своей модерацией, чтобы изменить значение вопроса.
Марцин Рачковски

14

Пожалуйста, используйте 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 ..

Подробнее о расширениях:


РЕДАКТИРОВАТЬ

Как указал Эван Кэррол, начиная с версии 9.4 вы можете использовать gen_random_uuid()

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


Сгенерированные соли кажутся слишком последовательными, чтобы быть действительно случайными, не так ли?
Le Droid

1
Вы имеете в виду $1$ ? Это идентификатор типа хэша (md5 == 1), остальное - случайное значение.
Jefferey Cave

Да, это была моя ошибочная интерпретация, спасибо за точность.
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-битный случайный. Они используются при каждой транзакции онлайн-банкинга, которую вы выполняете. Если они достаточно хороши для этого, они достаточно хороши почти для всего остального.
Патрик

1
Ну, что же вы знаете. Я не понимал, что это было решено в версии 4 . Спасибо, что поправили меня!
jmar777

@Patrick Small nit, UUID V4 - это 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 (count integer) возвращает bytea

Возвращает подсчет криптостойких случайных байтов. За один раз можно извлечь не более 1024 байтов. Это сделано для того, чтобы избежать истощения пула генераторов случайности.



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

Я исправляю его, чтобы удалить косую черту и знак плюса, которые иногда появляются в результате, а также для генерации результата в верхнем регистре выберите верхний (replace (replace (substring (encode (decode (md5 (random ()) :: text), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.