【问题标题】:Unique key generation唯一密钥生成
【发布时间】:2010-09-08 11:40:20
【问题描述】:

我正在寻找一种方法,特别是在 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,但我不确定这些键是否完全唯一。

这需要在后台运行,无需任何用户输入。

【问题讨论】:

  • 为什么密码需要完全唯一?
  • 我想crypt只是因为你需要crypt value,它与创造唯一价值无关,不是吗?!

标签: php web-services security passwords


【解决方案1】:

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

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

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

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

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

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

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

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

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

【讨论】:

  • 您好,您对数字 2 有任何进一步的阅读,我想使用不同的“来源”使用这种代码生成,谢谢。
  • @Jon:我对此没有任何了解,但基本上你将源标识符与来自该源的序列连接起来。所以你知道它是独一无二的。例如,如果您有两个来源,那么您将拥有 A1、A2、A3、A4 ......的序列。 A999 和 B1、B2、B3、B4 的另一个序列。 .. B999。由于来自源 A 的 ID 永远不会以 B 开头,因此您知道永远不会发生冲突。
  • k 谢谢,源标识符可以是 db 表的 id,以便在输入代码时可以从表中的记录中收集信息吗?
【解决方案2】:

任何算法都会导致重复

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

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

【讨论】:

  • GUID(或 UUIDS)和序列是唯一避免重复的两种方法,但对于此处提到的所有其他算法,您都是正确的。
  • 如果允许任意标识符长度,我同意。如果他想像他的示例中那样保留 8 个字符,那是不够的。
  • 如果您使用 Base32,则 8 个字符足以提供 1,099,511,627,776 个唯一密码,这对于手动输入的数据 (32^8) 来说是可敬的。这不允许验证,也不排除像 00000000 这样的模式。
  • 冲突是由于他的算法或他的编码(可能只使用十进制,这只有 100,000,000 种可能性。集中顺序生成是唯一可行的解​​决方案,或者像您建议的那样检查重复项。跨度>
【解决方案3】:

不写代码,我的逻辑是:

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

保持快乐!
H

【讨论】:

  • 这很接近,但取决于您的随机例程,其精度仅略高于微秒。正如他所提到的,uniqid 因同样的可能性而失败。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    您可能对这篇处理相同问题的文章感兴趣:GUIDs are globally unique, but substrings of GUIDs aren't

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

    【讨论】:

      【解决方案6】:

      我通常这样做:

      $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));
      }
      

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

      【讨论】:

        【解决方案7】:

        正如 Frank Kreuger 所说,使用 GUID 生成器。

        点赞this one

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

          • 因为它可能并不总是密码,而是他们的实际用户名/登录 ID 或交易 ID。这两个基本上都是用户名,所以它们必须是唯一的。我将数据库设置为只接受唯一密码,但同样,我不能假设我正在处理我可以访问的数据库。另一件事是我只想要 7 到 15 个字符。 GUID 通常更长,因此最终不会很容易成为唯一的。
          【解决方案9】:

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

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

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

          【讨论】:

            【解决方案10】:

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

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

            【讨论】:

              【解决方案11】:

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

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

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

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

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

              【讨论】:

              • 我不明白你为什么需要 md5 值。 MD5 是一种创建签名的单向哈希。请注意,不同的输入值可能会导致创建相同的签名,当输入值略有不同而不是完全不同时,会触发 MD5 函数以尝试返回不同的结果。因此,通过添加 MD5,我认为您会增加获得重复值的概率。
              【解决方案12】:

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

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

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

              当在不同的进程中调用时,你一定很不幸得到 2 个具有相同值的 microtime() 调用,认为 microtime() 调用通常在同一个脚本中调用时也具有不同的值。

              【讨论】:

                【解决方案13】:

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

                【讨论】:

                • 如果您采用加密安全哈希的子字符串,那么您就破坏了哈希的唯一性。哈希的任何部分都比其他任何部分都更安全或更随机。它与使用常规调用随机数生成器一样可能获得相同的密码。
                猜你喜欢
                • 2023-04-04
                • 2013-07-18
                • 2012-04-09
                • 1970-01-01
                • 2015-03-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-02-28
                相关资源
                最近更新 更多