2014-02-28 69 views
2

我正在构建一个登录系统,我想确保我正在编写写入代码来生成和存储数据库中的密码。 $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.'; 
} 

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

回答

0

较长的盐并不意味着更好的保护。你不能正确使用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的字符串的哈希(或其他算法)也好多了(更安全?)。

正确的执行将是:

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()); 
+1

不要使用旧的地穴。不要使用任何算法的单个迭代。不要使用SHA1。不要使用独特的每用户随机盐;这些都会造成不安全的密码存储。请首先阅读[Thomas Pornin关于如何安全散列密码的典型答案](https://security.stackexchange.com/a/31846/39623)。 –

0

我相信你是过度的所有。

这是很不够的,如果你会使用类似

$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到数据库并在相同的方式检查:

$hash = md5(md5($salt_from_db).md5($user_password)); 
if ($hash == $hash_from_db) { 
// success 
} else { 
// fail 
} 
在你的榜样,你是

而且每做一次操作pow(91239193912939123912,3),但每次都会返回相同的结果。因此,如果您只添加预先计算的值,那也是一样的。

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

而且在这里看到:Secure hash and salt for PHP passwords

+0

请不要使用** MD5 **来散列密码。 PHP 5.5(以及带有password_compat的PHP 5.3.7)内置了完美的BCrypt功能。 –

+0

谢谢,我知道。它只是一个例子。正如我提到的,md5()可以替换为任何其他散列函数。 – Yaroslav

+0

这里放入哪个算法并不重要;如果它不是PBKDF2,BCrypt或SCrypt,则可能不安全。如果它是单一的,或少数甚至数百轮快速哈希算法,那肯定是不安全的。 –

3

好吧,让我们来看看:

你产生当前时间戳(≥1393631056)和东西约700之间的数字。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”两个字符的盐。在盐中使用无效字符将导致crypt()失败。

现在有这些句子,应该让你怀疑两个方面:

  1. 标准DES为基础并只使用两个字符作为盐,和
  2. 这些字符必须来自./0-9A-Za-z,否则crypt失败。

因此,您的盐不仅包含./0-9A-Za-z以外的字符,但最差的16个字符的大字符集中只有16^2 = 256个可能的盐。

那么,你应该怎么做? Just don’t try to reinvent the wheel but use existing and proven solutions.

0

在网上冲浪我发现了一个更好的使用河豚的解决方案。 用这种方法,我明白我只需要将散列存储在数据库中,而不是盐。显然越是密码越强,代码越慢。

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'; 
    } 
} 
2

难道这还不够强大?我错过了一些大问题吗?

现代PHP实际上提供了内置的特别好密码哈希 - BCrypt,三个(SCrypt,BCrypt,PBKDF2)一致推荐的密码哈希函数(如2014年初的)。更好的是,它可以为你处理咸味(盐应该是随机的,足够长的)。

如果您使用PHP 5.5或更高版本,请阅读Safe Password Hashing at PHP.net - 这是用PHP存储密码的常见问题解答。如果您使用的是PHP 5.3.7而不是5.5,则可以使用password_compat library来使用这些函数。

这些函数使用BCrypt并为你处理盐,所以很简单!

特别是,你可以散列以足够高的成本的密码(挑选需要足够的刚长,你的预期最大负载下的成本,您的网站将不太CPU绑定) - 就像在password_hash Example 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 example比较:与往常一样

<?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.'; 
} 
?> 

,如果你想详细信息,请阅读How to securely hash passwords? - 但PHP 5.5密码功能使用Bcrypt,所以你可以使用足够高的成本。

请注意,随着时间的推移和您购买更好的硬件,您应该增加匹配成本。你可以透明地做到这一点,在以旧的成本验证密码后,检查旧的成本,如果发现,在新的成本作为登录过程的一部分重新刷新它,这样你就可以增加存储密码的安全性而不会打扰你的用户。当然,从不登录的用户不会从中获益。

对于老预5.3.7的crypt()例如,见领导答案How do you use bcrypt for hashing passwords in PHP?,其中有一个getSalt功能:

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

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

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

    return $salt; 
    }