uniqid有多独特?

77

这个问题并不是在寻找解决方案,而只是一个简单的好奇。PHP中的uniqid函数有一个更高熵的标志,可以使输出“更加唯一”。这让我想知道,当more_entropy为真时,这个函数产生相同结果的概率有多大,与当它为假时相比如何呢?换句话说,启用more_entropy时uniqid有多独特,与禁用时相比如何?始终启用more_entropy有什么缺点吗?


4
如果你想要一个始终独一无二的东西,你需要实现一个全局唯一标识符(GUID)。因为在函数中只有很少的熵,所以几乎任何其他方法最终都会发生冲突。例如,使用设置了 more_entropyuniqid 只提供约92位熵(23个十六进制位)。要了解为什么这不足以确保唯一性,请参阅“生日问题” 。 - ircmaxell
@ircmaxell 感谢你指出生日问题,这个问题非常有趣。在答案中肯定应该提到它。 - Petr Peller
2
uniqid()不是哈希函数,所以生日问题并不适用于它。但它确实有其漏洞。 - Joel Mellon
@ircmaxell 那个数字从哪里来的?more_entropy大约有30位熵(九个十进制数字),微秒部分大约有20位(六个十进制数字),其余的从哪里来的?你需要从10万年的范围内选择第二个才能获得42位的熵。 - Tgr
7个回答

38

2014年3月更新:

首先,需要注意的是uniqid有点名不副实,因为它不能保证唯一ID。

根据PHP文档

警告!

此函数不会创建随机或不可预测的字符串。不得将此函数用于安全目的。使用具有加密安全性的随机函数/生成器和加密安全哈希函数创建不可预测的安全ID。

还有:

此函数不会生成具有加密安全性的令牌,事实上,如果没有传递任何其他参数,则返回值与microtime()几乎没有区别。如果需要生成具有加密安全性的令牌,请使用openssl_random_pseudo_bytes()


将more-entropy设置为true可以生成更独特的值,但执行时间会更长(尽管很小),根据文档:

如果设置为TRUE,则uniqid()将在返回值的末尾添加其他熵(使用组合线性同余生成器),从而增加结果唯一的可能性。

请注意线条增加结果唯一的可能性,而不是保证唯一性。

您可以无限地追求唯一性,到达某个点并使用任意数量的加密例程、添加salt等来增强-这取决于目的。

我建议查看主PHP主题上的评论,特别是:

http://www.php.net/manual/zh/function.uniqid.php#96898

http://www.php.net/manual/zh/function.uniqid.php#96549

http://www.php.net/manual/zh/function.uniqid.php#95001

我的建议是找出您需要唯一性的原因,是为了安全(例如添加到加密/混淆程序中)?此外,需要多独特?最后,考虑速度方面的问题。适用性将随着底层考虑而变化。


1
这些函数注释中最重要的一课是,uuid本身作为cookie/客户端可读ID传递是非常危险的标识符,但作为本地/受保护的唯一ID,它具有一些很好的用途,即速度。2.5美分。 - DrPerdix
3
我不确定这是否显而易见,但是对于任何与安全相关的事情,请不要使用uniqid(或其衍生版本)。 PHP提供了一整套安全的加密随机生成器,例如:openssl_random_pseudo_bytes。请使用适合当前工作的正确工具。 - Halcyon
1
假设没有两个文件被保存在同一微秒,Unix 微秒时间戳对于每个文件都是唯一的。 - CMCDragonkai
生成唯一ID时,虽然产生冲突的概率很小,但并非不可能。将您的uniqid生成放在do {} while(collision)中。我在生成上传文件路径时也使用这种方法。 - afilina
3
不确定为什么接受了这个答案。Unique(独特的)!= random/unpredictable(随机/不可预测的)。 - gadelat
显示剩余2条评论

20

仅当您检查它们不存在时,事物才是独特的。无论您使用什么函数来生成“随机”字符串或ID-如果您不双重检查它是否重复,则总有那种可能性.. ;)

虽然uniqid基于当前时间,但上面的警告仍然适用-这取决于您将在哪里使用这些“唯一标识符”。所有这些的线索都在于“更加独特”的说法。独特就是独特。如何拥有更多或更少独特的东西,对我来说有点困惑!

按照上述方式进行检查,并结合所有这些内容,将使您得到接近唯一的东西,但这与密钥将被用于哪里和上下文有关。希望这有所帮助!


14
“发生碰撞的机率是万分之一”和“每个程序用户同时被雷击中的机率都比碰撞的机率还要小”,这两种表述有着巨大的区别。由良好的随机数生成器和良好的种子生成的128位值非常接近于“真正”唯一,考虑到获得可证明(且无法预测)独一无二的代价极高,因此这已足够。 - Michael Borgwardt
8
为进一步阐明@Michael的观点:对于128位,你需要美国每个人(3亿)每秒生成100万个数字,大约持续一天,才能有50%的概率出现冲突... 对于512位,你需要全球每个人(70亿人)每秒生成1万亿个数字,持续10^47年才能有50%的概率出现冲突... 因此,是的,如果随机数的上限足够高并且随机数发生器足够好,则只需随机性即可模拟唯一性... - ircmaxell
5
通过将Bambleweeny 57子介子脑的逻辑电路连接到一个悬挂在强布朗运动产生器中(比如一杯热茶)的原子矢量绘图仪,从而产生少量有限的不可能性原理是显然被理解的。 - danp
1
@ircmaxell:问题在于这些数字需要真正的随机性,因此需要一个真正的随机数生成器(RNG)。除非你还有一种方法用唯一/随机的>128位值来初始化它,否则即使使用具有>128位内部状态的伪随机数生成器(PRNG)也无法模拟它。但这正是你必须解决的问题!而且任何低于这个要求的方法几乎都会导致碰撞。如果这些300M人使用他们编译器自带的糟糕的rand()函数,那么在第一次迭代中就有超过90%的碰撞几率。此外,如果你需要唯一性,即使是0.001%的碰撞几率也太高了。 - cHao
1
只有在检查它们是否已经存在时,事物才是唯一的。但这并不总是正确的。在多线程或多进程的情况下,“检查是否存在”无法防止冲突。 - truease.com
显示剩余3条评论

12
从PHP手册网站上关于该函数的讨论中可以看出:
正如下面的其他人所指出的,如果没有前缀和"added entropy",这个函数只是返回带有微秒计数器的UNIX时间戳的十六进制数;它基本上只是microtime()的十六进制形式。
此外,值得注意的是,由于microtime()只在具有gettimeofday()的系统上工作,而Windows本身没有这个函数,因此在Windows环境中,uniqid()可能只会产生单秒分辨率的UNIX时间戳。
换句话说,如果没有"more_entropy",这个函数绝对糟糕,永远不应该使用。根据文档,该标志将使用"combined linear congruential generator"来"add entropy"。嗯,这是一个相当弱的随机数生成器。所以我完全跳过这个函数,对于与安全无关的事物,我会使用基于mt_rand的东西,并使用一个好的种子,对于与安全相关的事物,我会使用SHA-256。

8
没有设置more_unique标志,它会返回带有微秒计数器的Unix时间戳,因此如果在同一微秒内进行两次调用,则它们将返回相同的“唯一”ID。
接下来的问题是,这有多大可能性。答案是,不是很大,但也不能忽略。如果您需要一个唯一的ID并且经常生成它们(或使用其他地方生成的数据),请不要指望它绝对唯一。

22
信不信由你,它实际上调用了usleep(1)来确保那种情况永远不会发生! - Eli
2
@Eli不确定是否在玩笑,但很明显这不是情况,因为我运行以下代码时会得到重复结果:for($i=0; $i<10; $i++) echo uniqid() . "\n"; - djule5
4
不,我不是在捣乱:https://github.com/php/php-src/blob/af6c11c5f060870d052a2b765dc634d9e47d0f18/ext/standard/uniqid.c#L67 你可能正在运行一个非常旧的 PHP 版本或者在一个 usleep 不存在的平台上运行? - Eli
@Eli 有趣哈哈,感谢提供资源!我正在运行PHP 5.5.11,但是我的开发机器上安装的是Windows...所以这可能就解释了一切!因此,在Windows上它肯定不是那么独特... - djule5
有趣的是,不再需要使用usleep。请参考https://github.com/php/php-src/blob/PHP-7.2.12/ext/standard/uniqid.c。 - user5542121
1
@user5542121,他们决定不再调用usleep,而是使用poll time,因为usleep“可能会导致内核调度另一个进程,从而导致大约10毫秒的暂停”~ https://github.com/php/php-src/blob/PHP-7.2.12/ext/standard/uniqid.c#L61 - x3ns

5

源代码中相关部分如下:链接

if (more_entropy) {
    uniqid = strpprintf(0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg() * 10);
} else {
    uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
}

所以more_entropy会添加9个相对随机的小数位(php_combined_lcg()返回一个值在(0,1)之间)- 这大约是29.9位的熵,最多(实际上可能更少,因为LCG不是一个加密安全的伪随机数生成器)。

1
阅读了uniqueId源代码后,可以清楚地看到其工作方式是将 1970-01-01 00:00:00 开始算起的微秒时间戳转换为一个ID。它还会等待一微秒的时间。
这意味着在以下代码中:
$uniqueId = uniqid();
$uniqueId1 = uniqid();

即使没有使用more_entropy标志,您可以确定$uniqueId != $uniqueId1,因为每个ID始终是从不同的微秒生成的。
如果ID在不同的服务器上或可能在同一服务器但不同的线程上生成,则微秒时间可能相同,因此uniqueid可能不唯一。 如果是这种情况,则可以使用more_entropy标志获取额外的29.9位熵。现在发生碰撞的概率非常小,甚至不值得检查该ID是否已存在。
如果您仅在单个服务器上生成ID而没有使用php多线程,则没有必要使用more_entropy标志,否则请使用它。 如果需要加密安全的ID,则应使用一个不错的256位RNG。

1
在SO上最好的解释。是否可能使用JavaScript获得相同的结果,即基于Unix时间戳且包含数字和字母的13个字符长度ID? - provance
@provance 这是可能的,但我不确定你为什么想这样做。问题在于你不能信任来自客户端的数据,并且如果没有使用Ajaxnode.js向服务器发出请求,就无法保证ID的唯一性。你想要实现什么? - Dan Bray

0

如果你想生成一个唯一的ID,可以尝试这个方法。

$a = time();
$b = date("Ymd");
$c = uniqid();
$d = $asec + $bsec;
$e = $sku;
$gen = $a.'_'.$b.'_'.$c.'_'.$d.'_'.$e;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接