2014-02-28 155 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; 
    }