关于安全性:在PHP中安全随机数生成

Secure random number generation in PHP

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

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

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

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

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


我强烈建议在UNIX系统上以/dev/urandom为目标,或在Windows平台上以加密API为密码的熵源。

我不能过分强调实现散列的重要性,它不是神奇的熵增加装置。以这种方式误用它们并不比在散列之前使用seed和rand()数据更安全,我相信您肯定认识到这不是一个好主意。种子会取消(确定性mt_rand()),因此即使包括它也没有任何意义。

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

两个错误不等于一个正确。一个系统只有最薄弱的部分那么强大。这不是一个许可证或借口接受使更多的它不安全。

下面是一些获取安全随机128位字符串的PHP代码,来自下面的mark seecf在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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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."


您还可以考虑使用openssl openssl_random_pseudo_字节,它是从php 5.3开始提供的。

1
 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.

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

由于php 7,还提供了random_bytes函数

1
string random_bytes ( int $length )

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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;
}

如果您需要在php 5项目中使用它,请随意获取一个随机兼容的副本,它是这些函数的polyfill。