密碼散列安全

本部分解釋使用散列函數(shù)對(duì)密碼進(jìn)行安全處理背后的原因, 以及如何有效的進(jìn)行密碼散列處理。

為什么需要把應(yīng)用程序中用戶的密碼進(jìn)行散列化?

當(dāng)設(shè)計(jì)一個(gè)需要接受用戶密碼的應(yīng)用時(shí), 對(duì)密碼進(jìn)行散列是最基本的,也是必需的安全考慮。 如果不對(duì)密碼進(jìn)行散列處理,那么一旦應(yīng)用的數(shù)據(jù)庫受到攻擊, 那么用戶的密碼將被竊取。 同時(shí),竊取者也可以使用用戶賬號(hào)和密碼去嘗試其他的應(yīng)用, 如果用戶沒有為每個(gè)應(yīng)用單獨(dú)設(shè)置密碼,那么將面臨風(fēng)險(xiǎn)。

通過對(duì)密碼進(jìn)行散列處理,然后再保存到數(shù)據(jù)庫中, 這樣就使得攻擊者無法直接獲取原始密碼, 同時(shí)還可以保證你的應(yīng)用可以對(duì)原始密碼進(jìn)行相同的散列處理, 然后比對(duì)散列結(jié)果。

需要著重提醒的是,密碼散列只能保護(hù)密碼 不會(huì)被從數(shù)據(jù)庫中直接竊取, 但是無法保證注入到應(yīng)用中的 惡意代碼攔截到原始密碼。

為何諸如 md5()sha1() 這樣的常見散列函數(shù)不適合用在密碼保護(hù)場(chǎng)景?

MD5,SHA1 以及 SHA256 這樣的散列算法是面向快速、高效 進(jìn)行散列處理而設(shè)計(jì)的。隨著技術(shù)進(jìn)步和計(jì)算機(jī)硬件的提升, 破解者可以使用“暴力”方式來尋找散列碼 所對(duì)應(yīng)的原始數(shù)據(jù)。

因?yàn)楝F(xiàn)代化計(jì)算機(jī)可以快速的“反轉(zhuǎn)”上述散列算法的散列值, 所以很多安全專家都強(qiáng)烈建議 不要在密碼散列中使用這些散列算法。

如果不建議使用常用散列函數(shù)保護(hù)密碼, 那么我應(yīng)該如何對(duì)密碼進(jìn)行散列處理?

當(dāng)進(jìn)行密碼散列處理的時(shí)候,有兩個(gè)必須考慮的因素: 計(jì)算量以及“鹽”。 散列算法的計(jì)算量越大, 暴力破解所需的時(shí)間就越長(zhǎng)。

PHP 5.5 提供了 一個(gè)原生密碼散列 API, 它提供一種安全的方式來完成密碼 散列驗(yàn)證。 PHP 5.3.7 及后續(xù)版本中都提供了一個(gè) ? 純 PHP 的兼容庫

PHP 5.3 及后續(xù)版本中,還可以使用 crypt() 函數(shù), 它支持多種散列算法。 針對(duì)每種受支持的散列算法,PHP 都提供了對(duì)應(yīng)的原生實(shí)現(xiàn), 所以在使用此函數(shù)的時(shí)候, 你需要保證所選的散列算法是你的系統(tǒng)所能夠支持的。

當(dāng)對(duì)密碼進(jìn)行散列處理的時(shí)候,建議采用 Blowfish 算法, 這是密碼散列 API 的默認(rèn)算法。 相比 MD5 或者 SHA1,這個(gè)算法提供了更高的計(jì)算量, 同時(shí)還有具有良好的伸縮性。

如果使用 crypt() 函數(shù)來進(jìn)行密碼驗(yàn)證, 那么你需要選擇一種耗時(shí)恒定的字符串比較算法來避免時(shí)序攻擊。 (譯注:就是說,字符串比較所消耗的時(shí)間恒定, 不隨輸入數(shù)據(jù)的多少變化而變化) PHP 中的 == 和 === 操作符strcmp() 函數(shù)都不是耗時(shí)恒定的字符串比較, 但是 password_verify() 可以幫你完成這項(xiàng)工作。 我們鼓勵(lì)你盡可能的使用 原生密碼散列 API。

“鹽”是什么?

加解密領(lǐng)域中的“鹽”是指在進(jìn)行散列處理的過程中 加入的一些數(shù)據(jù),用來避免從已計(jì)算的散列值表 (被稱作“彩虹表”)中 對(duì)比輸出數(shù)據(jù)從而獲取明文密碼的風(fēng)險(xiǎn)。

簡(jiǎn)單而言,“鹽”就是為了提高散列值被破解的難度 而加入的少量數(shù)據(jù)。 現(xiàn)在有很多在線服務(wù)都能夠提供 計(jì)算后的散列值以及其對(duì)應(yīng)的原始輸入的清單, 并且數(shù)據(jù)量極其龐大。 通過加“鹽”就可以避免直接從清單中查找到對(duì)應(yīng)明文的風(fēng)險(xiǎn)。

如果不提供“鹽”,password_hash() 函數(shù)會(huì)隨機(jī)生成“鹽”。 非常簡(jiǎn)單,行之有效。

我應(yīng)該如何保存“鹽”?

當(dāng)使用 password_hash() 或者 crypt() 函數(shù)時(shí), “鹽”會(huì)被作為生成的散列值的一部分返回。 你可以直接把完整的返回值存儲(chǔ)到數(shù)據(jù)庫中, 因?yàn)檫@個(gè)返回值中已經(jīng)包含了足夠的信息, 可以直接用在 password_verify()crypt() 函數(shù)來進(jìn)行密碼驗(yàn)證。

下圖展示了 crypt()password_hash() 函數(shù)返回值的結(jié)構(gòu)。 如你所見,算法的信息以及“鹽”都已經(jīng)包含在返回值中, 在后續(xù)的密碼驗(yàn)證中將會(huì)用到這些信息。


        password_hash 和 crypt 函數(shù)返回值的組成部分,依次為:所選擇的算法,
        算法選項(xiàng),所使用的“鹽”,
        以及散列后的密碼。