【问题标题】:Generate unique random alphanumeric characters that are 7 characters long生成 7 个字符长的唯一随机字母数字字符
【发布时间】:2011-02-17 11:23:52
【问题描述】:

它不必是有意义的词——更像是随机密码生成,但要注意的是——它们应该是唯一的。我将把它用于某种包/产品代码。哪种方法最好? :)

【问题讨论】:

  • 为什么你的密码必须是唯一的?
  • 他/她说的是“喜欢密码”,而不是“密码”。据我所知,这是一个产品代码,而且必须是唯一的。
  • 次要旁注:如果您将这些代码提供给用户,请注意,您最终会给出一个代码,内容为“F*CKYOU”
  • @nickf:很好。我有一个函数可以交替生成辅音和元音,我用它来驱动一些 CAPTCHA 5 char 字符串...在测试过程中,我得到一个说 merda 的函数(翻译 = sh t)和一位年长的客户有点生气,因为他得到了一个说 foder(翻译 = to fck)...
  • @Alix 我过去在需要密码生成器时所做的就像 Jon 对 $allowed_chars 字符串的回答一样,但删除了所有元音以及可能被误解为其他字符的字符:1, l, 0

标签: php mysql passwords unique


【解决方案1】:

生成具有唯一元素和随机元素的序列通常是不可能的:显然,为了保持唯一,算法必须考虑序列中先前生成的元素,因此接下来的元素将不是真正随机的。

因此,您最好的选择是检测碰撞并重试(在您的特定情况下这可能非常昂贵)。

如果您仅限于 7 个字符,那么您在上面无能为力:

$allowed_chars = 'abcdefghijklmnopqrstuvwxz';
$allowed_count = strlen($allowed_chars);
$password = null;
$password_length = 7;

while($password === null || already_exists($password)) {
    $password = '';
    for($i = 0; $i < $password_length; ++$i) {
        $password .= $allowed_chars{mt_rand(0, $allowed_count - 1)};
    }
}

这最终应该会给你一个新密码。

然而,在我遇到的类似情况下,我通常会选择一个更大的密码大小,这也恰好是流行散列函数的十六进制表示的大小(例如md5)。然后你可以让自己更轻松,更不容易出错:

$password = time(); // even better if you have some other "random" input to use here

do {
    $password = md5(time().$password);
}
while (already_exists($password));

这还有一个额外的好处,即序列空间更大,因此冲突更少。您可以根据将来生成的预期密码数量来选择散列函数的大小,以“保证”较低的冲突概率,从而减少对可能昂贵的 already_exists 函数的调用。

【讨论】:

  • 'alphanumeric' 意味着您的 $allowed_chars 变量中需要 0...9 :)
【解决方案2】:

这是一种无需散列或循环的方法:

$password = sprintf(
    "%04s%03s",
    base_convert(mt_rand(0, pow(36, 4) - 1), 10, 36),
    base_convert(mt_rand(0, pow(36, 3) - 1), 10, 36)
);

正如其他一些人所提到的,确保唯一性更加复杂,并且应该是不必要的。最简单的方法是在末尾添加额外字符,并随着生成的每个密码递增。

【讨论】:

    【解决方案3】:

    以下内容看起来是随机的,应该是独一无二的,并且有 7 个字符以供将来使用:

    echo base_convert(intval(microtime(true) * 10000), 10, 36);
    

    或者多一点随机性和少一点唯一性(每秒100010000之间):

    echo base_convert(mt_rand(1, 9) . intval(microtime(true) * 1000), 10, 36);
    

    或者(每秒 10010000 之间的唯一性) - 这可能是最好的选择:

    echo base_convert(mt_rand(10, 99) . intval(microtime(true) * 100), 10, 36);
    

    或(每秒 1010000 之间的唯一性):

    echo base_convert(mt_rand(100, 999) . intval(microtime(true) * 10), 10, 36);
    

    你明白了。

    【讨论】:

    • 此处注意整数溢出。 intval(microtime(true) * 10000) == -65867797 - 这会给你一个 6 个字符的输出。
    【解决方案4】:

    具有 7 个字符的随机字母数字(基数 36 = 0..9 + a..z)值必须在 217678233678364164095 之间具有基数 10 表示,以下 sn-p 证明了这一点:

    var_dump(base_convert('1000000', 36, 10));                   //  2176782336
    var_dump(base_convert('zzzzzzz', 36, 10));                   // 78364164095
    

    为了使其独一无二,我们必须依赖非重复因素,显而易见的选择是time()

    var_dump(time());                                            //  1273508728
    var_dump(microtime(true));                                   //  1273508728.2883
    

    如果我们只想确保每秒 1 个唯一代码的最小唯一性因子,我们可以这样做:

    var_dump(base_convert(time() * 2, 10, 36));                  // 164ff8w
    var_dump(base_convert(time() * 2 + 1, 10, 36));              // 164ff8x
    var_dump(base_convert(time() * 2 + 2, 10, 36));              // 164ff8y
    var_dump(base_convert(time() * 2 + 3, 10, 36));              // 164ff8z
    

    您会注意到这些代码不是随机的,您还会注意到 time() (1273508728) 小于 2176782336(7 个字符代码的最小基数 10 表示),这就是为什么我做time() * 2

    现在让我们做一些日期数学,以增加随机性并增加唯一性因子,同时遵守旧版本 PHP (&lt; 5.0?) 的整数限制:

    var_dump(1 * 60 * 60);                                       //       3600
    var_dump(1 * 60 * 60 * 24);                                  //      86400
    var_dump(1 * 60 * 60 * 24 * 366);                            //   31622400
    var_dump(1 * 60 * 60 * 24 * 366 * 10);                       //  316224000
    var_dump(1 * 60 * 60 * 24 * 366 * 20);                       //  632448000
    var_dump(1 * 60 * 60 * 24 * 366 * 30);                       //  948672000
    var_dump(1 * 60 * 60 * 24 * 366 * 31);                       //  980294400
    var_dump(PHP_INT_MAX);                                       // 2147483647
    

    关于PHP_INT_MAX 我不确定最近版本的 PHP 到底发生了什么变化,因为以下内容显然适用于 PHP 5.3.1,也许有人可以对此有所了解

    var_dump(base_convert(PHP_INT_MAX, 10, 36));                 // zik0zj
    var_dump(base_convert(PHP_INT_MAX + 1, 10, 36));             // zik0zk
    var_dump(base_convert(PHP_INT_MAX + 2, 10, 36));             // zik0zl
    var_dump(base_convert(PHP_INT_MAX * 2, 10, 36));             // 1z141z2
    var_dump(base_convert(PHP_INT_MAX * 2 + 1, 10, 36));         // 1z141z3
    var_dump(base_convert(PHP_INT_MAX * 2 + 2, 10, 36));         // 1z141z4
    

    我在这里的合理化有点迷失了,我很无聊,所以我会很快完成。我们几乎可以使用整个 36 个基本字符集并安全地生成顺序代码,保证唯一性因子为每秒 1 个唯一代码for 3.16887646 years,使用以下代码:

    base_convert(mt_rand(22, 782) . substr(time(), 2), 10, 36);
    

    我刚刚意识到,由于mt_rand() 的第一个参数,上述内容有时会返回重复值,为了产生独特的结果,我们需要稍微限制我们的基本 36 字符集:

    base_convert(mt_rand(122, 782) . substr(time(), 2), 10, 36);
    

    请记住,上面的值仍然是连续的,为了使它们看起来随机,我们可以使用microtime(),但我们只能确保每秒10个代码for 3.8 months的唯一性因子:

    base_convert(mt_rand(122, 782) . substr(number_format(microtime(true), 1, '', ''), 3), 10, 36);
    

    事实证明这比我最初预期的要困难,因为有很多限制:

    • 使用整个 base 36 字符集
    • 生成外观随机的代码
    • 每秒唯一性因子与唯一性持久性之间的权衡
    • PHP 整数限制

    如果我们可以忽略以上任何一项,它会容易得多,我相信这可以进一步优化,但就像我说的:这让我很无聊。也许有人想在我离开的地方捡起这个。 =) 我饿了! =S

    【讨论】:

    • 为什么要以1000000而不是0000000开头?
    • @sawa:你看过问题和我回答的第一段了吗?
    • 我的问题是针对您的第一段。您正在证明这一点,var_dump(base_convert('1000000', 36, 10)); 内有 1000000 而不是 0000000。我问为什么?问题是要求一个字符串。为什么字符串不能以'0'开头?
    • @sawa:你可以,但是你必须用 0 填充它。我想我没有提到这一点,因为时间戳永远不会有那种低值。
    【解决方案5】:

    这是我最喜欢的方式。

    $pretrimmedrandom = md5(uniqid(mt_rand(),true));
    $trimmed =  substr($pretrimmedrandom ,0,7);
    

    uniqid 使用当前时间生成一个非常独特的随机字符串。结果看起来像“3f456yg”。

    【讨论】:

      【解决方案6】:

      鉴于您在这里提到了密码,我假设您需要一种安全的方法(即:某人不应该根据知道任何其他密码来猜测其他人的密码)。您可以使用以下内容:

      1. 确定主密码,例如“MasterPassword”
      2. 对于生成的每个密码,附加一个随机或连续的nonce,例如“MasterPassword1”、“MasterPassword2”。
      3. 对其执行加密哈希(SHA、MD5 等)并将哈希转换为十六进制表示,例如“ce7f181a44a4a5b7e43fe2b9a0b1f0c1”。
      4. 根据需要将其截断为尽可能多的字符 - 可能如您所指的那样是七个:“ce7f181”。
      5. 检查之前是否已分配。如果没有,请将其作为您的密码返回。否则,从 2 开始重复。

      如果安全不是问题,单独执行第 1 步和第 2 步就足够了。如果安全是一个问题,那么除了您自己之外没有人知道“MasterPassword”的价值是至关重要的。

      【讨论】:

      • 我不太喜欢加密货币,所以我无法用言语表达我对这个想法的不适......但这让我感到不舒服。您通过使用 base16 而不是例如 base-26+26+10 来减少可能性;另外(在这里猜测)您在截断哈希时会损害哈希的完整性。不过,我承认,唯一密码的要求也有点奇怪……
      • 同意所有观点。我很乐意承认关于十六进制的观点,你说得对,潜在角色越多越好。还同意截断,但不需要 32 个字符的密码(或更糟),这是不可避免的。此外,哈希值更多地用作生成高度唯一密码的手段,因为它与其他密码具有相似的基础,而不是作为安全措施。确实,也可以使用非加密哈希或 CRC,除非这会使主密码更容易受到攻击。
      • 加密哈希不是唯一的。如果您截断它们,它们肯定不是唯一的。与简单地生成随机字符串并检查 那些 以查看它们是否已分配相比,此方法确实没有任何优势。
      • P.S.而且我 am [有点] 热衷于加密,所以我可以用语言表达我对这个想法的不适。 ;)
      • 好的,我上面的回答措辞不佳。我从来没有暗示哈希是唯一的,只是它很可能是唯一的。如果需要安全性,那么与非加密随机字符串生成器相比,此方法具有优势 - (可证明)攻击者更难以在给定样本的情况下对 特定 其他用户的密码进行逆向工程生成的密码。您可以基本上将其视为使用加密 PRNG(哈希)生成字符,在这种情况下,此答案与其他答案实际上几乎没有区别。
      【解决方案7】:

      我将如何解决这个问题:

      考虑这 7 个字符可以是 26 个字母 (abc..z) 或 10 个数字 (01...9) 之一。这使得 36 个可能的字符。

      每次您的应用程序生成一个新代码时,让它增加一个全局变量。您可以使用“十六进制”转换器将此唯一数字转换为唯一字符串,并通过添加填充字符来构成字符串的其余部分。

      看看这个链接。我想这个人和你有同样的问题: http://www.codemaxima.com/2010/04/the-hexatridecimal-numbering-system/

      【讨论】:

        【解决方案8】:
        $random = substr(hash('md5',openssl_random_pseudo_bytes(32)),0,7);
        

        【讨论】:

          【解决方案9】:

          +1 @Michael Haren 的评论。如果您网站上的密码不应该具有唯一性约束。

          如果我尝试使用给定的密码,但由于它已在使用中而出现无法使用的错误,那么我知道系统上的某个用户拥有该密码。如果有 1000 个用户,我只需要尝试最多 1000 个其他帐户,然后才能找到拥有该密码的用户。

          没有真正回答您的问题,而不仅仅是评论。所以我要标记这个 CW。

          【讨论】:

          • 我相信“密码生成”一词就是一个例子,我认为他想生成产品代码。
          • @Alix:谢谢,我没有仔细阅读他的问题。我的错!我将在此处留下我的 CW 答案,以便考虑强制密码唯一的读者可以看到相反的论点。
          【解决方案10】:
          md5( microtime() );
          

          【讨论】:

            【解决方案11】:

            盖伦的回答只允许密码中的每个字符使用一次。该字符串中没有太多信息。一个简单的改变:

            $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
            $passwordlength = 7;
            for ($x = 1; $x <= $passwordlength; $x++) {
              $charlist .= $chars;
            }
            $temp_pw = substr( str_shuffle( $charlist ), 0, $passwordlength );
            

            【讨论】:

              【解决方案12】:
              substr(str_shuffle(md5(microtime())),rand(0,21),7);
              

              【讨论】:

                【解决方案13】:

                试试这个

                echo $unique_key = substr(md5(rand(0, 1000000)), 0, 5);
                

                它会给出长度为 5 的字符串。

                【讨论】:

                  【解决方案14】:

                  使用 Kohana 文本,

                  http://docs.kohanaphp.com/helpers/text

                  例如,

                     $prod_id = text::random('alpha', 7);
                  

                  如果你不想使用框架,你可以简单地复制代码。你会在那里找到很多好东西。

                  【讨论】:

                    【解决方案15】:

                    这是一个非常简单的方法

                    $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
                    $temp_pw = substr( str_shuffle( $chars ), 0, 7 );
                    if ( check_unique( $temp_pw ) ) {
                        $pw = $temp_pw;
                    }
                    

                    您必须实现自己的 check_unique 函数。那部分应该很容易。

                    【讨论】:

                      猜你喜欢
                      • 2011-08-18
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-08-23
                      • 2010-09-08
                      • 2017-04-05
                      • 2014-04-07
                      • 2013-11-07
                      • 1970-01-01
                      相关资源
                      最近更新 更多