如何在PostgreSQL中创建适合会话ID的随机字符串?


101

我想制作一个随机字符串,以用于使用PostgreSQL进行会话验证。我知道我可以使用来获得一个随机数SELECT random(),所以我尝试了SELECT md5(random()),但这是行不通的。我怎样才能做到这一点?


另一种解决方案可以在这里找到stackoverflow.com/a/13675441/398670
Craig Ringer 2012年

7
我已经对标题进行了编辑,以便现有的答案仍然十分合理,而Evan的答案也使事情变得更现代。对于内容争议,我不想锁定这个古老的问题-因此,请对所有答案进行其他修改,以适应所有问题。
蒂姆·波斯特

1
太好了,让我们看看@gersh是否可以澄清这个问题,因为关于他的初衷存在合理的分歧。如果他的初衷是我想的那样,那么许多答案都需要调整,降低票数或收回。并且,也许应该提出一个有关出于测试目的(或类似目的)生成字符串的新问题(不需要random()ness的情况)。如果不是我所假设的,那么我的答案需要迎合经过提炼的问题。
埃文·卡罗尔

5
@EvanCarroll-gersh最后一次出现于2015
。– BSMP

5
对于在2017年以上提出此问题的任何人,请考虑Evan的答案stackoverflow.com/a/41608000/190234,因为它使用了最初询问和回答问题时不可用的方法。
Marcin Raczkowski

Answers:


83

我建议这个简单的解决方案:

这是一个非常简单的函数,它返回给定长度的随机字符串:

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
此解决方案使用chars数组任一端的值-0和z-频率是其余值的一半。为了更均匀地分配字符,我chars[1+random()*(array_length(chars, 1)-1)]chars[ceil(61 * random())]
PreciousBodilyFluids

random()被称为length时间(就像许多其他解决方案一样)。有没有一种更有效的方式来每次选择62个字符?与相比,效果md5()如何?
ma11hew28 2014年

我找到了使用的另一种解决方案ORDER BY random()。哪个更快?
ma11hew28 2014年

1
值得注意的是,random可能使用不是CSPRNG的erand48,您最好只使用pgcrypto。
Yaur

2
很好的答案,除了它不使用安全的随机数生成器,因此对于会话ID来说不是很好。参见:stackoverflow.com/questions/9816114/…–
sudo

238

您可以像这样修复您的初始尝试:

SELECT md5(random()::text);

比其他一些建议简单得多。:-)


16
请注意,这仅返回“十六进制数字字母” {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);
Jimmie Tyrrell

2
会话ID的解决方案不是很好,随机性也不是很多。答案也是6岁。使用gen_random_uuid()以下方法检查完全不同的方法:更快,更随机,更有效地存储在数据库中。
埃文·卡罗尔

@Evan如果您想在没有扩展名的情况下获得更多的“随机性”,则可以SELECT md5(random()::text||random()::text);,或者SELECT md5(random()::text||random()::text||random()::text);

31

基于Marcin的解决方案,您可以执行以下操作以使用任意字母(在这种情况下,所有62个ASCII字母数字字符):

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

速度慢,不像随机性或存储效率高。会话ID的解决方案不是很好,随机性也不是很多。答案也是6岁。Check out this for a totally different method using gen_random_uuid():更快,更随机,更有效地存储在数据库中。
埃文·卡罗尔

23

您可以从UUID获得128位随机数。这是在现代PostgreSQL中完成工作的方法。

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

也许也值得阅读有关UUID的文档

uuid数据类型存储RFC 4122,ISO / IEC 9834-8:2005和相关标准定义的通用唯一标识符(UUID)。(某些系统将这种数据类型称为全局唯一标识符,或称为GUID。)此标识符是128位的数量,该数量是由一种算法生成的,该算法的选择使其他任何人都不太可能生成同一标识符在已知的宇宙中使用相同的算法。因此,对于分布式系统,这些标识符提供了比仅在单个数据库中唯一的序列生成器更好的唯一性保证。

与UUID发生碰撞的可能性有多少?假设它们是随机的

将需要生成大约100万亿个版本4 UUID,以使其有十亿分之一的可能性发生一次重复(“冲突”)。仅在生成261个UUID(2.3 x 10 ^ 18或2.3十亿个)之后,一次碰撞的机会就会增加到50%。将这些数字与数据库相关联,并考虑到版本4 UUID冲突的可能性是否可以忽略不计的问题,请考虑包含2.3亿个版本4 UUID的文件,其中有50%的机会包含一次UUID冲突。假设没有其他数据或开销,那么它的大小将为36 EB,比目前存在的最大数据库(PB级)大数千倍。以每秒生成10亿个UUID的速度,将花费73年的时间来生成文件的UUID。它还需要大约3。假设没有备份或冗余,则可以存储600万个10 TB的硬盘驱动器或盒式磁带。以每秒1 GB的典型“磁盘到缓冲区”传输速率读取文件,单个处理器将需要3000年以上的时间。由于驱动器不可恢复的读取错误率是每读取1018位最多1位,而文件将包含约1020位,因此仅从头到尾读取一次文件至少会导致大约100倍以上的错误-读取UUID而不是重复读取。与UUID复制问题相比,存储,网络,电源以及其他硬件和软件错误的发生率无疑是数千倍。一个处理器每秒1吉比特的传输速率将需要3000多年。由于驱动器不可恢复的读取错误率是每读取1018位最多1位,而文件将包含约1020位,因此仅从头到尾读取一次文件至少会导致大约100倍以上的错误-读取UUID而不是重复读取。与UUID复制问题相比,存储,网络,电源以及其他硬件和软件错误的发生率无疑是数千倍。一个处理器每秒1吉比特的传输速率将需要3000多年。由于驱动器不可恢复的读取错误率是每读取1018位最多1位,而文件将包含约1020位,因此仅从头到尾读取一次文件至少会导致大约100倍以上的错误-读取UUID而不是重复读取。无疑,存储,网络,电源以及其他硬件和软件错误的发生频率将比UUID复制问题高数千倍。

资料来源:维基百科

综上所述,

  • UUID是标准化的。
  • gen_random_uuid()是128位随机数,以128位(2 ** 128个组合)存储。零浪费。
  • random() 在PostgreSQL中仅生成52位随机数(2 ** 52种组合)。
  • md5()作为UUID存储的是128位,但只能是其输入的随机数(如果使用,则为52位random()
  • md5()以文本形式存储的是288位,但只能是其输入的随机数(如果使用,则为52位random())-超过UUID大小的两倍和随机性的一小部分)
  • md5() 作为一个哈希,可以进行优化以至于它不能有效地执行很多操作。
  • UUID的存储效率很高:PostgreSQL提供的类型恰好是128位。与text和不同varchar,等存储为varlena,对于字符串的长度有开销。
  • PostgreSQL漂亮的UUID带有一些默认的运算符,强制转换和功能。

3
晴不正确:一个适当地生成随机UUID仅具有122个随机比特,因为4个比特用于版本和2个比特用于所述变体:en.wikipedia.org/wiki/...
奥利弗格雷

2
如果源代码不执行那里写的内容,那么它不是UUID,因此PostgreSQL不应该这样调用。
OlivierGrégoire17年

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语句时,所有行都设置为相同的随机密码,而不是唯一密码。我通过在公式中添加主键ID来解决此问题。我将其添加到随机值,然后再次减去。随机性没有改变,但是PostgreSQL被诱使重新计算每一行的值。这是一个使用主键名称“ my_id”的示例: array_to_string(ARRAY(SELECT chr((65 + round((random()+my_id-my) * 25)) :: integer) FROM generate_series(1,8)), '')
Mark Stosberg

@MarkStosberg提出的解决方案按他所说的那样工作,但没有达到我的预期。产生的数据与假冒的格式不匹配(仅字母大小写或仅数字)。我通过对随机结果进行算术修正来解决此问题: array_to_string(ARRAY(SELECT chr((65 + round((random() * 25 + id) :: integer % 25 )) :: integer) FROM generate_series(1, 60)), '');
Nuno Rafael Figueiredo

4
不。您正在回答“如何生成随机会话ID ”而不是“如何生成随机字符串 ”。您已经根据描述中的两个词更改了问号(和标题)的含义。您正在回答其他问题。并继续滥用您的适度权力来改变问题的含义。
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 ..

有关扩展的更多信息:


编辑

如Evan Carrol所述,从v9.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。安装后,在SELECT,INSERT或UPDATE调用中调用任何可用的uuid_generate_vXXX()函数。uuid类型是一个16字节的数字,但它也具有字符串表示形式。


这似乎是潜在的危险建议。当涉及会话密钥时,您需要具有足够的加密随机性的唯一性随机性,以排除猜测它的任何合理机会。UUID使用的算法通过非随机(主要是)机制来保证唯一性,这构成了安全威胁。
jmar777

6
@ jmar777 UUID的全部目的是难以猜测并且高度随机。除v1版本外,它们具有很高的周期性。v4是完全128位随机的。您进行的每项网上银行交易都使用它们。如果它们足以胜任,那么它们几乎可以胜任其他任何事情。
Patrick

1
好吧,你知道什么。我没有意识到在版本4中已解决该问题。感谢您纠正我!
jmar777

@Patrick Small nit,V4 UUID是随机的122位,而不是128位。;)
Jesse,

5

INTEGER参数定义字符串的长度。保证以相等的概率覆盖所有62个字母数字字符(与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;

速度慢,不像随机性或存储效率高。会话ID的解决方案不是很好,随机性也不是很多。答案也是6岁。Check out this for a totally different method using gen_random_uuid():更快,更随机,更有效地存储在数据库中。
埃文·卡罗尔

3
@EvanCarroll:公平地说,gen_random_uuid()据我所知,它是在9.4版中发布的,该版本于2014-12-18发布,比您的答案低了一年多。额外的nitpick:答案只有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(计数整数)返回bytea

返回计数加密强度高的随机字节。一次最多可以提取1024个字节。这是为了避免耗尽随机性生成器池。



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

我修改它以删除有时出现在结果中的正斜杠和加号,并生成大写结果,请选择upper(replace(replace(substring(encode(decode(md5(random():: text), ')||解码(md5(random():: text),'hex'),'base64'),0,10),'/','A'),'+','Z')));
Seun Matt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.