【问题标题】:How do you create a random string that's suitable for a session ID in PostgreSQL?如何在 PostgreSQL 中创建适合会话 ID 的随机字符串?
【发布时间】:2010-10-19 17:05:02
【问题描述】:

我想制作一个随机字符串,用于使用 PostgreSQL 进行会话验证。我知道我可以用SELECT random() 得到一个随机数,所以我尝试了SELECT md5(random()),但这不起作用。我该怎么做?

【问题讨论】:

  • 另一个解决方案可以在这里找到stackoverflow.com/a/13675441/398670
  • 我已经编辑了标题,以便现有的答案仍然非常有意义,而 Evan 的答案也让事情变得更现代一些。我不想因为内容争议而锁定这个古老的问题 - 所以让我们进行任何其他编辑以适应所有答案。
  • 酷,让我们看看@gersh 是否可以澄清这个问题,因为对于他的初衷存在合理的分歧。如果他的初衷是我认为的那样,那么其中许多答案都需要调整、否决或撤回。而且,也许应该提出一个关于为测试目的(或类似目的)生成字符串的新问题(其中random()ness 不是必需的)。如果这不是我的假设,那么我的答案需要迎合精致的问题。
  • @EvanCarroll - gersh 最后一次出现是在 2015 年 11 月 21 日。
  • 对于 2017 年提出此问题的任何人,请考虑 Evan 的回答 stackoverflow.com/a/41608000/190234,因为它使用了最初询问和回答问题时不可用的方法。

标签: postgresql random


【解决方案1】:

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

SELECT md5(random()::text);

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

【讨论】:

  • 请注意,这仅返回“十六进制数字字母表”{0..9,a..f} 上的字符串。可能还不够——取决于你想用它们做什么。
  • 返回字符串的长度是多少?有没有办法让它返回更长的字符串?
  • 以十六进制表示时,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);
  • 对于会话 ID 来说不是一个很好的解决方案,随机性不大。答案也是6岁。 Check out this for a totally different method using gen_random_uuid():更快、更随机、更高效地存储在数据库中。
  • 随机性增加三倍,低效存储大小和 md5 开销增加三倍。
【解决方案2】:

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

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

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)

【讨论】:

  • 此解决方案使用 chars 数组两端的值 - 0 和 z - 使用频率是其余值的一半。为了使字符分布更均匀,我将chars[1+random()*(array_length(chars, 1)-1)] 替换为chars[ceil(61 * random())]
  • random() 被称为 length 次(就像在许多其他解决方案中一样)。每次从 62 个字符中选择是否有更有效的方法?与md5() 相比,它的表现如何?
  • 我发现another solution 使用了ORDER BY random()。哪个更快?
  • 值得注意的是,random 可能使用不是 CSPRNG 的 erand48,你最好只使用 pgcrypto。
  • 很好的答案,只是它不使用安全的随机数生成器,因此对会话 ID 不太好。见:stackoverflow.com/questions/9816114/…
【解决方案3】:

基于 Marcin 的解决方案,您可以使用任意字母表(在本例中为所有 62 个 ASCII 字母数字字符):

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

【讨论】:

【解决方案4】:

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

CREATE EXTENSION pgcrypto;
SELECT gen_random_uuid();

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

可能是worth reading the docs on UUID too

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

与 UUID 的冲突有多罕见或可猜测?假设它们是随机的,

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

来源:wikipedia

总之,

  • UUID 已标准化。
  • gen_random_uuid() 是 128 位随机存储在 128 位(2**128 组合)中。 0-浪费。
  • random() only generates 52 bits of random in PostgreSQL (2**52 combinations).
  • md5() 存储为 UUID 是 128 位,但它只能与输入一样随机 (52 bits if using random())
  • md5() 存储为文本是 288 位,但它只能像其输入一样随机 (52 bits if using random()) - 超过 UUID 大小的两倍和随机性的一小部分)
  • md5() 作为一个哈希值,可以优化到不能有效地做很多事情。
  • UUID 的存储效率很高:PostgreSQL 提供了一种正好是 128 位的类型。与 textvarchar 等存储为 varlena 不同,这对字符串的长度有开销。
  • PostgreSQL 漂亮的 UUID 带有一些默认运算符、强制转换和功能。

【讨论】:

  • 部分不正确:正确生成的随机 UUID 只有 122 个随机位,因为 4 位用于版本,2 位用于变体:en.wikipedia.org/wiki/…
  • 如果源不执行那里写的操作,那么它不是 UUID,不应被 PostgreSQL 调用。
  • 请注意,修剪 + 编码(例如 base64)会消除熵,并最终导致冲突。
  • @Antwan base64 是一种可逆编码,不会删除任何熵。
  • @jbg 修剪确实
【解决方案5】:

我最近在玩 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 方法的第二个参数指示字符串的长度。

【讨论】:

  • 我喜欢这个,但发现当我使用它作为一个 UPDATE 语句时,所有行都设置为相同的随机密码而不是唯一密码。我通过将主键 ID 添加到公式中解决了这个问题。我将它添加到随机值并再次减去它。随机性没有改变,但 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)), '');
  • 没有。您正在回答“我如何生成随机 会话 id”而不是“我如何生成随机 字符串”。您已根据描述中的两个词更改了问题(和标题)的含义。你在回答不同的问题。并继续滥用你的审核权力来改变问题的含义。
【解决方案6】:

请使用string_agg

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

我也将它与 MD5 一起使用来生成 UUID。我只想要一个比random () 整数更多位的随机值。

【讨论】:

  • 我想我可以连接 random() 直到我得到我想要的位数。哦,好吧。
【解决方案7】:

虽然默认情况下未激活,但您可以激活其中一个核心扩展:

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

【讨论】:

  • 生成的盐似乎太连续而不能真正随机,不是吗?
  • 你指的是$1$吗?那是一个哈希类型标识符(md5==1),其余的是随机值。
  • 是的,那是我的错误解释,感谢您的精确。
【解决方案8】:

@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 可以一次提取字节。这是为了避免排空 随机生成器池。

【讨论】:

  • 我想知道如果您正在散列的基础数据是随机的,sha512 是否会带来任何好处。假设随机性,任何编码为字符串的东西都应该足够,并且计算复杂度越低越好(例如 base64 编码?)。 -- 抱歉,旧评论,但在与工作人员讨论时提出
  • @JeffereyCave 已经 6 年了,所以我不记得上下文了,但看起来 OP 在询问 session idssha512 会有比md5 长4 倍的优势,因此collision attack 会更困难?
  • 我同意越长越好...我在想您的答案在select gen_random_bytes(1024) 上已经完成了......是的...... 6 年。与同事的非常随意的谈话提出了这一点。
【解决方案9】:

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():更快、更随机、更高效地存储在数据库中。
  • @EvanCarroll:平心而论,据我所知,gen_random_uuid() 出现在 9.4 版中,该版本于 2014 年 12 月 18 日发布,在您投反对票的答案一年多之后。额外的挑剔:答案只有 3 1/2 岁:-) 但你是对的,现在我们有 gen_random_uuid(),这就是应该使用的。因此,我会赞成你的回答。
【解决方案10】:

我不认为您正在寻找随机字符串本身。会话验证需要的是一个保证唯一的字符串。您是否存储会话验证信息以供审核?在这种情况下,您需要字符串在会话之间是唯一的。我知道两种相当简单的方法:

  1. 使用序列。适合在单个数据库上使用。
  2. 使用 UUID。普遍独一无二,在分布式环境中也非常出色。

UUID 凭借其生成算法保证是唯一的;实际上,非常不可能在任何机器上、任何时候、任何时候生成两个相同的数字(请注意,这比随机字符串强得多,随机字符串具有远小于 UUID 的周期性)。

您需要加载 uuid-ossp 扩展才能使用 UUID。安装后,在 SELECT、INSERT 或 UPDATE 调用中调用任何可用的 uuid_generate_vXXX() 函数。 uuid 类型是一个 16 字节的数字,但它也有一个字符串表示。

【讨论】:

  • 这似乎是有潜在危险的建议。当谈到会话密钥时,您需要唯一性在密码学上足够随机的随机性,以排除任何合理的猜测机会。 UUID 使用的算法通过非随机(主要)机制保证唯一性,这会带来安全威胁。
  • @jmar777 UUID 的全部目的是它们难以猜测且高度随机。除了 v1 版本外,它们的周期性非常高; v4 完全是 128 位随机的。它们被用于您进行的每一笔网上银行交易。如果它们足够好,那么它们几乎可以胜任其他任何事情。
  • 好吧,你知道什么。我没有意识到 Version 4 已经解决了这个问题。谢谢指正!
  • @Patrick Small nit,V4 UUID 是 122 位随机的,而不是 128 位。;)
【解决方案11】:

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

【讨论】:

    【解决方案12】:
    create extension if not exists pgcrypto;
    

    然后

    SELECT encode(gen_random_bytes(20),'base64')
    

    甚至

    SELECT encode(gen_random_bytes(20),'hex')
    

    这是针对 20 字节 = 160 位的随机性(例如,只要 sha1)。

    【讨论】:

      【解决方案13】:
      select encode(decode(md5(random()::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64')
      

      【讨论】:

      • 我修改它以删除有时出现在结果中的正斜杠和加号,并生成大写结果 select upper(replace(replace(substring(encode(decode(md5(random( )::text), 'hex')||decode(md5(random()::text), 'hex'), 'base64'), 0, 10), '/', 'A'), '+ ', 'Z'));
      • 你不能用TRANSLATE()代替两个REPLACE()s吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-10
      • 1970-01-01
      • 2016-07-31
      • 2010-10-01
      • 1970-01-01
      • 2010-10-25
      相关资源
      最近更新 更多