PHP 中的安全随机数生成

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1182584/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 01:24:15  来源:igfitidea点击:

Secure random number generation in PHP

phpsecurityhashrandom

提问by rwallace

Use case: the "I forgot my password" button. We can't find the user's original password because it's stored in hashed form, so the only thing to do is generate a new random password and e-mail it to him. This requires cryptographically unpredictable random numbers, for which mt_rand is not good enough, and in general we can't assume a hosting service will provide access to the operating system to install a cryptographic random number module etc. so I'm looking for a way to generate secure random numbers in PHP itself.

用例:“我忘记了密码”按钮。我们无法找到用户的原始密码,因为它以散列形式存储,因此唯一要做的就是生成一个新的随机密码并将其通过电子邮件发送给他。这需要加密不可预测的随机数,mt_rand 对此还不够好,一般我们不能假设托管服务将提供对操作系统的访问以安装加密随机数模块等,所以我正在寻找一种方法在 PHP 本身中生成安全的随机数。

The solution I've come up with so far involves storing an initial seed, then for each call,

到目前为止,我提出的解决方案涉及存储一个初始种子,然后对于每次调用,

result = seed
seed = sha512(seed . mt_rand())

This is based on the security of the sha512 hash function (the mt_rand call is just to make life a little more difficult for an adversary who obtains a copy of the database).

这是基于 sha512 哈希函数的安全性(mt_rand 调用只是为了让获得数据库副本的对手更加困难)。

Am I missing something, or are there better known solutions?

我错过了什么,还是有更知名的解决方案?

回答by Einstein

I strongly recommend targeting /dev/urandom on unix systems or the crypto-api on the windows platform as an entropy source for passwords.

我强烈建议将 unix 系统上的 /dev/urandom 或 windows 平台上的 crypto-api 作为密码的熵源。

I can't stress enough the importance of realizing hashes are NOT magical entropy increasing devices. Misusing them in this manner is no more secure than using the seed and rand() data before it had been hashed and I'm sure you recognize that is not a good idea. The seed cancels out (deterministic mt_rand()) and so there is no point at all in even including it.

我再怎么强调实现哈希不是神奇的熵增加设备的重要性。以这种方式滥用它们并不比在散列之前使用种子和 rand() 数据更安全,我相信您认识到这不是一个好主意。种子抵消了(确定性的 mt_rand()),因此即使包括它也没有任何意义。

People think they are being smart and clever and the result of their labor are fragile systems and devices which put the security of their systems and the security of other systems (via poor advice) in unecessary jeopardy.

人们认为他们很聪明,他们劳动的结果是脆弱的系统和设备,这些系统和设备将他们系统的安全性和其他系统的安全性(通过糟糕的建议)置于不必要的危险之中。

Two wrongs don't make a right. A system is only as strong as its weakest part. This is not a license or excuse to accept making even more of it insecure.

两个错误不等于一个正确。一个系统的强大取决于它最薄弱的部分。这不是接受使其更加不安全的许可或借口。



Here is some PHP code to obtain a secure random 128-bit string, from this comment at php.net by Mark Seecof:

以下是一些 PHP 代码,用于获取安全的随机 128 位字符串,来自Mark Seecof 在 php.net 的评论

"If you need some pseudorandom bits for security or cryptographic purposes (e.g.g., random IV for block cipher, random salt for password hash) mt_rand() is a poor source. On most Unix/Linux and/or MS-Windows platforms you can get a better grade of pseudorandom bits from the OS or system library, like this:

<?php
// get 128 pseudorandom bits in a string of 16 bytes

$pr_bits = '';

// Unix/Linux platform?
$fp = @fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
    $pr_bits .= @fread($fp,16);
    @fclose($fp);
}

// MS-Windows platform?
if (@class_exists('COM')) {
    // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
    try {
        $CAPI_Util = new COM('CAPICOM.Utilities.1');
        $pr_bits .= $CAPI_Util->GetRandom(16,0);

        // if we ask for binary data PHP munges it, so we
        // request base64 return value.  We squeeze out the
        // redundancy and useless ==CRLF by hashing...
        if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
    } catch (Exception $ex) {
        // echo 'Exception: ' . $ex->getMessage();
    }
}

if (strlen($pr_bits) < 16) {
    // do something to warn system owner that
    // pseudorandom generator is missing
}
?>

NB: it is generally safe to leave both the attempt to read /dev/urandom and the attempt to access CAPICOM in your code, though each will fail silently on the other's platform. Leave them both there so your code will be more portable."

“如果您出于安全或加密目的需要一些伪随机位(例如,用于分组密码的随机 IV,用于密码散列的随机盐)mt_rand() 是一个糟糕的来源。在大多数 Unix/Linux 和/或 MS-Windows 平台上,您可以获得来自操作系统或系统库的更好等级的伪随机位,如下所示:

<?php
// get 128 pseudorandom bits in a string of 16 bytes

$pr_bits = '';

// Unix/Linux platform?
$fp = @fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
    $pr_bits .= @fread($fp,16);
    @fclose($fp);
}

// MS-Windows platform?
if (@class_exists('COM')) {
    // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
    try {
        $CAPI_Util = new COM('CAPICOM.Utilities.1');
        $pr_bits .= $CAPI_Util->GetRandom(16,0);

        // if we ask for binary data PHP munges it, so we
        // request base64 return value.  We squeeze out the
        // redundancy and useless ==CRLF by hashing...
        if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
    } catch (Exception $ex) {
        // echo 'Exception: ' . $ex->getMessage();
    }
}

if (strlen($pr_bits) < 16) {
    // do something to warn system owner that
    // pseudorandom generator is missing
}
?>

注意:在您的代码中同时保留读取 /dev/urandom 的尝试和访问 CAPICOM 的尝试通常是安全的,尽管每个都会在另一个平台上静默失败。把它们都留在那里,这样你的代码就会更便携。”

回答by Eugene Dounar

You can also consider using OpenSSL openssl_random_pseudo_bytes, it's available since PHP 5.3.

您也可以考虑使用 OpenSSL openssl_random_pseudo_bytes,它从 PHP 5.3 开始可用。

 string openssl_random_pseudo_bytes ( int $length [, bool &$crypto_strong ] )

Generates a string of pseudo-random bytes, with the number of bytes determined by the length parameter. It also indicates if a cryptographically strong algorithm was used to produce the pseudo-random bytes, and does this via the optional crypto_strong parameter. It's rare for this to be FALSE, but some systems may be broken or old.

生成一串伪随机字节,字节数由长度参数决定。它还指示是否使用加密强算法来生成伪随机字节,并通过可选的 crypto_strong 参数执行此操作。这是 FALSE 的情况很少见,但某些系统可能已损坏或陈旧。

http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php

http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php

Since PHP 7 there is also random_bytesfunction available

从 PHP 7 开始,还有random_bytes可用的函数

string random_bytes ( int $length )

http://php.net/manual/en/function.random-bytes.php

http://php.net/manual/en/function.random-bytes.php

回答by Scott Arciszewski

PHP ships with a new set of CSPRNG functions (random_bytes()and random_int()). It's trivial to turn the latter function into a string generator function:

PHP 附带了一组新的 CSPRNG 函数(random_bytes()random_int())。将后一个函数变成字符串生成器函数很简单:

<?php
/**
 * Generate a random string, using a cryptographically secure 
 * pseudorandom number generator (random_int)
 * 
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 * 
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str(
    $length,
    $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
) {
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    if ($max < 1) {
        throw new Exception('$keyspace must be at least two characters long');
    }
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

If you need to use this in a PHP 5 project, feel free to grab a copy of random_compat, which is a polyfill for these functions.

如果您需要在 PHP 5 项目中使用它,请随意获取random_compat的副本,它是这些函数的polyfill