php里的随机数

栏目: PHP · 发布时间: 7年前

内容简介:php里的随机数

这次来填一个关于 php 随机数预测的坑。php5和php7中的随机数产生机制已经大有不同,本文将详细讲一讲这些区别和爆破种子的时候会产生的问题。

php中常用的随机数产生函数是 rand()mt_rand() 。下面将针对这两个函数展开。

php5 中的随机数

rand

php5 中的 rand 函数调用的是 glibc 中的 random() 。其实现算法可以简化为如下代码。

#include<stdio.h>

#defineMAX 1000
#defineseed 1

main() {
  int r[MAX];
  int i;

  r[0] = seed;
  for (i=1; i<31; i++) {
    r[i] = (16807LL * r[i-1]) % 2147483647;
    if (r[i] < 0) {
      r[i] += 2147483647;
    }
  }
  for (i=31; i<34; i++) {
    r[i] = r[i-31];
  }
  for (i=34; i<344; i++) {
    r[i] = r[i-31] + r[i-3];
  }
  for (i=344; i<MAX; i++) {
    r[i] = r[i-31] + r[i-3];
    printf("%d\n", ((unsigned int)r[i]) >> 1);
  }
}

我们可以看到,当前的随机数与之前第31个和之前第3个有关。因此只要获取了连续31个随机数,就有极大概率(输出的时候左移导致部分信息丢失)预测后面的随机数。

mt_rand

php5的 mt_rand 函数实现的是一个错误版本的 MT19937 随机数生成算法。

twist 函数中有个地方把变量弄错了 =。 =

对php5的 mt_rand 的攻击就是根据几个随机数序列来爆破种子,然后推测出整个随机数的序列。工具是openwall的 php_mt_seed

值得一提的是, mt_rand 可以指定随机数产生的范围 int mt_rand ( int $min , int $max ) 。此时产生随机数的方式是用一个宏来处理。

#defineRAND_RANGE(__n, __min, __max, __tmax) \
    (__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
...
number = (long) (php_mt_rand(TSRMLS_C) >> 1);
if (argc == 2) {
    RAND_RANGE(number, min, max, PHP_MT_RAND_MAX);
}
RETURN_LONG(number);

即用内置函数 php_mt_rand 产生的随机数进行乘除操作,得到位于合适范围的值。需要注意的是,无论 mt_rand 函数的调用带不带范围,其输出的结果与 php_mt_rand(TSRMLS_C) >> 1 有关。通过 mt_rand() 可以直接计算出 mt_rand(min, max) 来。

php7 中的随机数

rand

php7中的 rand 函数同 mt_randsrandmt_srand

mt_rand

从 php 7.1.0 开始, mt_rand 函数修复了之前对 MT19937 实现上的错误,但是仍然保留了错误版本的随机数生成方式。详情见 mt_srand 的文档。

  • MT_RAND_MT19937 Uses the fixed, correct, Mersenne Twister implementation, available as of PHP 7.1.0.
  • MT_RAND_PHP Uses an incorrect Mersenne Twister implementation which was used as the default up till PHP 7.1.0. This mode is available for backward compatibility.

除了修复 twist 函数的问题之外,在产生一个范围的随机数的时候,php7和php5的行为也不一致。

if (argc == 0) {
    // genrand_int31 in mt19937ar.c performs a right shift
    RETURN_LONG(php_mt_rand() >> 1);
}
...
RETURN_LONG(php_mt_rand_common(min, max));
...
if (BG(mt_rand_mode) == MT_RAND_MT19937) {
    return php_mt_rand_range(min, max);
}
...
umax++;
...
result = php_mt_rand();
...
return (zend_long)((result % umax) + min);

根据代码我们可以看到, mt_rand 函数不带范围的时候,输出的结果为 php_mt_rand() >> 1 ;而带范围的时候,输出的结果为 (php_mt_rand() % (max-min+1)) + min 。通过 mt_rand() 不可以直接计算出 mt_rand(min, max) ,存在一些误差。

由于上述原因,之前用来爆破随机数种子的 工具 已经无法直接使用。我自己实现了一个多线程爆破种子的c程序,在4核2G内存的虚拟机上跑了大概4个小时才将种子遍历完毕。

random_int / random_bytes

php7 提供了更加安全的随机数产生函数, random_intrandom_bytes 调用了系统的一些安全的随机数产生函数来输出。

  • On Windows, » CryptGenRandom() will always be used.
  • On Linux, the » getrandom(2) syscall will be used if available.
  • On other platforms, /dev/urandom will be used.
  • If none of the aforementioned sources are available, then an Exception will be thrown.

php的运行模式对随机数产生的影响

Apache2handler

/sapi/apache2handler/sapi_apache2.cstatic int php_handler(request_rec *r) 函数可以看到,

ctx = SG(server_context);
parent_req = ctx->r;
...
if (!parent_req) {
		php_apache_request_dtor(r);
        ...

只有在 parent_reqNULL 的情况下,才会运行到 php_apache_request_dtor ,调用 php_request_shutdown ,这个函数会调用注册的 PHP_RSHUTDOWN_FUNCTION ,导致随机数的种子被标记为未初始化。

在Apache下,一个 Connection 中的所有 request 都交给一个 Apache 的进程处理。很可能没有调用到 php_apache_request_dtor 导致在一个 Connection 中的请求共用一个种子。(未测试)

php-fpm

/sapi/fpm/fpm/fpm_main.cint main(int argc, char *argv[]) 函数可以看到, php-fpm 的进程会循环处理请求,请求结束后调用 php_request_shutdown 函数进行清理。因此,在 php-fpm 的环境下,每个请求用的都是一个新的种子。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机程序设计艺术

计算机程序设计艺术

Donald E. Knuth / 李伯民、范明、蒋爱军 / 人民邮电出版社 / 2016-1-1 / 198

《计算机程序设计艺术》系列是公认的计算机科学领域经典之作,深入阐述了程序设计理论,对计算机领域的发展有着极为深远的影响。本书是该系列的第 1 卷,讲解基本算法,其中包含了其他各卷都需用到的基本内容。本卷从基本概念开始,然后讲述信息结构,并辅以大量的习题及答案。一起来看看 《计算机程序设计艺术》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码