【问题标题】:What is the best way to generate a random key within PHP?在 PHP 中生成随机密钥的最佳方法是什么?
【发布时间】:2010-10-12 20:15:26
【问题描述】:

我希望创建一个可重用的函数,该函数将生成一个随机密钥,其中包含选定长度的可打印 ACSII 字符(从 2 到 1000+)。我认为可打印的 ASCII 字符将是 33-126。它们的密钥不需要完全唯一,只要在完全相同的毫秒内生成即可(因此uniqid() 不起作用)。

我认为chr() 和mt_rand() 的组合可能会起作用。

这是要走的路,还是其他最好的方法?

编辑: uniqid() 也不起作用,因为它没有长度参数,它只是 PHP 给你的。

我的想法:这是我的想法:

function GenerateKey($length = 16) {
    $key = '';

    for($i = 0; $i < $length; $i ++) {
        $key .= chr(mt_rand(33, 126));
    }

    return $key;
}

这有什么问题吗?

另一个编辑:其他大多数问题都与密码生成有关。我想要更多种类的角色,我不在乎 1 与 l。我希望尽可能多的可能键。

注意:生成的密钥不一定是加密安全的。

【问题讨论】:

  • 随机数生成器通常不保证唯一性,即使“以完全相同的毫秒生成”。为了保证唯一性,您需要能够检测到碰撞。
  • 添加了有关安全性的重大警告,否则我将不得不对每个答案投反对票。请注意,答案中的某些函数返回二进制,其他返回十六进制,其他 base64 等。
  • 如果您真的想要所有 ASCII 可打印字符,Random::asciiPrintableString($length) 正是您所需要的。如果您只需要字母和数字,请改用Random::alphanumericString($length)。

标签: php random


【解决方案1】:

2021 年更新

从 PHP 7.0 开始,PHP 有一个专门的函数用于生成任意长度的随机密钥:random_bytes()。

与以前的解决方案不同,即使包括openssl_random_pseudo_bytes(),random_bytes() 也会 只返回适合加密使用的数据。它的随机源由操作系统提供,因此实现取决于操作系统。如果没有适合加密使用的随机数据源,PHP 将抛出异常。

此问题的先前答案尝试使用诸如 uniqid() 和哈希之类的源创建唯一标识符,但这不会生成 随机 键,只会生成一个很可能是唯一的。同样, rand() 或 mt_rand() 的输出对于加密目的来说不够随机。这个问题是在 random_bytes() 或其前身 openssl_random_pseudo_bytes() 在 PHP 中可用之前提出的。

请注意,random_bytes() 返回原始的 8 位二进制数据。如果您希望使这些数据可打印,则需要使用 base64_encode() 或 bin2hex() 之类的编码。

类似的函数 random_int() 也存在,它使用相同的随机源,但输出为整数。当然,这意味着可能输出的密钥数量仅限于整数大小,因此它本身并不适合加密密钥之类的东西。

【讨论】:

    【解决方案2】:

    更新 (12/2015):对于 PHP 7.0,您应该使用 random_int() 而不是 mt_rand,因为它提供“加密安全值”

    就个人而言,我喜欢使用sha1(microtime(true).mt_rand(10000,90000)),但您正在寻找更多可定制的方法,因此请尝试此功能(这是对您的this answer 请求的修改):

    function rand_char($length) {
      $random = '';
      for ($i = 0; $i < $length; $i++) {
        $random .= chr(mt_rand(33, 126));
      }
      return $random;
    }
    

    不过,这可能会比 uniqid()、md5() 或 sha1() 慢得多。

    编辑:看来你先搞定了,抱歉。 :D

    编辑 2: 我决定在我的 Debian 机器上用 PHP 5 和 eAccelerator 做一个不错的小测试(请原谅长代码):

    function rand_char($length) {
      $random = '';
      for ($i = 0; $i < $length; $i++) {
        $random .= chr(mt_rand(33, 126));
      }
      return $random;
    }
    
    function rand_sha1($length) {
      $max = ceil($length / 40);
      $random = '';
      for ($i = 0; $i < $max; $i ++) {
        $random .= sha1(microtime(true).mt_rand(10000,90000));
      }
      return substr($random, 0, $length);
    }
    
    function rand_md5($length) {
      $max = ceil($length / 32);
      $random = '';
      for ($i = 0; $i < $max; $i ++) {
        $random .= md5(microtime(true).mt_rand(10000,90000));
      }
      return substr($random, 0, $length);
    }
    
    $a = microtime(true);
    for ($x = 0; $x < 1000; $x++)
      $temp = rand_char(1000);
    
    echo "Rand:\t".(microtime(true) - $a)."\n";
    
    $a = microtime(true);
    for ($x = 0; $x < 1000; $x++)
      $temp = rand_sha1(1000);
    
    echo "SHA-1:\t".(microtime(true) - $a)."\n";
    
    $a = microtime(true);
    for ($x = 0; $x < 1000; $x++)
      $temp = rand_md5(1000);
    
    echo "MD5:\t".(microtime(true) - $a)."\n";
    

    结果:

    Rand:   2.09621596336
    SHA-1:  0.611464977264
    MD5:    0.618473052979
    

    因此,如果您想要速度(但不是完整的字符集),我的建议是坚持使用 MD5、SHA-1 或 Uniqid(我还没有测试......)

    【讨论】:

    • 以几秒钟的优势击败你。
    • 比你少几个月 ;)
    • +1 用于干净的答案和对原始代码的修改,这个最好使用 mt_rand()
    • 您能否更新此答案以强调需要random_bytes() 而不是rand()?
    • @ScottArciszewski 如果您想保持这种回答方式,我会改用random_int()。否则,您需要将字节转换为 ascii 样式的输出。
    【解决方案3】:

    您也可以生成一个随机密钥并检查您的数据库中的密钥如果密钥退出,您可以通过此方法生成另一个密钥

            function activation($lengt=20){
        $modalpage =new Modal;//you can call your modal page and check the key
    
        $characters = "1234567890abcdefghijklmnopqrstuvwxyz";
        for($i=0;$i<$lengt;$i++)
    
        {    
    
        $key .= $characters{rand(0,35)};
        $check= array(
         ":key" => $key,
        );
    
         $result = $modalpage->findkey($check);
    
         if($result==true){ //if the key is exits return the function and generate another key !
    
            return activation($lengt);   
    
         }else //if the key is not exits return the key
        return $key;
        }
    
    
                } 
    
    $mykey=activation(15);
    

    【讨论】:

      【解决方案4】:

      $key = md5(microtime() . rand());

      Microtime 本身并不安全,因为它只留下 1/1000 的机会被猜到。

      Rand 本身也不是很安全,但是将它们连接在一起会产生相当可靠的随机化。

      【讨论】:

      • “Rand 本身也不是很安全,但是将它们连接在一起会产生相当可靠的随机化。”没有。您有一个来自rand() 的熵上限为 32 位的可预测值。如果我们假设microtime() 包含 10 位熵,那么您最终会得到大约 42 位。每 200 万个样本后发生生日碰撞,但 rand() 是可以预测的。更好的解决方案:使用 CSPRNG 生成密钥。
      【解决方案5】:

      为什么不使用 openssl_random_pseudo_bytes http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php

      【讨论】:

      • 如果您展示一个示例来说明它如何提供帮助,这个答案会更有效。
      • 我成功使用上面的如下 $sessionkey = bin2hex(openssl_random_pseudo_bytes(1024));这会生成一个加密安全的随机密钥......这是对..“生成随机密钥的最佳方法”的答案......这是一个非常好的方法......a)它使用一个简单的快速功能......它是密码安全,它显然是随机的......你可以生成任何长度的密钥......
      【解决方案6】:

      如果您想要加密强度的随机性,这里的答案都不够充分(是否有坚定的攻击者试图猜测您的随机密钥是什么?)。散列时间并不安全,攻击者可以通过猜测他们认为您的服务器生成密钥的时间来大大加快他们的搜索速度,并且很容易在给定年份的所有毫秒内搜索,即使在商用笔记本电脑上(它是 35位搜索空间)。此外,通过散列函数运行uniqid() 或其他弱随机源的结果以“扩展它”的建议是危险的——一旦他们发现你这样做了,这不会使攻击者的搜索更加困难。

      如果您真的需要加密级别的安全性,您应该从 /dev/random 读取,以下代码应该适用于任何符合 POSIX 的系统(Windows 除外):

      #Generate a random key from /dev/random
      function get_key($bit_length = 128){
          $fp = @fopen('/dev/random','rb');
          if ($fp !== FALSE) {
              $key = substr(base64_encode(@fread($fp,($bit_length + 7) / 8)), 0, (($bit_length + 5) / 6)  - 2);
              @fclose($fp);
              return $key;
          }
          return null;
      }
      

      如果您需要更快的速度,可以改为从“dev/urandom”读取。

      【讨论】:

      • 只是想注意 - 我有一个脚本需要额外的 50 秒左右才能加载,结果是使用 /dev/random 而不是 /dev/urandom 是原因。在我的本地测试环境中,这从未发生过。
      • /dev/urandom 应该是默认值,使用/dev/random/ 会过早耗尽随机熵池(在没有链接到/dev/random 的专用随机数生成器的系统上)。实际上,由于这将是攻击者执行 DoS 攻击的好方法,我认为根本不应该公开该功能,它只对具有自己 PRNG 的应用程序/库有用。
      【解决方案7】:

      你仍然可以使用 uniqid(),只是做一些额外的处理,将它的值扩展为你需要的字符数。

      例如,要将其扩展为 32 个字符,您可以这样做

      $id = md5(uniqid());
      

      要将其扩展为64个字符,只需附加md5的md5,像这样

      $first = md5(uniqid());
      $id = $first . md5($first);
      

      然后,如果您需要小于 32 的某个倍数,则根据需要进行 trucate。

      您可能会遇到碰撞,但这不太可能。如果您对此持怀疑态度,请使用相同的想法,但通过像 AES 之类的对称密码而不是散列它来插入 uniqid()。

      【讨论】:

        【解决方案8】:

        that question 你会感兴趣吗?

        我不确定为什么uniqid() 对您不起作用,在什么情况下您需要在同一毫秒内的唯一编号,但不一定非要不然;你生成什么速度如此之快以至于在同一毫秒内你可能会发生碰撞?我想知道uniqid() 需要多少时间才能生成它的号码。如果需要,可以使用uniqid() 函数的前缀参数和几个随机字母,你应该是安全的。

        如果是用于生成文件,您可能需要查看tmpfile() 或tempname()。

        在任何情况下,根据您要实现的目标,您可以循环并验证是否已获取唯一 ID(在数组中,使用 file_exists 等),如果是这样,则只需生成另一个。


        另外,由于我不确定我是否完全理解您的问题,所以我会向您指出那些听起来非常相似的其他问题,而我却能理解您的不同之处:

        如果您希望创建一个人类可读的唯一 id,第一个会很有趣。如果您想使用随机数和 md5/sha1,第二个可能会很有用。不过,我再次认为 uniqid() 可能已经是您正在寻找的东西了。

        【讨论】:

          猜你喜欢
          • 2020-10-05
          • 2012-03-17
          • 1970-01-01
          • 2013-09-26
          • 2016-05-23
          • 1970-01-01
          • 1970-01-01
          • 2016-11-20
          相关资源
          最近更新 更多