在PHP中生成随机密钥的最佳方法是什么?

40
我想创建一个可重复使用的函数,用于生成所选长度(2到1000+)的随机密钥,其字符为可打印的ASCII字符(即33-126)。我认为使用可打印ASCII字符会比较好。这些密钥不需要完全唯一,只需要在相同毫秒内生成时是唯一的(因此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;
}

这样会有什么问题吗?

另一次编辑:大多数其他问题都涉及密码生成。我想要更广泛的字符集,并且不在乎1l的区别。我希望可以生成最多数量的密钥。

注意:生成的密钥不一定要具有密码学安全性。


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

58

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

个人而言,我喜欢使用sha1(microtime(true).mt_rand(10000,90000)),但您正在寻找更具可定制性的方法,所以请尝试这个函数(这是对您的请求的修改[此答案][1]):

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

这可能比uniqid()、md5()或sha1()慢得多。编辑:看起来你先到了,抱歉。编辑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]: 在php中生成短唯一标识符

1
仅仅比你快了几秒钟。 - Darryl Hein
3
就比你早几个月赢了你一步 ;) - lpfavreau
1
对于干净的答案和对原始代码的修改,给予+1。使用mt_rand()更好。 - lpfavreau
1
你能否更新这个答案,强调需要使用random_bytes()而不是rand()吗? - Scott Arciszewski
1
@ScottArciszewski 如果你想保持这种回答的风格,我会使用 random_int()。否则,你需要将字节转换为 ASCII 样式输出。 - St. John Johnson
显示剩余3条评论

29

如果您需要加密级别的随机性(是否有一个决定攻击者试图猜测您的随机密钥?),这里没有任何答案是足够的。哈希时间不安全,攻击者可以通过在他们认为您的服务器生成密钥的时间周围猜测来大大加快搜索速度,而且即使在一台普通笔记本电脑上也很容易在给定年份内搜索所有毫秒(它是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”中读取。


4
想要说明一下 - 我有一个脚本加载需要额外的50秒左右时间,结果发现使用/dev/random代替/dev/urandom是导致这个问题的原因。在我的本地测试环境中,从未出现过这种情况。 - whichdan
4
应该默认使用/dev/urandom,使用/dev/random会在没有链接到/dev/random的专用随机数生成器的系统上过早地消耗随机熵池。实际上,由于这将成为攻击者执行DoS攻击的好方法,我不认为该函数应该公开,它仅对具有自己PRNG的应用程序/库有用。 - Maarten Bodewes

9
你仍然可以使用uniqid()函数,只需对其返回值进行一些额外的处理以扩展其长度至你所需的字符数。例如,若要将其扩展至32个字符,则可以执行以下操作:
$id = md5(uniqid());

为了将其扩展到64个字符,只需附加md5的md5,如下所示。
$first = md5(uniqid());
$id = $first . md5($first);

然后,如有需要,截取到32的倍数即可。

可能会出现碰撞的情况,但这种情况相当少见。如果您对此很担心,只需使用相同的想法,但将uniqid()通过对称密码(如AES)进行加密,而不是哈希它。


3

1
如果您展示一个如何帮助的例子,这个答案会更有效。 - ethrbunny
我成功地使用了上述代码,如下所示:$sessionkey = bin2hex(openssl_random_pseudo_bytes(1024)); 这将生成一个密码学安全的随机密钥...这是对“生成随机密钥的最佳方法”的回答...它是一种非常好的方式,因为a)它使用了一个简单快速的函数...它是加密安全的,显然是随机的...而且你可以生成任意长度的密钥... - omar-ali

2

更新于2021年

自PHP 7.0以来,PHP已经有了一个专门用于生成任意长度随机密钥的函数:random_bytes()

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

以前对这个问题的回答尝试使用uniqid()和哈希等来源创建唯一标识符,但那不会生成一个随机密钥,只会生成一个高度可能是唯一的密钥。同样,rand()或mt_rand()的输出对于加密目的来说不够随机。在PHP中出现random_bytes()或它的前身openssl_random_pseudo_bytes()之前,就已经有人问过这个问题。

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

还有一个类似的函数random_int(),它也使用相同的随机源,但输出为整数。当然,这意味着可能生成的密钥数量受限于整数的大小,因此它本身不适合用作加密密钥。


0

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

单独使用Microtime并不安全,因为只有1/1000的机会被猜测到。

单独使用Rand也不是非常安全,但将它们连接在一起进行哈希处理可以产生相当可靠的随机化。


“Rand本身也不是非常安全,但将它们连接起来进行哈希处理可以产生相当坚固的随机化。” 不行。从rand()中获得32位熵的可预测值上限。如果我们假设microtime()包含10比特熵,则最终获得大约42比特。每200万个样本后出现生日碰撞,但rand()是可预测的。更好的解决方案:为生成密钥使用CSPRNG。 - Scott Arciszewski

0

这个问题对你来说是否感兴趣?

我不确定为什么uniqid()对你无效,在什么情况下你需要在同一毫秒内生成唯一的数字,但不一定是其他情况;你正在生成什么东西以至于在同一毫秒内可能会发生冲突?我想知道uniqid()花费多少时间来生成它的数字。如果你想要,可以使用uniqid()函数的前缀参数加上几个随机字母,你应该是安全的。

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

无论如何,根据你想要实现的目标,你可以循环并验证唯一ID是否已经被占用(在数组中,使用file_exists等),如果是这种情况,就生成另一个。


另外,由于我不确定我是否完全理解了你的问题,因此我会向那些听起来非常相似的其他问题指出,同时我会弄清楚你的问题与它们之间的区别:

如果您希望进行人类可读的唯一标识符,则第一个将非常有用。如果您想玩随机数字和md5 / sha1,则第二个可能会有用。尽管如此,我认为uniqid()可能已经是您要查找的内容。


-1
此外,您可以生成一个随机密钥并在数据库中检查该密钥是否存在,如果该密钥已存在,则可以使用此方法生成另一个密钥。
        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);

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