关于安全:在PHP中生成随机密码

Generating a random password in php

我正在尝试用PHP生成一个随机密码。

但是,我得到了所有的a,返回类型是类型array,我希望它是一个字符串。关于如何更正代码有什么想法吗?

谢谢。

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


Security warning: rand() is not a cryptographically secure pseudorandom number generator. Look elsewhere for generating a cryptographically secure pseudorandom string in PHP.

试试这个(用strlen代替count,因为字符串上的count总是1

1
2
3
4
5
6
7
8
9
10
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
}

演示:http://codepad.org/ul8k4ayk


DR:

  • 使用下面给出的random_int()random_str()
  • 如果没有random_int(),使用随机兼容。

说明:

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

对于随机字符串的一般情况,可以放宽对CSPRNG的要求,但当涉及到安全性时,则不必这样做。

在PHP中,密码生成的简单、安全和正确答案是使用randomlib,而不是重新发明轮子。这个图书馆已经过行业安全专家和我自己的审计。

对于喜欢自己发明解决方案的开发人员,php 7.0.0将为此提供random_int()。如果您仍然使用php 5.x,我们为random_int()编写了php 5 polyfill,这样您就可以在php 7发布之前使用新的API。使用我们的random_int()polyfill可能比编写自己的实现更安全。

有了安全随机整数生成器,生成安全随机字符串比生成饼图更容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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;
}


我知道你正试图以一种特定的方式生成你的密码,但你也可能想看看这个方法…

1
2
3
$bytes = openssl_random_pseudo_bytes(2);

$pwd = bin2hex($bytes);

它是从php.net网站上获取的,它创建了一个字符串,该字符串的长度是您在openssl_random_pseudo_bytes函数中输入的数字的两倍。所以上面会创建一个4个字符长的密码。

简而言之…

1
$pwd = bin2hex(openssl_random_pseudo_bytes(4));

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

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


带2行的小代码。

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

1
2
3
4
5
6
7
8
function rand_string( $length ) {

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

}

echo rand_string(8);

with rand_string you can define how much character will be create.


如果您使用的是PHP7,则可以使用random_int()功能:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

Old answer below:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}


在一行中:

1
substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') , 0 , 10 )


最好的选择是ircmaxell的randomlib库。

使用实例:

1
2
3
4
5
$factory = new RandomLib\Factory;
$generator = $factory->getGenerator(new SecurityLib\Strength(SecurityLib\Strength::MEDIUM));

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

它产生的字符串比普通随机函数(如shuffle()rand())随机性更强(这是您通常想要的敏感信息,如密码、盐和密钥)。


你要的是strlen($alphabet),而不是常数alphabetcount(相当于'alphabet')。

然而,rand并不是一个合适的随机函数。它的输出可以很容易地预测,因为它是隐式种子与当前时间。此外,rand在密码学上不安全;因此,从输出中确定其内部状态相对容易。

相反,从/dev/urandom中读取以获得加密随机数据。


我将发布一个答案,因为现有的一些答案很接近,但有一个:

  • 一个比您想要的更小的字符空间,这样暴力强制更容易,或者对于相同的熵密码必须更长。
  • 不被认为是密码安全的RNG
  • 对于一些第三方图书馆的要求,我认为展示自己可能需要什么可能是很有趣的。

这个答案将避开count/strlen问题,因为生成的密码的安全性(至少是imho)超越了您的方式。我还假设php>5.3.0。

让我们把问题分解成以下几个组成部分:

  • 使用某种随机性的安全源获取随机数据
  • 使用该数据并将其表示为可打印的字符串
  • 在第一部分中,php>5.3.0提供了openssl_random_pseudo_bytes函数。请注意,虽然大多数系统都使用加密强算法,但您必须进行检查,以便我们使用包装器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
     * @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字符长,我们可以简单地使用:

    1
    base64_encode(strong_random_bytes(intval(ceil($n * 3 / 4))));

    3/4因子是由于base64编码导致的字符串长度至少比字节字符串大三分之一。如果$n是4的倍数,那么结果将是精确的,否则最多可以长3个字符。由于额外的字符主要是填充字符=,如果出于某种原因,我们有一个限制,即密码是一个精确的长度,那么我们可以将其截断为我们想要的长度。这尤其是因为对于给定的$n而言,所有密码的结尾都是相同的数字,因此能够访问结果密码的攻击者最多只能猜测2个字符。

    对于额外的学分,如果我们想达到运营商问题中的确切规格,那么我们将不得不做更多的工作。我将放弃这里的基本转换方法,使用一个快速而肮脏的方法。两者都需要产生比结果中使用的随机性更大的随机性,因为62个条目的长字母表。

    对于结果中的额外字符,我们可以简单地从结果字符串中丢弃它们。如果我们从字节字符串中的8个字节开始,那么高达25%的base64字符将是这些"不需要的"字符,因此简单地丢弃这些字符将导致一个不短于op所需的字符串。然后我们可以简单地将其截断,以得到精确的长度:

    1
    2
    $dirty_pass = base64_encode(strong_random_bytes(8)));
    $pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);

    如果生成更长的密码,填充字符=在中间结果中所占的比例会越来越小,因此,如果需要排出用于prng的熵池,则可以实现更精简的方法。


    base_convert(uniqid('pass', true), 10, 36);

    e0m6ngefmj4

    编辑

    正如我在评论中提到的,长度意味着暴力攻击比定时攻击更有效,因此它与担心"随机生成器的安全性"无关。安全性,特别是对于这个用例,需要补充可用性,因此上述解决方案实际上足以满足所需的问题。

    但是,为了防止您在搜索安全随机字符串生成器(我假设有些人基于响应)时偶然发现这个答案,对于生成令牌之类的内容,下面是此类代码生成器的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    function base64urlEncode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    function secureId($length = 32) {

        if (function_exists('openssl_random_pseudo_bytes')) {
            $bytes = openssl_random_pseudo_bytes($length);
            return rtrim(strtr(base64_encode($bytes), '+/', '0a'), '=');
        }
        else { // fallback to system bytes

            error_log("Missing support for openssl_random_pseudo_bytes");

            $pr_bits = '';

            $fp = @fopen('/dev/urandom', 'rb');
            if ($fp !== false) {
                $pr_bits .= @fread($fp, $length);
                @fclose($fp);
            }

            if (strlen($pr_bits) < $length) {
                error_log('unable to read /dev/urandom');
                throw new \Exception('unable to read /dev/urandom');
            }

            return base64urlEncode($pr_bits);
        }
    }


    这基本上与更简单的substr(md5(uniqid()), 0, 8);相同。


    另一个(仅限Linux)

    1
    2
    3
    4
    5
    6
    7
    8
    function randompassword()
    {
        $fp = fopen ("/dev/urandom", 'r');
        if (!$fp) { die ("Can't access /dev/urandom to get random data. Aborting."); }
        $random = fread ($fp, 1024); # 1024 bytes should be enough
       fclose ($fp);
        return trim (base64_encode ( md5 ($random, true)),"=");
    }


    聪明一点:

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

    在这里检查一下。


    使用此简单代码生成12长度的med强密码

    1
    2
    $password_string = '!@#$%*&abcdefghijklmnpqrstuwxyzABCDEFGHJKLMNPQRSTUWXYZ23456789';
    $password = substr(str_shuffle($password_string), 0, 12);


    尝试使用大写字母、小写字母、数字和特殊字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function generatePassword($_len) {

        $_alphaSmall = 'abcdefghijklmnopqrstuvwxyz';            // small letters
        $_alphaCaps  = strtoupper($_alphaSmall);                // CAPITAL LETTERS
        $_numerics   = '1234567890';                            // numerics
        $_specialChars = '`~!@#$%^&*()-_=+]}[{;:,<.>/?\'"\|';   // Special Characters

        $_container = $_alphaSmall.$_alphaCaps.$_numerics.$_specialChars;   // Contains all characters
        $password = '';         // will contain the desired pass

        for($i = 0; $i < $_len; $i++) {                                 // Loop till the length mentioned
            $_rand = rand(0, strlen($_container) - 1);                  // Get Randomized Length
            $password .= substr($_container, $_rand, 1);                // returns part of the string [ high tensile strength ;) ]
        }

        return $password;       // Returns the generated Pass
    }

    Let's Say we need 10 Digit Pass

    1
    echo generatePassword(10);

    示例输出:

    ,IZCQIV IV 7

    @ WLQSFHT(D)

    1!8 + 1 4 @ UD


    快一号。简单、干净、一致的格式,如果您需要的话

    1
    $pw = chr(mt_rand(97,122)).mt_rand(0,9).chr(mt_rand(97,122)).mt_rand(10,99).chr(mt_rand(97,122)).mt_rand(100,999);

    这是基于本页的另一个答案,https://stackoverflow.com/a/21498316/525649。

    这个答案只生成十六进制字符,0-9,a-f。对于看起来不像十六进制的东西,请尝试以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    str_shuffle(
      rtrim(
        base64_encode(bin2hex(openssl_random_pseudo_bytes(5))),
        '='
      ).
      strtoupper(bin2hex(openssl_random_pseudo_bytes(7))).
      bin2hex(openssl_random_pseudo_bytes(13))
    )
    • base64_encode返回更广泛的字母数字字符
    • rtrim有时在末端移除=

    实例:

    • 32eFVfGDg891Be5e7293e54z1D23110M3ZU3FMjb30Z9a740Ej0jz4
    • b280R72b48eOm77a25YCj093DE5d9549Gc73Jg8TdD9Z0Nj4b98760
    • 051b33654C0Eg201cfW0e6NA4b9614ze8D2FN49E12Y0zY557aUCb8
    • y67Q86ffd83G0z00M0Z152f7O2ADcY313gD7a774fc5FF069zdb5b7

    这对于为用户创建一个界面来说不是非常可配置的,但是对于某些目的来说是可以的。增加字符数以说明缺少特殊字符。


  • 创建包含此代码的文件。
  • 就像评论里说的那样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?php

    /**
    * @usage  :
    *       include_once($path . '/Password.php');
    *       $Password = new Password;
    *       $pwd = $Password->createPassword(10);
    *       return $pwd;
    *
    */


    class Password {

        public function createPassword($length = 15) {
            $response = [];
            $response['pwd'] = $this->generate($length);
            $response['hashPwd'] = $this->hashPwd( $response['pwd'] );
            return $response;
        }

        private function generate($length = 15) {
            $chars ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(){}/?,><";
            return substr(str_shuffle($chars),0,$length);
        }

        private function hashPwd($pwd) {
            return hash('sha256', $pwd);
        }

    }

    ?>

  • 我创建了一个更全面、更安全的密码脚本。这将创建两个大写、两个小写、两个数字和两个特殊字符的组合。共8个字符。

    1
    2
    3
    4
    5
    6
    7
    8
    $char = [range('A','Z'),range('a','z'),range(0,9),['*','%','$','#','@','!','+','?','.']];
    $pw = '';
    for($a = 0; $a < count($char); $a++)
    {
        $randomkeys = array_rand($char[$a], 2);
        $pw .= $char[$a][$randomkeys[0]].$char[$a][$randomkeys[1]];
    }
    $userPassword = str_shuffle($pw);


    如果您需要8个字符,1个大写字母和1个数字。

    1
    2
    3
    4
    5
      $p_lc_letters = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 6); // Generate 6 lowercase letters
      $p_uc_letters = substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 1); // Generate 1 uppercase letter
      $p_numbers = substr(str_shuffle('0123456789'), 0, 1); // Generate 1 number

      echo $password = substr(str_shuffle($p_lc_letters.''.$p_uc_letters.''.$p_numbers), 0, 8);


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
        // We can generate without large code..
        $length = 8;
        $chars = array_merge(range(0,9), range('a','z'),range('A','Z'),['!','@','#','$','%','&','*','?']);
        //$chars = array_merge(range(0,1), range('A','Z'));
        shuffle($chars);
        $code = implode(array_slice($chars, 0, $length));
        $arr = array($code);
        $password = implode("",$arr);
        echo $password;

    我一直想尝试实现我自己的"安全"和"随机"密码生成器。

    我认为为函数提供一个lengthtype参数会更有趣、更灵活。

    这里有一个函数,它执行一些检查以确保length不太短或太长(也允许可变长度)。它还允许在type参数中选择密码字符集。

    如果lengthtype无效,使用php的mt_rand()函数,比rand()更"随机",则返回null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    <?php

    /**
    * Generates a random password with the given length and of the given type.
    *
    * @param int $length
    * @param string $type
    * @return string | null
    */

    function random_password($length=8, $type='alpha_numeric') {

        if ($length < 1 || $length > 1024) return null;

        $lower = 'abcdefghijklmnopqrstuvwxy';
        $upper = strtoupper($lower);
        $numbers = '1234567890';
        $dash = '-';
        $underscore = '_';
        $symbols = '`~!@#$%^&*()+=[]\\{}|:";\'<>?,./';

        switch ($type) {
            case 'lower':
                $chars = $lower;
                break;
            case 'upper':
                $chars = $upper;
                break;
            case 'numeric':
                $chars = $numbers;
                break;
            case 'alpha':
                $chars = $lower . $upper;
                break;
            case 'symbol':
                $chars = $symbols . $dash . $underscore;
                break;
            case 'alpha_numeric':
                $chars = $lower . $upper . $numbers;
                break;
            case 'alpha_numeric_dash':
                $chars = $lower . $upper . $numbers . $dash;
                break;
            case 'alpha_numeric_underscore':
                $chars = $lower . $upper . $numbers . $underscore;
                break;
            case 'alpha_numeric_dash_underscore':
                $chars = $lower . $upper . $numbers . $underscore . $dash;
                break;
            case 'all':
                $chars = $lower . $upper . $numbers . $underscore . $dash . $symbols;
                break;
            default:
                return null;
        }

        $min = 0;
        $max = strlen($chars) - 1;

        $password = '';

        for ($i = 0; $i < $length; $i++) {
            $random = mt_rand($min, $max);
            $char = substr($chars, $random, 1);
            $password .= $char;
        }

        return $password;
    }