str_shuffle和随机性

16

之前我写了一个随机字符串生成器,它使用mt_rand()函数在一个字符串中选择第n个字符,直到达到所需的长度。

public function getPassword ()
{
    if ($this -> password == '')
    {
        $pw             = '';
        $charListEnd    = strlen (static::CHARLIST) - 1;
        for ($loops = mt_rand ($this -> min, $this -> max); $loops > 0; $loops--)
        {
            $pw .= substr (static::CHARLIST, mt_rand (0, $charListEnd), 1);
        }
        $this -> password   = $pw;
    }
    return $this -> password;
}

(CHARLIST是一个包含密码字符池的类常量。$min和$max是长度限制)

今天,在研究完全不同的东西时,我偶然发现了以下代码:

function generateRandomString ($length = 10) {    
    return substr(str_shuffle ("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
}

这个方法实现了与我的循环 mt_rand() 的代码相同的效果,只是用了一行。我非常喜欢它,因为代码越少越好。 :)

但是当我查阅 PHP 手册中的 str_shuffle 时,发现文档内容比较简洁。我真正想知道的一件事是,它使用什么算法来生成随机数?手册没有提到获取洗牌字符串所进行的随机化类型是什么。如果它使用 rand() 而不是 mt_rand(),那么坚持使用我当前的解决方案可能会更好。

因此,我想知道 str_shuffle 如何随机排列字符串。它使用 rand() 还是 mt_rand()?我正在使用我的随机字符串函数来生成密码,因此随机性的质量很重要。

更新:如已指出的,str_shuffle 方法与我已经使用的代码并不等价,并且由于字符串的字符保持相同,只是其顺序改变,因此会更不随机。然而,我仍然好奇 str_shuffle 函数如何对其输入字符串进行随机化。


这两个函数不相同。使用str_shuffle()的版本不会有任何重复,所以即使RNG相同,它也不太随机。 - Barmar
你说得对,我刚意识到这一点。 - GordonM
1
@Barmar 换句话说,RTFS?这通常是一个陈词滥调的答案。 - GordonM
这就是为什么我把它放在注释里而不是答案里的原因。 - Barmar
str_shuffle() 在内部使用 rand()。它是一个弱 PRNG,可预测,并且只有 2^32 种可能的输出。 - Scott Arciszewski
3个回答

37

更好的解决方案是使用mt_rand,它使用梅森旋转算法,效果更好。

正如已经指出的那样,str_shuffle方法与我已经使用的代码不等同,并且由于字符串的字符保持不变,只是顺序改变,因此将不太随机。但是我仍然好奇str_shuffle函数如何随机化其输入字符串。

为了使输出相等,让我们只使用0,1并查看每个函数的可视化表示

简单的测试代码

header("Content-type: image/png");
$im = imagecreatetruecolor(512, 512) or die("Cannot Initialize new GD image stream");
$white = imagecolorallocate($im, 255, 255, 255);
for($y = 0; $y < 512; $y ++) {
    for($x = 0; $x < 512; $x ++) {
        if (testMTRand()) { //change each function here 
            imagesetpixel($im, $x, $y, $white);
        }
    }
}
imagepng($im);
imagedestroy($im);

function testMTRand() {
    return mt_rand(0, 1);
}

function testRand() {
    return rand(0, 1);
}

function testShuffle() {
    return substr(str_shuffle("01"), 0, 1);
}

输出 testRand()

输入图像描述

输出 testShuffle()

输入图像描述

输出 testMTRand()

输入图像描述

基本上,我想知道 str_shuffle 如何随机字符串。它是使用 rand() 还是 mt_rand()?我的随机字符串函数用于生成密码,因此随机性的质量很重要。

您可以清楚地看到,str_shuffle 几乎产生与 rand 相同的输出...


6
有点过于 pedantic(追求细节)了,不同算法可以有相同的输出。它们也可能只在范围为 [0,1] 时表现相同,但这种情况非常罕见。无论如何,加一分。我对漂亮的图片情有独钟 :)。 - Corbin
1
testShuffle 的输出结果与 testRand 几乎完全不同,实际上它们产生了相反的输出(在您的测试中) :-) - OlavJ
为什么要设置不同的y坐标?当对齐时(x和y坐标都为0),该网站验证了我所说的:它们完全相反。 - OlavJ
mt_rand 用于模拟目的是可以的,但对于安全来说并不是一个好选择。使用 mt_rand 生成密码几乎和使用 rand 一样糟糕。它的种子太小了,而且只观察输出的一小部分就可以预测其输出。 - CodesInChaos
1
快速更新:从PHP 7.1开始,rand()mt_rand()的别名,因此上述三种测试方法将是相同的。 - Razor
显示剩余7条评论

3

请注意,如果您的应用程序真正关注安全性,则不应使用此方法。梅森旋转器(Mersenne Twister)并不具有加密安全性。伪随机数生成器(PRNG)可以产生在统计上看起来是随机的值,但仍然很容易被破解。


0

虽然仍不具备加密安全性,但以下是一种使用str_shuffle()的方法,允许字符重复,从而提高复杂性...

generate_password($length = 8, $strength = 3) {
    if ($length < 6) $length = 6;
    if ($length > 32) $length = 32;
    // Excludes [0,O,o,1,I,i,L,l,1] on purpose for readability
    $chars = 'abcdefghjkmnpqrstuvwxyz';
    if ($strength >= 2) $chars .= '23456789';
    if ($strength >= 3) $chars .= strtoupper($lower);
    if ($strength >= 4) $chars .= '!@#$%&?';
    return substr(str_shuffle(str_repeat($chars, $length)), 0, $length);
}

$chars 在字符串被洗牌之前重复出现了 $length 次,这比仅对单个字符进行洗牌要好一些。

我们只在不存储敏感信息的系统中使用此功能 ;)


一个改进的方法是逐个构建字符串,检查最后一个字符以确保不会连续出现2个相同的字符,但你明白我的意思 ;) - Mavelo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接