Как создать случайную строку, подходящую для идентификатора сеанса в 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 байтов. Это сделано для того, чтобы избежать истощения пула генераторов случайности.

Джаред Бек
источник
4

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

user516487
источник
2
select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
user457226
источник
Я исправляю его, чтобы удалить косую черту и знак плюса, которые иногда появляются в результате, а также для генерации результата в верхнем регистре выберите верхний (replace (replace (substring (encode (decode (md5 (random ()) :: text), 'hex ') || decode (md5 (random () :: text),' hex '),' base64 '), 0, 10),' / ',' A '),' + ',' Z '));
Seun Matt