本部分解釋使用散列函數對密碼進(jìn)行安全處理背后的原因, 以及如何有效的進(jìn)行密碼散列處理。
當設計一個(gè)需要接受用戶(hù)密碼的應用時(shí), 對密碼進(jìn)行散列是最基本的,也是必需的安全考慮。 如果不對密碼進(jìn)行散列處理,那么一旦應用的數據庫受到攻擊, 那么用戶(hù)的密碼將被竊取。 同時(shí),竊取者也可以使用用戶(hù)賬號和密碼去嘗試其他的應用, 如果用戶(hù)沒(méi)有為每個(gè)應用單獨設置密碼,那么將面臨風(fēng)險。
通過(guò)對密碼進(jìn)行散列處理,然后再保存到數據庫中, 這樣就使得攻擊者無(wú)法直接獲取原始密碼, 同時(shí)還可以保證你的應用可以對原始密碼進(jìn)行相同的散列處理, 然后比對散列結果。
需要著(zhù)重提醒的是,密碼散列只能保護密碼 不會(huì )被從數據庫中直接竊取, 但是無(wú)法保證注入到應用中的 惡意代碼攔截到原始密碼。
MD5,SHA1 以及 SHA256 這樣的散列算法是面向快速、高效 進(jìn)行散列處理而設計的。隨著(zhù)技術(shù)進(jìn)步和計算機硬件的提升, 破解者可以使用“暴力”方式來(lái)尋找散列碼 所對應的原始數據。
因為現代化計算機可以快速的“反轉”上述散列算法的散列值, 所以很多安全專(zhuān)家都強烈建議 不要在密碼散列中使用這些散列算法。
當進(jìn)行密碼散列處理的時(shí)候,有兩個(gè)必須考慮的因素: 計算量以及“鹽”。 散列算法的計算量越大, 暴力破解所需的時(shí)間就越長(cháng)。
PHP 5.5 提供了 一個(gè)原生密碼散列 API, 它提供一種安全的方式來(lái)完成密碼 散列和 驗證。 PHP 5.3.7 及后續版本中都提供了一個(gè) ? 純 PHP 的兼容庫。
PHP 5.3 及后續版本中,還可以使用 crypt() 函數, 它支持多種散列算法。 針對每種受支持的散列算法,PHP 都提供了對應的原生實(shí)現, 所以在使用此函數的時(shí)候, 你需要保證所選的散列算法是你的系統所能夠支持的。
當對密碼進(jìn)行散列處理的時(shí)候,建議采用 Blowfish 算法, 這是密碼散列 API 的默認算法。 相比 MD5 或者 SHA1,這個(gè)算法提供了更高的計算量, 同時(shí)還有具有良好的伸縮性。
如果使用 crypt() 函數來(lái)進(jìn)行密碼驗證, 那么你需要選擇一種耗時(shí)恒定的字符串比較算法來(lái)避免時(shí)序攻擊。 (譯注:就是說(shuō),字符串比較所消耗的時(shí)間恒定, 不隨輸入數據的多少變化而變化) PHP 中的 == 和 === 操作符 和 strcmp() 函數都不是耗時(shí)恒定的字符串比較, 但是 password_verify() 可以幫你完成這項工作。 我們鼓勵你盡可能的使用 原生密碼散列 API。
加解密領(lǐng)域中的“鹽”是指在進(jìn)行散列處理的過(guò)程中 加入的一些數據,用來(lái)避免從已計算的散列值表 (被稱(chēng)作“彩虹表”)中 對比輸出數據從而獲取明文密碼的風(fēng)險。
簡(jiǎn)單而言,“鹽”就是為了提高散列值被破解的難度 而加入的少量數據。 現在有很多在線(xiàn)服務(wù)都能夠提供 計算后的散列值以及其對應的原始輸入的清單, 并且數據量極其龐大。 通過(guò)加“鹽”就可以避免直接從清單中查找到對應明文的風(fēng)險。
如果不提供“鹽”,password_hash() 函數會(huì )隨機生成“鹽”。 非常簡(jiǎn)單,行之有效。
當使用 password_hash() 或者 crypt() 函數時(shí), “鹽”會(huì )被作為生成的散列值的一部分返回。 你可以直接把完整的返回值存儲到數據庫中, 因為這個(gè)返回值中已經(jīng)包含了足夠的信息, 可以直接用在 password_verify() 或 crypt() 函數來(lái)進(jìn)行密碼驗證。
下圖展示了 crypt() 或 password_hash() 函數返回值的結構。 如你所見(jiàn),算法的信息以及“鹽”都已經(jīng)包含在返回值中, 在后續的密碼驗證中將會(huì )用到這些信息。