【问题标题】:Generate cryptographically secure random numbers in php在 php 中生成加密安全的随机数
【发布时间】:2009-06-24 23:39:50
【问题描述】:

PHP 的rand() 函数不能提供好的随机数。所以我开始使用mt_rand(),据说效果更好。但这些结果有多好?有什么方法可以再次改进吗?

我的想法:

function rand_best($min, $max) {
    $generated = array();
    for ($i = 0; $i < 100; $i++) {
        $generated[] = mt_rand($min, $max);
    }
    shuffle($generated);
    $position = mt_rand(0, 99);
    return $generated[$position];
}

这应该给你“完美”的随机数,不是吗?

【问题讨论】:

标签: php security random


【解决方案1】:

Pseudorandom number generators (PRNG) 是非常复杂的野兽。

没有真正的“完美”随机数生成器——事实上,数学函数所能做的最好的就是伪随机——它们对于大多数意图和目的来说似乎足够随机。

事实上,从 PRNG 返回的数字执行任何附加操作并不会真正增加其随机性,事实上,数字可能会变得不那么随机。

所以,我最好的建议是,不要乱用 PRNG 返回的值。使用足以满足预期用途的 PRNG,如果不是,则在必要时找到可以产生更好结果的 PRNG。

坦率地说,mt_rand 函数似乎使用了Mersenne twister,这是一个相当不错的 PRNG,因此对于大多数休闲用途来说可能已经足够了。

但是,Mersenne Twister 并非设计用于任何安全环境。请参阅this answer,了解当您需要随机性以确保安全时使用的解决方案。

编辑

cmets 中有一个问题,为什么对随机数执行操作会降低随机数。例如,一些 PRNG 可以在位的不同部分返回更一致、更少的随机数——高端可能比低端更随机。

因此,在丢弃高端并返回低端的操作中,该值可能会变得比从 PRNG 返回的原始值更随机。

目前我找不到很好的解释,但我基于 Random.nextInt(int) 方法的 Java 文档,该方法旨在在指定范围内创建一个相当随机的值。该方法考虑了值的各个部分的随机性差异,因此与rand() % range等更幼稚的实现相比,它可以返回更好的随机数。

【讨论】:

  • 它如何变得“不那么随机”?
  • karim:由于所有 PRNG 都基于仅生成明显随机数的计算,因此存在某些缺点。原则上,生成“随机”数字与计算机可以做的一切背道而驰,因为计算机在设计上是确定性机器。根据对数字执行哪些计算以生成随机位,有一些在统计上表现出良好的随机性,而另一些则没有。不过,这取决于您的发电机。 LCG 的低位较弱,其他类型的高位较弱,其他类型甚至没有任何弱点。这是一个非常复杂的话题。
  • coobird:Java 的 Random.nextInt(int) not 满足较弱的位。它的作用是消除由于模运算而导致的偏差,因为模运算更喜欢较小的值。我花了两天时间才明​​白为什么它会起作用,但它做得非常优雅。但是 600 个字符太短了。丢弃低位的部分(因为随机性较小)在 Random.next(int) 中完成:return (int)(seed >>> (48 - bits))。如您所见,LCG 的 48 位中最多返回 32 位,这很好地消除了“弱”低位。
  • 非常感谢。所以我将只使用 mt_rand()。我认为有一些可能性可以提高随机性,但显然没有。
  • marco:如果有疑问,就不要。干预伪随机数需要非常小心,以免破坏结果。如果您对自己在做什么一无所知,您很可能会让事情变得更糟。
【解决方案2】:

快速回答:

在新的PHP7 中,终于有了对cryptographically secure pseudo-random integers. 的支持

int random_int ( int $min , int $max )

还有一个polyfill for PHP5x

更长的答案


没有完美的随机数生成器,计算机使用pseudorandom number generator 来创建看起来随机的序列。序列看起来是随机的(并且通过了一些randomness tests),但是因为有一些算法可以生成它,所以您可以以完全相同的状态重复算法并获得相同的结果。

cryptography“不要发明自己的密码”相同的建议可以翻译为随机数生成器,这意味着您不能只是将大量随机数生成器组合在一起并期望得到更好的生成器.


随机数生成器的一个子集是cryptographically secure random number generators

普通 PRNG 的要求也满足 加密安全的 PRNG,但反之则不然。 CSPRNG 要求分为两组:第一,他们通过统计 随机性测试;其次,他们在严重的情况下坚持得很好 攻击,即使它们的初始或运行状态的一部分变为 可供攻击者使用

所以这非常接近您对“完美”的定义。再一次,在任何情况下(除了学习如何进行加密),您都应该尝试实现其中一种算法并在您的系统中使用它。


不过幸运的是PHP7已经实现了,

int random_int ( int $min , int $max )

生成适合使用的加密随机整数 公正的结果至关重要(即洗牌扑克牌)。

随机的来源如下:

  • 在 Windows 上仅使用 CryptGenRandom()
  • arc4random_buf() 可用时使用(通常特定于 BSD)
  • /dev/arandom 在可用的地方使用
  • getrandom(2) 系统调用(在较新的 Linux 内核上)
  • /dev/urandom 用于以上都不可用的地方

这使得之前的所有答案都过时了(并且有些已弃用)。

【讨论】:

  • openssl_random_pseudo_bytes 自 2009/5.3.0 以来一直存在。只需将其包装在检查 strong_crypto 的东西中,例如 function f($len){$ret=openssl_random_pseudo_bytes($len,$strong);if(!$strong){throw new \RuntimeException('$crypto_strong failed!');}return $ret;}
【解决方案3】:

我不确定您所做的是否“改善”了随机性。据我了解,您生成 100 个随机数,然后随机选择其中一个。

从我的概率课程记忆中,这可能不会增加随机性,好像生成器函数 (mt_rand()) 中存在潜在偏差,那么它仍然会以某种方式反映在输出中。

【讨论】:

  • 您理解正确。感谢您的回答。现在我知道我无法通过进一步的计算来提高随机性。
  • 如果您有两种不同的 RNG 算法会怎样?那你有改善吗?
【解决方案4】:

mt_rand() 在什么方面“不好”?

例如:如果它偏爱某个数字。假设 mt_rand(1, 10) 偏爱该范围内的低数字,即“1”和“2”平均每个出现超过 10%。那么你的“改进”仍然会遇到同样的问题。

从错误的序列中选择一个随机数仍然是错误的。

【讨论】:

  • 是的,这正是我的想法。
  • +1 只是为了“从错误的序列中选择一个随机数仍然是错误的。”。
【解决方案5】:
<?php
  function random_number(){
      return 4; // return generated number
                // guaranteed to be random
  }
  ?>

抛开所有的玩笑不谈,您正在进入一个哲学问题,即什么是“随机”或什么是“最好”。理想情况下,您希望您的随机数在您的程序过程中几乎没有模式。通常系统时间用作种子,但我也使用之前的随机数作为种子,之前的随机数作为种子。问题是,如果有足够强大的计算机和对硬件运行和生成器功能的全面了解,您将能够预测生成的整个数字集。因此,如果你有一台足够强大的计算机(有些人将上帝归入这一类),它知道宇宙中所有可能的变量和功能,那么你就能够预测每一个发生或将要发生的事件。大多数随机数生成器本身都很好,但如果你认识可以看到这些模式的人,他们更有可能就像 Beautiful Mind 中的那个人,你应该让他们去诊所检查一下。

By popular demand:D

【讨论】:

  • 不知道不读 xkcd 的人会明白这个笑话。可能会更好地链接它。
  • 我只插入图片
  • 一开始我没看懂这个笑话。但是在看到链接页面上的漫画后,我当然做到了。 :D
【解决方案6】:

我编写了一个 cronjob,它定期(例如,每小时一次)从 random.org 获取 1000 个数字并将它们添加到 PHP 数组中。每当我想要脚本中的随机数时,我都会使用 mt_rand(0,1000) 从中调用一个数字。额外的几微秒开销,但我得到了基于自然大气噪声的真正随机数。

【讨论】:

  • 这引入了在刷新之前在数组中重复使用相同数字的可能性,这是一组比 32-位整数,即使千数来自更大的范围。更好的方法是将数组视为队列;根据需要添加新值,始终使用第一项并在之后丢弃它。
  • 我使用 random.org 上的标准“num=10000&min=1&max=1000000”生成了随机数,并获得了 50 个重复值。所以它似乎本质上并不是真正的随机。
【解决方案7】:

这完全取决于你需要什么随机数 :) 对我来说ShuffleBag 是最好的:)

【讨论】:

    【解决方案8】:

    编辑:我的评论不再有效。请看下面的答案:https://stackoverflow.com/a/31443898/109561


    我猜你担心 mt_rand() 的分布。我已经测试过了,它非常水平,并且包含两个界限。

    我在 php 手册中的 mt_rand() 文档的 cmets 中添加了我的测试,但由于政治过于冗长,无法进入此处,因此被愚蠢的版主删除。

    【讨论】:

    • 我还应该注意,对于真正的随机数(如果有这样的事情),随着迭代次数接近无穷大,它们将变得更加均匀分布。虽然 mt_rand() 也是如此,但从我的测试来看,平均而言,它似乎在获得更均匀的分布方面要快得多。
    • 这完全取决于随机数遵循的分布。这是一个均匀分布,那么是的。但是也有专门的 PRNG 在其他分布(正态、指数等)中生成随机数,这可以节省您更改分布的工作量(对于某些分布,没有简单/好的/有效的方法)。仅从一个维度来看,rand() 和 mt_rand() 在“随机性”方面应该看起来相当相同。它从 3 维向上开始,LCG 让事情变得有趣 :-)
    【解决方案9】:

    如果你不喜欢 PHP 内置的 rand(),你可能也不应该使用他们内置的 shuffle(),因为它似乎是建立在他们的 rand() 之上的。

    我有一半的把握现在的“行业标准”洗牌是Fisher-Yates 洗牌。

    【讨论】:

      【解决方案10】:

      没有所谓的“完美”随机数。不管你对“完美”的主观定义是什么。只能实现伪随机。

      我只是想为您指明正确的方向。你问了一个关于完美随机数的问题,即使完美是在引号中。是的,你可以提高随机性。您甚至可以实现启发式或“自然”算法,例如“大气噪声”之类的想法——但是,您仍然不是完美的,无论如何都不是。

      【讨论】:

      • 但是即使只有伪随机数,你也可以提高随机性,不是吗?
      • 随机性只是我们预测输出能力的倒数......所以一个完美的随机二进制数字只有 50% 的“随机”。要创建“真正的”随机数需要消除数字之间的可预测模式,而做到这一点的最佳方法是使用我们人类真正不太了解的过程;比如放射性衰变和大气噪声。这些来源可在线获得,请分别查找 HotBits 和 Random.org 以获取这些来源。
      【解决方案11】:

      使用 /dev/ramdom(linux 设备真随机数生成器)为 mt_rand 播种

      <?
      $rnd_dev=mcrypt_create_iv(4, MCRYPT_DEV_RANDOM); //need "apt-get install php5-mcrypt"
      $seed=ord(substr($rnd_dev, 0, 1))<<24 |
            ord(substr($rnd_dev, 1, 1))<<16 |
            ord(substr($rnd_dev, 2, 1))<<8 |
            ord(substr($rnd_dev, 3, 1));
      mt_srand($seed);
      echo mt_rand();
      ?>
      

      【讨论】:

      • 如果没有外部噪声源,计算机就无法拥有“真正的随机数生成器”,如果有,为什么要用它来播种随机性较小的源?如果您已经有了“真正的”随机数,为什么不直接使用它们呢?
      【解决方案12】:

      我做了一个 PHP 类来生成随机数和字符串PHPRandomValue

      它使用“mcrypt_create_iv(4, MCRYPT_DEV_URANDOM)”来生成随机数和值。我在从事加密项目时做到了,因为我需要一个安全的随机值生成器。这是一个示例用法

      $randomValue = new RandomValue;
      
      $randomValue->randomNumber(): = -3880998
      
      $randomValue->randomNumberBetween(1,10): = 2
      
      $randomValue->randomTextString(): = CfCkKDHRgUULdGWcSqP4
      
      $randomValue->randomTextString(10):  = LorPIxaeEY
      
      $randomValue->randomKey(): = C7al8tX9.gqYLf2ImVt/!$NOY79T5sNCT/6Q.$!.6Gf/Q5zpa3
      
      $randomValue->randomKey(10):  = RDV.dc6Ai/
      

      【讨论】:

        【解决方案13】:

        生成真正的随机数是不可能的,最好的希望是 rand() 提供的伪随机数,您的函数与 rand() 相比并不接近随机数。看看这个http://en.wikipedia.org/wiki/Random_number_generator

        【讨论】:

        • 你的意思是mt_rand(),php中的rand()函数已经被证明是有缺陷的。
        • 据我所知,mt_rand() 和 rand() 没有任何其他伪 RNG 的缺陷。
        • 不同之处在于随机数的算法和可证明的统计特性。 rand() 对于许多应用程序来说 是足够的。此外,Mersenne Twister 速度足够快,生成的数字质量更高,因此对于没有深入了解该领域的人,您可以安全地建议使用 MT19937 而不是内置的 rand()。
        • 这里只是一个小补充; rand() 一些问题的视觉指南:link
        【解决方案14】:

        Tru 随机数

        <?php
        for ($i = -1; $i <= 4; $i++) {
            $bytes = openssl_random_pseudo_bytes($i, $cstrong);
            $hex   = bin2hex($bytes);
        
            echo "Lengths: Bytes: $i and Hex: " . strlen($hex) . PHP_EOL;
            var_dump($hex);
            var_dump($cstrong);
            echo PHP_EOL;
        }
        ?>
        

        还有加密安全 ;)

        【讨论】:

          【解决方案15】:

          虽然答案在几年前就被接受了,但我会重新打开它。

          既然所有这些随机性都取决于系统时间,那么我们也来搞乱系统时间吧!操作在计算机上花费的时间实际上是相当可变的(特别是如果该服务器上正在发生其他事情),所以如果我们利用 microtime 来利用它......(找不到任何可移植的 nanotime 命令)

          $a='';
          for (int $i=0; $i<9001; $i++)
          {
              usleep(mt_rand(1000,10000));//Also eliminates timing attacks... possibly?
              $a=hash('SHA512',$a.uniqid(mt_rand().microtime(),true));
          }
          echo $a;
          

          名义上这有 207023 位的熵,因为您每次迭代都会添加另外 23 位,但是有很多相互依赖关系,所以它可能要少几个数量级。还是不错的。

          您知道 PHP 上的任何操作都需要非常随机的时间吗?比如... HTTP 请求某个网站(RANDOM.org 除外)并测量它所花费的时间?

          【讨论】:

            【解决方案16】:

            使用 random.org,你可以使用这个:

            function getToken($length, $min, $max){
                $r = explode('
            ',file_get_contents('http://www.random.org/integers/num='.$length.'&min='.$min.'&max='.$max.'&col=1&base=10&format=plain'));
            
                $string = '';
                foreach ( $r as $char ) $string.=$char;
                return $string;
            }
            

            这应该给出真正的随机数

            【讨论】:

            • 我使用 random.org 上的标准“num=10000&min=1&max=1000000”生成了随机数,并获得了 50 个重复值。所以它似乎本质上并不是真正的随机。
            猜你喜欢
            • 2021-06-18
            • 2010-11-14
            • 2015-09-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-06-23
            • 1970-01-01
            相关资源
            最近更新 更多