12

我正在寻找一种方法,特别是在 PHP 中,我将保证始终获得唯一的密钥。

我做了以下事情:

strtolower(substr(crypt(time()), 0, 7));

但是我发现有时我会得到一个重复的键(很少,但经常)。

我也想过这样做:

strtolower(substr(crypt(uniqid(rand(), true)), 0, 7));

但是根据 PHP 网站,uniqid() 可以,如果 uniqid() 在同一微秒内被调用两次,它可以生成相同的密钥。我认为添加 rand() 它很少会,但仍然可能。

在上面提到的行之后,我还删除了 L 和 O 等字符,这样用户就不会感到困惑。这可能是重复的部分原因,但仍然是必要的。

我想到的一个选择是创建一个网站,该网站将生成密钥,将其存储在数据库中,确保它是完全唯一的。

还有其他想法吗?是否有任何网站已经这样做了,它们有某种 API 或者只是返回密钥。我找到了http://userident.com但我不确定密钥是否完全唯一。

这需要在没有任何用户输入的情况下在后台运行。

4

13 回答 13

19

只有 3 种方法可以生成唯一值,它们是密码、用户 ID 等:

  1. 使用有效的 GUID 生成器 - 这些生成器很长并且不能缩小。如果你只使用部分你 FAIL
  2. 至少部分数字是从单个序列中顺序生成的。您可以添加绒毛或编码以使其看起来不那么连续。优点是它们开始时间短 - 缺点是它们需要单一来源。解决单一来源限制的方法是对来源进行编号,因此您可以包含 [source #] + [seq #],然后每个来源都可以生成自己的序列。
  3. 通过其他方式生成它们,然后根据先前生成的值的单一历史记录检查它们。

不保证任何其他方法。请记住,从根本上说,您正在生成一个二进制数(它是一台计算机),但随后您可以将其编码为十六进制、十进制、Base64 或单词列表。选择适合您使用的编码。通常对于用户输入的数据,您需要一些 Base32 的变体(您暗示过)。

关于 GUIDS 的注意事项:它们从长度和用于生成它们的方法中获得了独特性的力量。 任何小于 128 位的内容都是不安全的。 除了随机数生成之外,GUID 还具有一些特性,使其更加独特。请记住,它们实际上只是唯一的,而不是完全唯一的。这是可能的,尽管实际上不可能有一个副本。

更新了关于 GUID 的说明:自从写这篇文章以来,我了解到许多 GUID 生成器使用加密安全的随机数生成器(很难或不可能预测下一个生成的数字,并且不太可能重复)。实际上有 5 种不同的UUID 算法。算法 4 是 Microsoft 当前用于 Windows GUID 生成 API 的算法。GUID是 Microsoft 对 UUID 标准的实现。

更新:如果你想要 7 到 16 个字符,那么你需要使用方法 2 或 3。

底线:坦率地说,没有完全独特的东西。即使你使用顺序生成器,你最终也会用完宇宙中所有的原子的存储空间,从而循环回到你自己并重复。你唯一的希望是在达到那个点之前宇宙的热寂。

即使是最好的随机数生成器也有可能重复等于您正在生成的随机数的总大小。以四分之一为例。它是一个完全随机的比特生成器,它的重复几率是 2 分之一。

所以这一切都归结为你的独特性门槛。通过使用序列,然后对其进行 base32 编码,您可以对 1,099,511,627,776 个数字具有 8 位数字的 100% 唯一性。任何其他不涉及检查过去数字列表的方法仅具有等于 n/1,099,511,627,776(其中 n = 生成的先前数字的数量)不唯一的几率。

于 2008-09-10T21:06:12.557 回答
1

任何算法都会导致重复

因此,我是否可以建议您使用现有算法* 并简单地检查重复项?

*轻微添加:如果uniqid()可以基于时间不唯一,还包括一个全局计数器,您在每次调用后递增。这样,即使在同一微秒内,情况也会有所不同。

于 2008-09-10T20:30:06.697 回答
0

如果不编写代码,我的逻辑是:

从您喜欢的任何可接受的字符生成随机字符串。
然后将一半的日期戳(部分秒和全部)添加到前面,另一半添加到末尾(或者如果您愿意,可以在中间的某个位置)。

保持快乐!
H

于 2008-09-10T20:20:48.950 回答
0

如果您使用原始方法,但在密码前添加用户名或电子邮件地址,则如果每个用户只能有 1 个密码,它将始终是唯一的。

于 2008-09-10T20:22:25.140 回答
0

您可能对处理相同问题的这篇文章感兴趣:GUID 是全局唯一的,但 GUID 的子字符串不是

该算法的目标是使用时间和位置的组合(相对论极客的“时空坐标”)作为唯一性密钥。但是,计时并不完美,因此有可能,例如,从同一台机器快速连续生成两个 GUID,它们在时间上彼此如此接近以至于时间戳相同。这就是唯一性的用武之地。

于 2008-09-10T20:22:29.993 回答
0

我通常这样做:

$this->password = '';

for($i=0; $i<10; $i++)
{
    if($i%2 == 0)
        $this->password .= chr(rand(65,90));
    if($i%3 == 0)
        $this->password .= chr(rand(97,122));
    if($i%4 == 0)
        $this->password .= chr(rand(48,57));
}

我想有一些理论上的漏洞,但我从来没有遇到过重复问题。我通常将它用于临时密码(例如在密码重置后),它的效果很好。

于 2008-09-10T20:23:29.600 回答
0

您可能对 Steve Gibson 在https://www.grc.com/passwords.htm上的密码生成器的顶级安全实现感兴趣(没有来源,但他对它的工作原理有详细的描述) 。

该网站创建了巨大的 64 个字符的密码,但由于它们是完全随机的,因此您可以轻松地使用前 8 个(或多个)字符来获得不太安全但“尽可能随机”的密码。

编辑:从您以后的回答中,我看到您需要的东西更像是 GUID 而不是密码,所以这可能不是您想要的...

于 2008-09-10T20:27:48.560 回答
0

正如 Frank Kreuger 评论的那样,使用 GUID 生成器。

喜欢这个

于 2008-09-10T20:31:19.393 回答
0

我仍然不明白为什么密码必须是唯一的?如果您的 2 个用户具有相同的密码,有什么缺点?

这是假设我们谈论的是与用户 ID 相关联的密码,而不仅仅是唯一标识符。如果就是您要寻找的东西,为什么不使用 GUID?

于 2008-09-10T20:51:35.227 回答
0

我确实相信您的部分问题是您试图为我们提供一个用于两种不同用途的单一功能......密码和 transaction_id

这确实是两个不同的问题领域,最好不要尝试一起解决它们。

于 2008-09-11T21:11:09.757 回答
0

我最近想要一个快速简单的随机唯一键,所以我做了以下事情:

$ukey = dechex(time()) . crypt( time() . md5(microtime() + mt_rand(0, 100000)) ); 

所以,基本上,我得到了以秒为单位的 unix 时间,并添加了一个从时间 + 随机数生成的随机 md5 字符串。这不是最好的,但对于低频请求来说已经相当不错了。它快速且有效。

我做了一个测试,我会生成数千个键,然后寻找重复,每秒大约 800 个键没有重复,所以还不错。我想这完全取决于 mt_rand()

我将它用于调查跟踪器,我们的提交率约为每分钟 1000 份调查......所以现在(交叉手指)没有重复。当然,速率不是恒定的(我们在一天中的某些时间收到提交)所以这不是失败证明也不是最好的解决方案......提示是使用增量值作为键的一部分(在我的情况下,我使用了 time(),但可能会更好)。

于 2009-09-15T11:58:50.377 回答
0

Ingoring 加密部分与创建唯一值没有太大关系,我通常使用这个:

function GetUniqueValue()
{
   static $counter = 0; //initalized only 1st time function is called
   return strtr(microtime(), array('.' => '', ' ' => '')) . $counter++;
}

当在同一进程中调用时,$counter 会增加,因此值在同一进程中始终是唯一的。

当在不同的进程中调用时,您必须非常不幸地获得 2 个具有相同值的 microtime() 调用,认为 microtime() 调用通常在同一脚本中调用时也具有不同的值。

于 2010-03-18T19:49:59.173 回答
-1

我通常做一个随机子字符串(随机化 8 到 32 之间的字符数,或者为了用户方便而更少)或者我得到的某个值的 MD5,或者时间,或者某种组合。为了获得更多随机性,我将值的 MD5 (比如姓氏)与时间连接起来,再次 MD5,然后取随机子字符串。是的,您可以获得相同的密码,但可能性不大。

于 2008-09-10T20:26:37.237 回答