- UID
- 1
- 精华
- 积分
- 76365
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
网址:https://www.0xaa55.com/thread-16718-1-1.html
转载请保留出处。
前言:一般的随机数如果被你拿去用于生成随机密码的话,攻击者可以通过遍历随机数种子来猜出你的密码。攻击者只需要推出你的随机函数的算法,就可以通过遍历随机数种子等无数种方法猜出你生成的随机数或者随机字符串。所以我们需要一个符合密码安全的伪随机数生成器。它的名字叫CSPRNG。
其次,这个github的库现在应该没以前那么弱了吧。。
名词介绍:
CSPRNG - 符合密码安全的伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator)
标题上说的,是指php-csprng。这是一个“密码安全”的随机数生成器库,用PHP写的。
链接:https://github.com/cubiclesoft/php-csprng/blob/master/support/random.php
该项目有一个网站,明显善意的作者解释了他在寻找熵源方面的困难:用纯PHP写CSPRNG实际上比人们想象的困难很多。因为PHP目前没有提供单一的一个函数用来保证它有足够的熵可以在所有的Web主机上运行,并且PHP的网站有成吨的错误消息。这一部分是PHP自己的设计错误,因为PHP是一个极其灵活的跨平台工具,能在各种主机上运行。当涉及PHP的应用程序安全性时,它的优点和缺点就在于此。 他说得很好。我们必须开始将密码原语构建到我们的语言中,特别是CSPRNG。没有一个你就不能做密码学。
作者还提到对于如何取得熵的文档很缺乏:对于造一个CSPRNG轮子,最大的问题是几乎找不到任何有关取得熵的文档。很多文档都说了“如果我有熵我就能XXXX”但对于如何得到熵,几乎没有文档有说明。 为啥没有文档?因为如果没有运行环境提供支持的话,你并没有任何办法可以生成安全的随机数。除非你有个硬件真随机数生成器,否则收集熵需要时间,比如各种、读取硬盘磁道、按键、网络包裹、鼠标移动等。对于PHP这样的高级跨平台脚本语言你不能这么做。你能得到的唯一的资料就是:别这么干。
在PHP里如果你要造CSPRNG,一个靠谱的方式是:
- 判断你的系统是否给你提供CSPRNG
- 是的话就用它,否则拒绝提供随机数,除非别人给你装了个靠谱的。
废话到此为止。让我们来看一下代码中的一些实际问题。
下载在线密码生成器
代码从系统信息(内存使用情况、php版本等)收集熵,用户输入($_GET和$_POST数组等),openssl_random_pseudo_bytes()(能用的情况下),或者下载在线密码生成器。然后这些东西被做成哈希,用于生成器种子。- function RSG_GetRootSeedURLs()
- {
- $result = array(
- "https://www.random.org/integers/?num=100&min=0...
- "https://www.fourmilab.ch/cgi-bin/Hotbits?nbytes=128...
- "https://www.grc.com/passwords.htm"...
- );
- }
复制代码 如果平台不提供openssl_random_pseudo_bytes()的支持,这就很难受了。很明显,要是机器没法上网,或者在线生成器屏蔽了这个脚本的请求,它就会回退到从系统信息到用户输入收集非常弱的熵。
更糟糕的是,攻击者可以故意中断它与在线生成器之间的连接,彻底消除这个熵源,或者强制脚本回退到未加密的连接(请相信,它确实是这么做的)
并且就算这些在线生成器能用,这个脚本假设这些在线生成器生成的随机数靠谱,并且不会把它暴露给任何人。这可真是一个非常大的假设。
泄漏用户输入验证器
这玩意儿的另一个问题是它使用用户输入作为熵源。这虽然具有一定的随机性,但这种做法并不靠谱。如果攻击者能猜出熵源,他们能通过生成器的输出值来猜出用户的输入。
比如,你用这玩意儿生成一个盐,用于和一个长的scrypt计算来一起把用户的密码给“哈希”了,那么这个攻击者他只需要知道盐,就可以判断密码(通过用一个密码作为输入来取一个随机数,然后判断它是否等于盐),他并不需要被迫爆破scrypt哈希碰撞。
如果你有其它的靠谱的熵源,那么用这个作为用户输入的附加源是OK的(而且推荐这么做)。然而,这份代码它并没有,所以在这里它并不OK。这只是一个小问题,但这是一个很好的CSPRNG设计需要考虑的问题。
流发生器
当种子生成了,它就会被送进一个自制的基于哈希的确定性流生成器。这是一个反复的过程:- next number = SHA1(mt_rand() || ctr || previous_number || seed);
复制代码 在这里,||意味着并列,mt_rand是PHP的非密码学安全伪随机数生成器,ctr是每次迭代后+1的数字,显然seed是种子。此外如果SHA512可用,它就会使用SHA512来换掉SHA1。
这玩意儿并不安全。攻击者可以获知除了种子以外的所有东西(ctr和mt_rand()是可预测的)。为了预测下一个数字,他们必须在不知道种子的情况下计算哈希。说白了就是攻击者知道SHA1(X || Y)和X,并且需要去计算SHA1(Z || Y)。
事实证明哈希函数(碰撞阻力)并不能保证这么做很困难。在这个特殊的例子中,我没那么聪明,不知道这是怎么做到的,但是在非常相似的情况下(当对手可以选择X而不是选择它们中的一个的话),可以通过长度扩展攻击来轻易实现攻击。所以这种构造并不安全。
你不应该自己发明流生成器。使用CTR模式的AES或者一个靠谱的流密码,并且经常更换种子。或者最好你还是使用平台自带CSPRNG而不是自己造一个。如果你的平台的CSPRNG坏掉了,你就啥也干不了,这并不怪你。
“正态”分布发生器
值得庆幸的是,这部分代码似乎没有被使用,但是这很奇葩搞得我不得不提起它。RSG_NormalizedStream类应该生成“正态分布的随机数”。
这类玩意儿卵用没有。它实际就是重复生成$min到$max之间的数字的随机排列。$balancer数组里面填充了$min到$max之间的数字,然后当你选了一个后,它就被从数组里删除,以使其无法被重复选中。- public function RandomInt($entropy = "")
- {
- $num = $this->randgen->RandomInt(
- 0,
- count($this->balancer) - 1,
- $entropy . $this->counter
- );
- $entropy = "";
- $this->counter++;
- $result = $this->balancer[$num];
- array_splice($this->balancer, $num, 1);
- if (!count($this->balancer))
- {
- $range = $this->max - $this->min;
- for ($x = 0; $x <= $range; $x++)
- $this->balancer[$x] = $this->min + $x;
- }
- return $result;
- }
复制代码 如果我们使用这玩意儿生成1到9(包括了1和9)之间的随机数,则可以看到每个集合都是“123456789”的随机排列。
6, 1, 4, 5, 8, 3, 7, 2, 0
9, 3, 7, 4, 9, 8, 2, 0, 1
5, 6, 3, 6, 1, 9, 0, 5, 2
这玩意儿与CSPRNG没有任何关系。有人可能会意外用到它,而且希望它的输出是安全的。
结论
这代码里估计有更多的错误,我都懒得去找了。所以总而言之,我们了解到:- 使用平台自带CSPRNG。
- 别让别人瞎用你的代码、泄漏你的代码。
- 不要用敏感信息作为随机数种子,除非你有其它的可靠熵源。否则,你的随机数生成器可能会泄漏一个验证器。
- 使用经过深入研究的玩意儿。
参考资料:
How not to CSPRNG
http://www.cryptofails.com/post/72902772336/how-not-to-csprng
php-csprng
https://github.com/cubiclesoft/php-csprng/blob/master/support/random.php
CSPRNG Documentation
http://barebonescms.com/documentation/csprng/
scrypt
https://en.wikipedia.org/wiki/Scrypt
长度扩展攻击
https://en.wikipedia.org/wiki/Length_extension_attack
|
|