[英]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.