多次调用uniqid()函数结果不唯一

5

我有一个有趣的例子,当在XAMPP本地主机上多次重复调用uniqid()时,它并没有生成唯一的数字。唯一ID会在5-20次之间被重复使用,然后神奇地改变。

然而,有趣的是,这段代码在我们的生产服务器上完美运行。

所以这是我正在做的事情:我正在创建一个包装器,当点击时,通过一个简单的JavaScript函数隐藏/显示div中的子内容。由于可隐藏的div是动态生成的,所以它被PHP生成的唯一ID所引用。

以下是问题的示例:

// Replace something like '[element] => <newline> (' with <a href="javascript:toggleDisplay('[unique id]');">...</a><div id="[unique id]" style="display: none;">   
$out = preg_replace_callback(
        $regex,
        function ($matches) {
            $id = uniqid();
            return $matches[1] . "<a class='debug' href='javascript:toggleDisplay(\"" . $id . "\");'>" . $matches[2] . "</a>" . "<div id='" . $id . "' style='display: none'>";
        }, $out
    );

以下是 JavaScript 函数的代码(这样你就能看到我在做什么,它完美地运行):
<script language="Javascript">
    function toggleDisplay(id) {
        document.getElementById(id).style.display = (document.getElementById(id).style.display == "block") ? "none" : "block";
    }
</script>'

问题在于所有的输出div都具有相同的唯一id(!!),聚类数量在5-15之间,因此JavaScript不知道引用哪个div。
我发现了一些东西:如果我像这样做 $id = uniqid() . rand(10000,99999) 而不是只有 $id = uniqid(),那么代码再次按预期工作。所以我非常确定问题在于uniqid()并没有真正生成一个唯一的ID,考虑到我没有覆盖或重复使用$id变量。
另一个有趣的事情是:如果我将microtime()与uniqid()一起回显,则只有在microtime()更改时,uniqid()才会更改。对我而言,这感觉像是一个线索。
那么我的问题是:为什么uniqid()有时只能生成唯一的ID?即使microtime()相同,uniqid()是否应该生成一个唯一的数字?这种行为是否有记录或广为人知?还是我还有其他遗漏的内容?
我问这个问题是因为我对使用uniqid()感到不舒服,因为我不理解其核心行为。
非常感谢您的任何见解。谢谢。

uniqid的文档页面上,有一段红色文字说明它不会生成随机或不可预测的字符串。生成这样的字符串的选项包括使用openssl_random_pseudo_bytes、使用放置独占锁的文件生成计数器(模拟MySQL的auto_increment),或者通过javascript生成唯一数字(再次使用auto_increment),或者使用MySQL通过SELECT UUID()获取UUID等。 - N.B.
1
uniqid 的文档中,还有一个可选的第二个参数用于测试“更多熵”。可以使用 uniqid("", true); 进行测试。 - John McMahon
1
我认为他并不期望它们是随机或不可预测的,只是不应该有重复。 - Barmar
2个回答

10
uniqid()函数返回的结果并不保证唯一性,你使用microtime()进行调查也可以看出原因。根据uniqid()函数的手册页面,它会获取一个以微秒为基础的前缀唯一标识符。所以主要输入确实是当前的“microtime”。然而,它还需要一个额外的参数:more_entropy,如果设置为TRUE,则uniqid()函数将在返回值末尾添加附加熵(使用组合线性同余生成器),从而增加结果唯一的可能性。需要注意的是,即使使用此参数,手册也不会保证唯一性。但是,与您手动使用rand()相同,它增加了另一个随机源,从而使冲突变得极不可能。我们可以查看函数的源代码来确认这一点,其中可以看到没有设置more_entropy参数时的输出确实只是当前微秒时间戳的十六进制表示形式。有趣的一点是:
#if HAVE_USLEEP && !defined(PHP_WIN32)
    if (!more_entropy) {
#if defined(__CYGWIN__)
        php_error_docref(NULL, E_WARNING, "You must use 'more entropy' under CYGWIN");
        RETURN_FALSE;
#else
        usleep(1);
#endif
    }
#endif

所以,如果你不在Windows下运行,该函数实际上会尝试休眠一微秒,以强制后续值不同。
这使得连续多次运行uniqid()成为一个坏主意,因为如果它成功了,它将变得很慢。(需要一微秒的睡眠或调用随机数生成器。)
更好的做法是使用它一次来生成一个任意前缀,然后为每个项简单地递增计数器,可能看起来像这样:
$id_prefix = uniqid();
$id_suffix = 0;
$out = preg_replace_callback(
        $regex,
        function ($matches) use ($id_prefix, &$id_suffix) {
            $id = $id_prefix . $id_suffix;
            $id_suffix ++;
            return $matches[1] . '... some html ...' . $id . ' ... ';
        },
        $out
);

啊,那个“不支持Windows”的条款解释了为什么我在我的Mac上无法复现它。 - Barmar
这是一个非常有见地的答案,因为它解释了more_entropy在做什么(以及使用它可能存在的问题)。谢谢。 - David Wyly
1
源代码的链接已经失效,对于那些对整个源代码感到好奇的人,请使用以下链接:https://github.com/php/php-src/blob/master/ext/standard/uniqid.c - Leandro Jacques

4
uniqid()函数的文档中可以看到: 警告:该函数不会创建随机或不可预测的字符串。此函数不能用于安全目的。请使用密码学安全的随机函数/生成器和密码学安全的哈希函数来创建不可预测的安全ID。
如果运行脚本的操作系统没有提供很好的时钟分辨率,则uniqid()可能连续几次返回相同的值。
您也可以使用$id = uniqid(rand(10000,99999)),或者进一步增加随机性:$id = uniqid(rand(10000,99999), true)
无论如何,结论是名称具有误导性,因为它不能保证每次调用函数时都会获取唯一值。

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