在PHP中生成随机密码

234

我试图在PHP中生成随机密码。

然而,我得到的都是 'a',返回类型是数组类型,而我想要它是字符串类型。有什么办法可以纠正代码吗?

谢谢。

function randomPassword() {
    $alphabet = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
    for ($i = 0; $i < 8; $i++) {
        $n = rand(0, count($alphabet)-1);
        $pass[$i] = $alphabet[$n];
    }
    return $pass;
}

9
所有答案都没有使用安全的随机数生成器,而你需要它来生成密码。 - Scott Arciszewski
4
访客应该从一个可以适当更新的来源获取可能与安全有关的信息,而不是一个关闭了新回答的问题。我正在删除重复问题的答案,这样访客就会读到开放问题的答案。如果这个问题被重新打开,答案将被恢复。 - Jeremy
6
在这个问题中,没有明确说明需要使用“密码学安全的”密码。对于一些人来说,使用“/dev/random”的答案已经足够了,因为问题并没有要求一个“安全”的密码(也不应该被编辑以包含它,因为那样会改变原问题的意思)。虽然我非常支持安全性,但我认为这种方式并没有充分考虑。就像使用“mysql_”一样,这些答案仍然有效,但应该标记为不安全。也许这是SO需要包含的额外软件--能够警告*不安全的代码的能力? - Jimbo
6
@JeremyBanks 你能否恢复这个问题的答案?仅仅因为它是一个重复问题并不意味着答案是错误的(我不小心投票重新打开了,我同意这是一个重复问题)。删除答案没有任何意义,考虑将此问题删除并将答案迁移到其他问题上(我以前见过这样做)。 - Naftali
7
如果你想让某个东西不再重新开放,那么锁定它。否则99%的人都会重新开放它,并制造一堆麻烦。就我个人而言,我完全不同意这样随意删除得分高的答案,但也不能与你为此而争论。 - Shadow The Spring Wizard
显示剩余9条评论
32个回答

309

安全警告rand() 不是一个加密安全的伪随机数生成器。在 PHP 中寻找其他 生成加密安全的伪随机字符串

尝试以下代码(使用 strlen 替代 count,因为对于字符串而言,count 总是返回 1):

function randomPassword() {
    $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
    $pass = array(); //remember to declare $pass as an array
    $alphaLength = strlen($alphabet) - 1; //put the length -1 in cache
    for ($i = 0; $i < 8; $i++) {
        $n = rand(0, $alphaLength);
        $pass[] = $alphabet[$n];
    }
    return implode($pass); //turn the array into a string
}

示例


29
使用 $pass .= $alphabet[$n] 更为简单直接。 - Matthew
37
使用 rand 生成密码是一个非常糟糕的想法,因为它不是一种安全的伪随机数生成器(PRNG)。而且,mt_rand 也不比它更好。 - CodesInChaos
19
这个问题涉及到生成密码。生成密码的代码显然需要使用安全随机数。 - CodesInChaos
12
由于这不是一个重复的问题,所以出于相同的原因,这个答案是不正确的,因为问题是关于生成密码而不是随机字符串。这个答案提供了一种非常不安全的生成密码的方法。请使用下面@user3260409的答案,其中使用openssl_random_pseudo_bytes()代替rand() - Sorry-Im-a-N00b
38
我看到了你们生产环境中不安全的代码,并希望从源头上制止它。密码需要使用加密安全的随机数生成。 - Scott Arciszewski
显示剩余21条评论

157

简述:

  • 使用random_int()和下面给出的random_str()
  • 如果没有 random_int(),请使用random_compat

由于你正在生成密码,因此需要确保生成的密码是不可预测的。确保您的实现中具有此属性存在的唯一方法是使用密码学安全伪随机数生成器(CSPRNG)。

CSPRNG的要求可以放宽为普通情况下的随机字符串,但涉及到安全时不能这样做。

在PHP中生成密码的简单、安全、正确答案是使用RandomLib而不是重新发明轮子。该库已经由行业安全专家以及我本人进行了审核。

对于喜欢自己创造解决方案的开发人员,PHP 7.0.0将提供random_int()来解决此问题。如果您仍在使用PHP 5.x,可以使用我们编写的PHP 5兼容性包装器以使用新API。在PHP 7发布之前使用我们的random_int()兼容性包装器可能比编写自己的实现更安全。

有了一个安全的随机整数生成器,生成安全的随机字符串比吃派更容易:

<?php
/**
 * Generate a random string, using a cryptographically secure 
 * pseudorandom number generator (random_int)
 * 
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 * 
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str(
    $length,
    $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
) {
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    if ($max < 1) {
        throw new Exception('$keyspace must be at least two characters long');
    }
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

5
RandomLib已经有两年没有更新了。在最近的PHP版本(我使用的是7.1.25)中使用它会抛出各种“mcrypt_*”函数的弃用警告。我在一个问题线程上看到,您因无法联系@ircmaxell而forked该库,但是您的fork在Travis上显示“构建失败”。您是否愿意更新这个答案(它仍然在Google上排名很高)? - Janus Bahs Jacquet
3
很好的发现!它需要被移除。 - Scott Arciszewski
对于>=PHP7的版本,你可以使用bin2hex(random_bytes($length/2))来生成随机字符串,但是它不包含大写字母。 - gerardnll

127

我知道你正在尝试以特定方式生成密码,但你可能也想看看这种方法...

$bytes = openssl_random_pseudo_bytes(2);

$pwd = bin2hex($bytes);

这段内容来自php.net网站,它创建了一个字符串,该字符串的长度是您在openssl_random_pseudo_bytes函数中输入数字的两倍。因此,上面的代码将创建一个密码,长度为4个字符。

简而言之...


$pwd = bin2hex(openssl_random_pseudo_bytes(4));

创建一个8个字符长的密码。

请注意,该密码只包含数字0-9和小写字母a-f!


13
如果您想要一个包含大写字母、小写字母和数字的密码,可以尝试使用此链接:https://gist.github.com/zyphlar/7217f566fc83a9633959 - willbradley
1
@زياد 由谁说的?如果生成器使用7位字节,我会同意你的观点,但是openssl_random_pseudo_bytes()是一个强大的完整二进制字节随机生成器,不需要任何进一步的洗牌。此外,我想指出,假设堆叠多个加密方法会使任何东西更随机是危险的,在某些情况下,由于累积哈希碰撞,实际上可能恰恰相反。 - Havenard

67

仅用两行代码的微小程序。

演示: http://codepad.org/5rHMHwnH

function rand_string( $length ) {

    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    return substr(str_shuffle($chars),0,$length);

}

echo rand_string(8);

使用rand_string函数,你可以定义需要生成多少个字符。


24
不错,尽管使用这种方法不会得到任何重复字符,但这可能是不希望的。 - Hobo
14
这个函数不适合生成长密码。首先,如果$length比$chars字符串还要长,那么你得到的字符串长度将不会达到你输入的长度,而是$chars字符串的长度。其次,该函数只能保证每个字符只出现一次,没有重复的字符。此外,它也不能保证使用大写字母或数字,而这通常是必需的(除非你的长度超过26,因为上述错误)。 - Programster
1
这个是怎么样的 @Programster 和 @Hobo? substr(str_shuffle(str_repeat($chars,$length)),0,$length); - Charles-Édouard Coste
@Charles-EdouardCoste 看起来还可以(特别是如果你加入一些特殊字符)。尽管它仍然不能保证生成的密码至少包含每种字符类型中的一个。唯一让我感到不满意的是将整个字符集重复到所需密码的长度,但这确保了生成的密码中的字符不必是唯一的,并且在单次使用中没有明显的性能影响。 - Programster
我同意。最好不要根据算法的行数来选择算法。顺便说一下,如果主要目标只是在网站上创建新用户时生成临时密码,那应该满足需求,我猜。 - Charles-Édouard Coste

53

如果你使用的是PHP7,你可以使用random_int()函数:

function generate_password($length = 20){
  $chars =  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
            '0123456789`-=~!@#$%^&*()_+,./<>?;:[]{}\|';

  $str = '';
  $max = strlen($chars) - 1;

  for ($i=0; $i < $length; $i++)
    $str .= $chars[random_int(0, $max)];

  return $str;
}

以下是旧回答:

function generate_password($length = 20){
  $chars =  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.
            '0123456789`-=~!@#$%^&*()_+,./<>?;:[]{}\|';

  $str = '';
  $max = strlen($chars) - 1;

  for ($i=0; $i < $length; $i++)
    $str .= $chars[mt_rand(0, $max)];

  return $str;
}

13
不要使用mt_rand生成密码。 - CodesInChaos
2
@CodesInChaos,这比上面的例子使用的rand()更好。根据PHP手册,推荐使用openssl_random_pseudo_bytes()。 - willbradley
4
“@willbradley,‘mt_rand’ 的种子质量也同样糟糕,因此它仍然不适合于任何安全用途。” - CodesInChaos
2
你看,这就是让我烦恼的地方,ChaosInCodes。你没有看问题,只是发表了一些通用的陈述,重复了一堆站不住脚的信念。简而言之:完全不同的问题,却给出了错误的建议。随机密码很好。它们可能只能使用 "x" 次。坦白说,如果你设计的系统没有定时锁定和DOS检测以及 "x次尝试后->锁定",那么你做错了。在这种情况下,使用mt_rand密码是不可能被猜测到的。相反,使用mt_rand不会使密码更容易被暴力破解。它只是不会。 - Mr Heelis
1
我不会在“$chars”中使用反斜杠\ - Pedro Lobito
显示剩余2条评论

32
在一行中:
substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') , 0 , 10 )

13
这会防止同样的字母被重复使用,因为它们只是在不重复出现的情况下交换位置。 - nickdnk
9
毋庸置疑,没有人应该基于代码行数来优化他们的密码生成函数。即使使用的随机数生成器是安全的(实际上并不安全),在生成10个字符的密码时避免重复字符会将熵值从约52位降至约50位(破解速度快了约4倍)。如果你将其扩展到20个字符,则非重复性将把熵值从约103位降至约94位(破解速度快了约512倍)。 - Jeremy
1
这个方法让我想起了恩尼格玛密码的漏洞。哈哈 - hatef
4
恢复熵:substr(str_shuffle(str_repeat($chars,$length)),0,$length); (说明:这是一段PHP代码,用于生成随机字符串。) - Charles-Édouard Coste

27

你最好的选择是使用ircmaxell的RandomLib库

使用示例:


$factory = new RandomLib\Factory;
$generator = $factory->getGenerator(new SecurityLib\Strength(SecurityLib\Strength::MEDIUM));

$passwordLength = 8; // Or more
$randomPassword = $generator->generateString($passwordLength);

它生成的字符串比普通的随机函数(例如shuffle()rand())更具强度随机性,这是您在处理诸如密码、盐和密钥等敏感信息时所需的。


4
这是正确的答案。不要使用rand()mt_rand() - Scott Arciszewski
1
这可能是最安全的答案(不确定它与random_bytes相比如何),但这并不意味着rand的答案是错误的。 - Cerbrus
6
“rand”不适合用于密码生成。 - Scott Arciszewski
8
@Cerbrus: 当然,这个答案并不是说使用rand()的回答是不正确的。它们本身就是错误的! - Deduplicator

15
我将发布一个答案,因为现有的一些答案都有以下问题之一:
  • 字符空间比您想要的更小,因此暴力破解更容易或者密码必须更长才能具有相同的熵
  • RNG不被认为是加密安全的
  • 需要一些第三方库,我认为展示如何自己实现可能很有趣

这个答案将规避count/strlen问题,因为生成的密码的安全性至少在我的看法中超越了你如何得到它。我还假设PHP > 5.3.0。

让我们将问题分解成组成部分:

  1. 使用一些安全的随机源获取随机数据
  2. 使用该数据并将其表示为某些可打印的字符串

首先,PHP > 5.3.0 提供了函数 openssl_random_pseudo_bytes。请注意,虽然大多数系统使用的是加密强度较高的算法,但您仍需要进行检查,因此我们将使用一个包装器:

/**
 * @param int $length
 */
function strong_random_bytes($length)
{
    $strong = false; // Flag for whether a strong algorithm was used
    $bytes = openssl_random_pseudo_bytes($length, $strong);

    if ( ! $strong)
    {
        // System did not use a cryptographically strong algorithm 
        throw new Exception('Strong algorithm not available for PRNG.');
    }        

    return $bytes;
}

对于第二部分,我们将使用 base64_encode,因为它接受一个字节字符串并生成一系列字符,其字母表非常接近原始问题中指定的字母表。如果我们不介意在最终字符串中出现+/=字符,并且希望结果至少有$n个字符长,我们可以简单地使用:
base64_encode(strong_random_bytes(intval(ceil($n * 3 / 4))));

3/4的因子是由于base64编码导致的字符串长度至少比字节字符串大三分之一。结果将对$n是4的倍数的情况精确,否则会多出3个字符。由于额外的字符主要是填充字符=,如果我们有一个确切长度的密码约束,则可以将其截断到所需长度。这特别适用于给定的$n,所有密码都以相同数量的这些字符结尾,因此访问结果密码的攻击者将少猜测2个字符。


如果我们想满足OP问题中的确切规范,那么需要做更多工作。在这里,我将放弃基础转换方法,采用快速而粗略的方法。由于62个字符的字母表,两种方法都需要生成更多的随机数。
对于结果中的额外字符,我们可以简单地将它们从结果字符串中丢弃。如果我们在字节串中使用8个字节,那么最多约25%的base64字符将是这些“不受欢迎”的字符,因此只需丢弃这些字符即可得到与OP所需长度一样长或更长的字符串。然后,我们只需截断它以达到确切的长度:
$dirty_pass = base64_encode(strong_random_bytes(8)));
$pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);

如果你生成更长的密码,填充字符=在中间结果中所占比例会越来越小,因此,如果担心耗尽用于PRNG的熵池,则可以实现更精简的方法。

1
感谢这个补充。现有的回答中没有提到 openssl_random_pseudo_bytes 可能会产生弱结果。我之前并没有意识到这一点。 - Jeremy

14
你需要的是strlen($alphabet)而不是常量alphabetcount(等同于'alphabet')。
然而,rand不适合此目的。其输出易被预测,因为它会隐式地使用当前时间进行种子初始化。此外,rand不能保证密码安全,因此相对容易从输出中确定其内部状态。
相反,应该从/dev/urandom读取以获取密码学上的随机数据。

13

变得更聪明:

function strand($length){
  if($length > 0)
    return chr(rand(33, 126)) . strand($length - 1);
}

在线检查请点击这里


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