[英]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()
失败。
现在,这些句子中有两个方面应该使您感到怀疑:
./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.