12

经过几天的研究和讨论,我想出了这种从访问者那里收集熵的方法(你可以在这里查看我的研究历史)

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

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

注意:胡椒是手动设置的每个站点/设置的随机字符串。

然后我执行以下(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 这样的随机源。这个系统只是一个辅助系统或(当我们没有(访问)这些资源时)一个后备方案。

4

8 回答 8

11

在最好的情况下,您最大的危险是本地用户泄露信息漏洞。在最坏的情况下,全世界都可以预测您的数据。任何有权访问与您相同的资源的用户:相同的日志文件、相同的网络设备、相同的边界网关或在您和您的远程连接之间运行的同一条线路允许他们通过展开您的随机数来嗅探您的流量发电机。

他们会怎么做?为什么,当然是信息论的基本应用一点密码知识

不过,你没有错!用真正的随机源播种你的 PRNG 通常对于防止上述攻击的发生非常有用。例如,如果系统具有低熵或其随机源是可重现的,那么了解如何/dev/random在每个系统的基础上填充的人可以利用这种相同级别的攻击。

如果您能够充分保护为您的熵池播种的过程(例如,通过通过安全线路从多个来源收集数据),那么随着您越来越接近理想,有人能够监听的可能性会越来越小一次性密码本的加密特性。

换句话说,不要在 PHP 中执行此操作,而是将单个随机源输入到单个 Mersenne twister 中。正确地做到这一点,通过阅读你最好的、特定于系统的替代方案/dev/random,从尽可能多的安全、不同的“真实”随机性来源中播种它的熵池。我知道您已经说过这些随机性来源是不可访问的,但是当所有主要操作系统都提供类似的功能时,这个概念很奇怪。所以,我想我发现在这种情况下“辅助系统”的概念是可疑的。

这仍然很容易受到知道您的熵来源的本地用户的攻击,但是保护机器并增加其中的真实熵将使他们在没有人的情况/dev/random下更难以完成肮脏的工作-中间攻击

至于/dev/random确实可以访问的情况,您可以很容易地播种:

  • 查看您的系统上有哪些选项可供使用/dev/hw_random
  • 拥抱rngd(或一个很好的选择)来定义你的随机性来源
  • 用于rng-tools检查和改进您的随机性配置文件
  • 最后,如果您需要一个好的、强大的随机性来源,请考虑投资更专业的硬件。

祝您在保护您的应用程序时好运。


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

于 2012-03-31T23:31:09.410 回答
4

使用 Random.Org

如果您需要真正的随机数,请使用random.org。这些数字是通过大气噪​​声产生的。除了PHP 库之外,它还有一个http 接口,允许您通过简单的请求获取真正的随机数:

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 请求可能不是最好的主意……但是如果只执行一个更大的请求以像每 5 分钟那样预缓存随机数呢?

于 2012-04-02T21:00:41.730 回答
1

说到这一点,据我所知,没有办法在 PHP 脚本中生成熵,对此没有答案感到抱歉。即使您查看完善的脚本(例如phppass),您也会看到,它们的后备系统无法发挥作用。

问题是,你是否应该尝试一下。由于您想在 GPL 下发布您的系统,您可能不知道它将在什么场景中使用。在我看来,最好是要求一个随机源,或者快速失败(因适当的错误消息而死),以便想要使用您的系统的开发人员立即知道存在问题。

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

$randomBinaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);

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

于 2012-04-07T13:17:39.090 回答
0

如果您可以访问客户端,则可以启用鼠标移动跟踪 - 这是真正的 crypt 用于额外熵级别的方法。

于 2012-04-07T19:21:59.603 回答
0

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

在最坏的情况下(没有可用的 openssl_random_pseudo_bytes 或 urandom),phpseclib 的 crypt_random 有效且默默地回退到 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位)

于 2012-04-10T17:26:22.023 回答
0

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

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

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

于 2012-04-06T09:21:19.193 回答
0

更新 2对所有人的代码审查警告:不要使用原始问题中的代码。这是安全责任。如果此代码在任何地方都在线删除它,因为它会向恶意用户打开整个系统、网络和数据库。您不仅会暴露您的代码,还会暴露您的所有用户数据。

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

更新 1:为了真正的安全,你需要在你的熵中有不可猜测的 随机性。添加熵的合适选项有您的问题参考是使用脚本执行时间的 Delta 而 不是 microtime() 本身。因为 Delta 依赖于你的服务器的负载。硬件环境、温度、网络负载、电源负载、磁盘访问、CPU 使用率和电压波动等都是不可预测的组合。

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

脚本执行 Delta 示例代码来了:

@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

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

于 2012-04-07T15:42:07.810 回答
-1

rn_rand() 应该被习惯而不是 rand()

于 2014-01-27T10:11:36.187 回答