内容简介: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_rand
, srand
同 mt_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_int
和 random_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.c
中 static int php_handler(request_rec *r)
函数可以看到,
ctx = SG(server_context);
parent_req = ctx->r;
...
if (!parent_req) {
php_apache_request_dtor(r);
...
只有在 parent_req
为 NULL
的情况下,才会运行到 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.c
中 int main(int argc, char *argv[])
函数可以看到, php-fpm
的进程会循环处理请求,请求结束后调用 php_request_shutdown
函数进行清理。因此,在 php-fpm
的环境下,每个请求用的都是一个新的种子。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 区块链随机数的实现:墨客随机数子链RandDrop(强随机数和真随机数)_作者陈小虎
- Java随机数探秘
- Java 随机数探秘
- [译] 随机数与区块链
- 无法预测的根源——随机数
- 【Android杂谈】生成随机数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Making Things See
Greg Borenstein / Make / 2012-2-3 / USD 39.99
Welcome to the Vision Revolution. With Microsoft's Kinect leading the way, you can now use 3D computer vision technology to build digital 3D models of people and objects that you can manipulate with g......一起来看看 《Making Things See》 这本书的介绍吧!