在Web应用程序中收集熵以创建(更多)安全随机数

时间:2012-03-27 04:23:49

标签: php web-applications cryptography prng entropy

经过几天的研究和讨论后,我想出了一种方法来收集访客的熵(你可以看到我研究的历史here

当用户访问我运行此代码时:

$entropy=sha1(microtime().$pepper.$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].
$_SERVER['HTTP_USER_AGENT'].serialize($_POST).serialize($_GET).serialize($_COOKIE)); 

注意:pepper是手工设置的每个站点/设置随机字符串。

然后执行以下(My)SQL查询:

$query="update `crypto` set `value`=sha1(concat(`value`, '$entropy')) where name='entropy'";

这意味着我们将访客请求的熵与已经收集的其他人的熵相结合。

就是这样。

然后当我们想要生成随机数时,我们将收集的熵与输出结合起来:

$query="select `value` from `crypto` where `name`='entropy'";
//...
extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).$entropy.microtime())))); 

注意:最后一行是phpseclib的crypt_rand函数的修改版本的一部分。

请告诉我您对该方案的意见以及有关熵收集/随机数生成的其他想法/信息。

ps:我知道像/ dev / urandom这样的随机源。 这个系统只是一个辅助系统或(当我们没有(访问)这些来源时)一个后备方案。

8 个答案:

答案 0 :(得分:11)

在最佳情况下,您最大的危险是本地用户披露信息漏洞。在最糟糕的情况下,整个世界都可以预测您的数据。任何可以访问相同资源的用户:相同的日志文件,相同的网络设备,相同的边界网关或运行的同一行在您和您的远程连接之间允许他们通过展开您的随机数生成器来嗅探您的流量。

他们会怎么做?为什么,information theory的基本应用和knowledge of cryptography的基本应用当然!

但你没有错误的想法!使用真正的随机来源为您的PRNG播种通常对防止上述攻击发生非常有用。例如,如果系统具有低熵或其随机性源是可重现的,那么理解how /dev/random gets populated on a per-system basis的人就可以利用这种相同级别的攻击。<​​/ p>

如果您可以充分保护熵池的流程(例如,通过安全线路从多个来源收集数据),那么随着您越来越近,某人能够收听的可能性会变得越来越小one-time pad的理想加密质量。

换句话说,不要在PHP中执行此操作,使用单个Mersenne Twister中的随机源。通过reading from your best, system-specific alternative to /dev/random正确地做到这一点,从尽可能多的安全,独特的“真实”随机来源中汲取其熵池。我知道你已经声明这些随机性来源是无法访问的,但是这个当为所有主要操作系统提供类似的功能时,这个概念很奇怪。所以,我想在这种背景下我发现“辅助系统”的概念是可疑的。

这仍然容易受到当地用户认识到你的熵源的攻击,但是保护机器并增加/dev/random内的真实熵将使他们更难以完成他们的肮脏工作没有man-in-the-middle attack

对于/dev/random确实可访问的情况,您可以非常轻松地播种:

  • 查看系统中存在哪些选项以使用/dev/hw_random
  • 拥抱rngd(或一个不错的选择)来定义您的随机性来源
  • 使用rng-tools检查和改善随机性配置文件
  • 最后,如果您需要良好,强大的随机性来源,请考虑投资in more specialized hardware.

祝您申请安全。


PS:您可能希望将来在Security.SECryptography.SE提出这样的问题!

答案 1 :(得分:4)

使用Random.Org

如果您需要真正随机的数字,请使用random.org。这些数字是通过大气噪声生成的。除library for PHP之外,它还有一个http interface,可让您通过简单的请求获得真正随机的数字:

https://www.random.org/integers/?num=10&min=1&max=6&col=1&base=10&format=plain&rnd=new

这意味着您可以简单地检索PHP中的实际随机数 ,而无需服务器上的任何其他PECL张力。

如果您不希望其他用户能够“窃取”您的随机数(正如MrGomez所说),只需使用https进行证书检查即可。以下是https证书检查的示例:

$url = "https://www.random.org/integers/?num=10&min=1&max=6&col=1&base=10&format=plain&rnd=new";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($ch);
if ($response === FALSE)
        echo "http request failed: " . curl_error($ch);
else
        echo $response;    
curl_close($ch);

如果您需要有关如何创建https请求的更多信息:

有关安全性的更多信息

同样,有些人可能会争辩说,如果攻击者与您同时查询random.org,他可能会获得相同的数字并预测..我不知道random.org是否会以这种方式工作,但如果你真的很担心,你可以通过愚弄攻击者来愚弄你丢弃的虚假请求,或者只使用你得到的随机数的某一部分来减少机会。

正如戈麦斯先生在评论中指出的那样,这不应被视为对安全的最终解决方案,而只是作为可能的熵来源之一。

效果

当然,如果你需要一个突击延迟,那么每个客户端请求做一个random.org请求可能不是最好的想法......但是如何做one bigger request来预先缓存随机数,就像每个5分钟?

答案 2 :(得分:1)

要说明一点,据我所知,没有办法在PHP脚本中生成委托,对不起这个非答案。即使你看一下像phppass那样很好的脚本,你也会看到,他们的后备系统无法发挥一些魔力。

问题是,无论你是否应该尝试。由于您希望在GPL下发布系统,因此您可能不知道它将在何种情况下使用。在我看来,最好是需要一个随机源,或者快速失败(死掉一个适当的错误信息),所以想要使用你的系统的开发人员立即知道存在问题

要从随机来源中读取,您可以调用mcrypt_create_iv()函数...

$randomBinaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);

...此函数从操作系统的随机池中读取。从PHP 5.3开始,它也在Windows服务器上运行,因此您可以将其留给PHP来处理随机源。

答案 3 :(得分:0)

如果您有权访问/ dev / urandom,可以使用:

function getRandData($length = 1024) {
    $randf  = fopen('/dev/urandom', 'r');
    $data   = fread($randf, $length);
    fclose($randf);
    return $data;
}

更新: 当然,如果打开设备失败,你应该有一些备份

答案 4 :(得分:0)

更新2 代码审核警告所有人:不要使用原始问题中的代码。这是一种安全责任。如果此代码在任何地方联机删除,因为它将整个系统,网络和数据库打开给恶意用户。您不仅会公开您的代码,还会泄露您的所有用户数据。

不要序列化用户输入。如果在您的代码中已经这样做了,请停止服务器并更改代码。这是一个很好的例子,没有自己做加密。

更新1 :为了获得真正的安全性,您需要在熵中包含 UN-guessable randomess 。添加熵的合适选项有问题参考是使用脚本执行时间的增量 microtime()本身。因为Delta Rely对您的服务器负载。硬件环境,温度,网络负载,电源负载,磁盘访问,CPU使用和电压波动的组合也是不可预测的。

使用Time(),时间戳或微量时间是您实施中的一个缺陷。

脚本执行Delta Exemple代码:

@martinstoeckli正确地说明了适用于加密的随机生成来自

 mcrypt_create_iv($lengthinbytes, MCRYPT_DEV_URANDOM);

但超出了没有加密模块的要求

在SQL中,将 RAND()与生成的数字结合使用。 http://www.tutorialspoint.com/mysql/mysql-rand-function.htm

Php也提供 Rand()功能

http://php.net/manual/en/function.rand.php

他们不会给你相同的号码,所以你可以使用它们。

答案 5 :(得分:0)

如果您有权访问客户端,您可以启用鼠标移动跟踪 - 这就是真正的密码用于额外的熵级别。

答案 6 :(得分:0)

正如我之前所说,我的rand函数是phpseclib的crypt_random函数的修改版本。 你可以在我的第一篇文章中给出的链接中看到它。至少phpseclib加密库的作者证实了这一点;普通应用还不够?我不是说极端/理论上的安全性,只是在实际需要的范围内谈论实际安全性,同时“轻松”/“足够低成本”可用于网络上的几乎所有普通应用程序。

phpseclib的crypt_random在最坏的情况下(没有openssl_random_pseudo_bytes或urandom可用)有效而无声地回退到mt_rand(你应该知道它真的很弱),但在这种情况下我的函数使用了更安全的方案。这只是一个回归到一个计划的堕落,强制/预测其输出更加困难,并且(应该)在实践中足以满足所有普通应用/网站的需求。它使用了可能的(实际上非​​常可能并且难以预测/规避)随时间收集的额外熵,这对外人来说几乎不可能知道。它将这个可能的熵添加到mt_rand的输出(以及其他源的输出:urandom,openssl_random_pseudo_bytes,mcrypt_create_iv)。如果你被告知你应该知道,这个熵可以加上但不能减去。在(几乎肯定非常罕见)最坏的情况下,额外的熵将是0或一些太小的量。在平庸的情况下,我认为几乎所有的情况,我认为它甚至超过实际需要。 (我已经进行了大量的密码学研究,所以当我说我认为它是基于比普通程序员更加知情和科学的分析)。

查看我修改过的crypt_random的完整代码:

function crypt_random($min = 0, $max = 0x7FFFFFFF)
{
    if ($min == $max) {
        return $min;
    }

    global $entropy;

    if (function_exists('openssl_random_pseudo_bytes')) {
        // openssl_random_pseudo_bytes() is slow on windows per the following:
        // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
        if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
            extract(unpack('Nrandom', pack('H*', sha1(openssl_random_pseudo_bytes(4).$entropy.microtime()))));
            return abs($random) % ($max - $min) + $min; 
        }
    }

    // see http://en.wikipedia.org/wiki//dev/random
    static $urandom = true;
    if ($urandom === true) {
        // Warning's will be output unles the error suppression operator is used.  Errors such as
        // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
        $urandom = @fopen('/dev/urandom', 'rb');
    }
    if (!is_bool($urandom)) {
        extract(unpack('Nrandom', pack('H*', sha1(fread($urandom, 4).$entropy.microtime()))));
        // say $min = 0 and $max = 3.  if we didn't do abs() then we could have stuff like this:
        // -4 % 3 + 0 = -1, even though -1 < $min
        return abs($random) % ($max - $min) + $min;
    }


    if(function_exists('mcrypt_create_iv') and version_compare(PHP_VERSION, '5.3.0', '>=')) {
        @$tmp16=mcrypt_create_iv(4, MCRYPT_DEV_URANDOM);
        if($tmp16!==false) {
            extract(unpack('Nrandom', pack('H*', sha1($tmp16.$entropy.microtime()))));
            return abs($random) % ($max - $min) + $min;
        }
    }


    /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called.
       Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here:

       http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/

       The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro:

       http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */
    static $seeded;
    if (!isset($seeded) and version_compare(PHP_VERSION, '5.2.5', '<=')) { 
        $seeded = true;
        mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF));
    }

    extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF).$entropy.microtime()))));
    return abs($random) % ($max - $min) + $min;

}

$ entropy包含我的额外熵,它来自所有请求参数的熵组合到现在+当前请求的参数熵+在安装时手动设置的随机字符串(*)的熵。

*:长度:22,由小写和大写字母+数字组成(超过128位熵)

答案 7 :(得分:-1)

应该使用

rn_rand()而不是rand()