繁体   English   中英

安全的密码生成和存储

[英]Secure password generation and storage

我正在构建一个登录系统,并且我想确保自己正在编写写代码以在数据库中生成和存储密码。 $options['passwd']是用户选择作为密码的字符串。

这是我生成哈希的代码:

public function createPasswd($options) {
    $hash_seed = 'm9238asdasdasdad31d2131231231230132k32ka¡2da12sm2382329';
    $password = $options['passwd'];
    $date =  new DateTime();
    $timestamp = $date->getTimestamp();
    $rand_number = rand($timestamp,$timestamp + pow(91239193912939123912,3));
    $rand = pow($rand_number, 12);
    $salt = str_shuffle($rand.$hash_seed);
    $hash = crypt($password, $salt);
    return $hash;
}//End class createPasswd

我只是将哈希存储在数据库中,然后将其与用户密码进行比较,如下所示:

if ($hash == crypt($password, $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

这足够强大吗? 我是否错过了一些大问题?

好吧,让我们来看看:

您正在当前时间戳记(≥1393631056)和7.59E + 59之间生成一个数字:

$rand_number = rand($timestamp,$timestamp + pow(91239193912939123912,3));
$rand = pow($rand_number, 12);

这些值和计算似乎只是任意的。 坦白说,如果随机值已经很大,则pow($rand_number, 12)返回INF

然后放入随机数和固定种子,对其进行随机处理,然后将其与crypt一起用作盐来哈希密码:

$salt = str_shuffle($rand.$hash_seed);
$hash = crypt($password, $salt);

随机浮点数加固定盐仅在16个(对于INF )与20个不同字符之间产生。 但是,仔细查看crypt发现,如果您不指定算法, crypt将会使用CRYPT_STD_DES

基于DES的标准哈希,带有字母“ ./0-9A-Za-z ”的两个字符的盐。 在salt中使用无效字符将导致crypt()失败。

现在,这些句子中有两个方面应该使您感到怀疑:

  1. 基于标准DES的盐仅使用两个字符,并且
  2. 这些字符必须来自./0-9A-Za-z ,否则crypt失败。

因此,您的盐不仅包含./0-9A-Za-z以外的其他./0-9A-Za-z ,而且在最坏的16个字符./0-9A-Za-z ,只有16 ^ 2 = 256种可能的盐。

那你该怎么办? 只是不要尝试重新发明轮子,而是使用现有的和成熟的解决方案。

这足够强大吗? 我错过了一些大问题吗?

现代PHP实际上提供了内置的非常好的密码哈希-BCrypt,这是三款(SCrypt,BCrypt,PBKDF2)一致推荐的密码哈希函数之一(截至2014年初)。 更好的是,它可以为您处理盐分(盐应该是随机且足够长的)。

如果您使用的是PHP 5.5或更高版本,请阅读PHP.net上的“安全密码哈希” -这是在PHP中存储密码的常见问题解答。 如果您使用的是PHP 5.3.7,但尚未使用5.5,则可以使用password_compat库来使用这些功能。

这些功能使用BCrypt并为您处理盐,所以很简单!

特别是,您可以以足够高的成本对密码进行哈希处理(选择一个花费的时间足够长,以至于在预期的最大负载下,您的站点将不受CPU限制)-就像password_hash示例2一样

<?php
/**
 * In this case, we want to increase the default cost for BCRYPT to 12.
 * Note that we also switched to BCRYPT, which will always be 60 characters.
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";
?>

然后,您存储它返回的字符串。

为了进行验证,请从存储它的位置(即数据库)检索返回的字符串,并与password_verify示例进行比较:

<?php
// See the password_hash() example to see where this came from.
$hash = '$2y$07$BCryptRequires22Chrcte/VlQH0piJtjXl.0t1XkA8pw9dMXTpOq';

if (password_verify('rasmuslerdorf', $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}
?>

与往常一样,如果您需要详细信息,请阅读如何安全地哈希密码? -但是PHP 5.5密码功能使用Bcrypt,因此您可以使用足够高的成本。

请注意,随着时间的流逝,您购买了更好的硬件,应该增加匹配的成本。 您可以通过以下方式透明地执行此操作:在以旧费用验证密码之后,检查旧费用,如果找到旧费用,则在登录过程中以新费用重新哈希它,从而可以提高存储密码的安全性而无需打扰您的用户。 当然,从未登录的用户不会从中受益。

对于旧的5.3.7之前的crypt()示例,请参见如何在PHP中使用bcrypt哈希密码的主要答案 ,其getSalt函数为:

  private function getSalt() {
    $salt = sprintf('$2a$%02d$', $this->rounds);

    $bytes = $this->getRandomBytes(16);

    $salt .= $this->encodeBytes($bytes);

    return $salt;
  }

更长的盐并不意味着更好的保护。 您没有正确使用crypt函数。 $ salt参数不应为简单的随机字符串。

考虑这个例子:

echo crypt('password one', 'salt lorem ipsum dolor sit amet');  
echo crypt('password two', 'salt');

两者都将返回相同的字符串! (sa3tHJ3 / KuYvI)

检查http://php.net/crypt以获得有关如何正确使用$ salt的更多信息。

保留唯一的hash_seed代码端,然后仅将结合了密码和您的hash_seed的字符串的sha哈希(或其他算法)存储在数据库中,这也更好(更安全)。

正确的实现是:

define('SECRET_KEY', 'm9238asdasdasdad31d2131231231230132k32ka¡2da12sm2382329');  // longer is better

public function createPasswd($options) {
    return hash('sha256', SECRET_KEY . $options['passwd']);
}

要检查密码:

if ($stored_hash == hash('sha256', SECRET_KEY . $password) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}

sha256可以用系统上任何可用的算法替换。 通过以下方式获取完整列表:

var_dump(hash_algos());

我敢肯定,你做的太过分了。

如果您使用类似的东西就足够了

$salt = get_random_salt(); // will return any random string
$hash = md5(md5($salt).md5($user_password)); // or any combinations of hashing and concatenations. It's your secure alorithm. Or use other more resistant hash algorithm.

比您需要将$hash$salt保存到DB并以相同的方式进行检查:

$hash = md5(md5($salt_from_db).md5($user_password));
if ($hash == $hash_from_db) {
// success
} else {
// fail
}

同样在您的示例中,您每次都在执行pow(91239193912939123912,3)操作,但每次都会返回相同的结果。 因此,如果只添加预先计算的值,则是相同的。

我的意思是没有必要使用那种复杂的salt算法。 您可以仅将mt_rand()用于此目的。

另请参见此处: PHP密码的安全哈希和盐

在网上冲浪时,我发现了一种使用河豚的更好解决方案。 通过这种方法,我知道我只需要将哈希存储在数据库中,而不是盐。 显然,越圆越多,密码越强,生成速度越慢。

public function better_crypt($input, $rounds = 8)  {
        $salt = "";
        $salt_chars = array_merge(range('A','Z'), range('a','z'), range(0,9));
        for($i=0; $i < 22; $i++) {
          $salt .= $salt_chars[array_rand($salt_chars)];
        }
        return crypt($input, sprintf('$2a$%02d$', $rounds) . $salt);
    }//End function

if(crypt($pass_string, $passwd) == $passwd) {
   echo 'password is correct';
  }
}

使用password_hash()默认情况下在后台使用bcrypt: http : //ie2.php.net/password_hash

如果您无法从PHP版本中使用它,请访问: https : //github.com/ircmaxell/password_compat

如果您还有其他用途,请尝试自己编写,现在停止,请不要听白痴建议其他任何选择。 特别是当他们建议使用MD5时...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM