2024 日本 PHP 研討會

安全地雜湊密碼

本節說明使用雜湊函式保護密碼的原因,以及如何有效地進行雜湊。

為什麼使用者提供的密碼應該進行雜湊處理?

密碼雜湊(Password hashing)是設計任何接受使用者密碼的應用程式或服務時,必須考慮的最基本安全措施之一。如果沒有雜湊,任何儲存的密碼都可能在資料儲存庫遭到入侵時被竊取,然後立即被用來入侵應用程式或服務,以及使用者在其他服務上的帳戶(如果他們沒有使用獨特的密碼)。

藉由在儲存使用者密碼之前套用雜湊演算法,任何攻擊者都難以確定原始密碼,同時仍然能夠在未來將產生的雜湊值與原始密碼進行比較。

然而,需要注意的是,雜湊密碼僅保護它們免於在資料儲存庫中遭到入侵,但不一定能保護它們免於被注入應用程式或服務本身的惡意程式碼攔截。

為什麼像 md5()sha1() 這樣的常見雜湊函式不適合用於密碼?

諸如 MD5、SHA1 和 SHA256 之類的雜湊演算法設計得非常快速且高效。借助現代技術和電腦設備,對這些演算法的輸出進行「暴力破解」(brute force),以確定原始輸入已變得非常容易。

由於現代電腦可以快速「反轉」(reverse)這些雜湊演算法,許多安全專家強烈建議不要將它們用於密碼雜湊。

如果常見的雜湊函式不適合,應該如何雜湊密碼?

雜湊密碼時,兩個最重要的考慮因素是計算成本和加鹽(salt)。雜湊演算法的計算成本越高,暴力破解其輸出的時間就越長。

PHP 提供了原生密碼雜湊 API,可以安全地處理雜湊驗證密碼

雜湊密碼時建議使用的演算法是 Blowfish,這也是密碼雜湊 API 使用的預設演算法,因為它的計算成本明顯高於 MD5 或 SHA1,同時仍具有可擴展性。

crypt() 函式也可用於密碼雜湊,但僅建議將其用於與其他系統的互通性。強烈建議盡可能使用原生密碼雜湊 API

什麼是鹽值?

加密鹽(cryptographic salt)是在雜湊過程中套用的資料,用於消除在預先計算的雜湊及其輸入對列表(稱為彩虹表)中查詢輸出的可能性。

簡單來說,鹽是一些額外的資料,它使雜湊更難以破解。網路上有許多服務提供大量的預先計算雜湊列表,以及這些雜湊的原始輸入。使用鹽使得在這些列表中找到結果雜湊變得極不可能或不可能。

如果未提供鹽,password_hash() 將會建立一個隨機鹽,這通常是最簡單且最安全的方法。

鹽值如何儲存?

使用 password_hash()crypt() 時,傳回值包含鹽作為產生雜湊的一部分。此值應逐字儲存在資料庫中,因為它包含有關所使用雜湊函式的資訊,然後可以在驗證密碼時直接提供給 password_verify()

警告

為了避免計時攻擊,應該始終使用 password_verify(),而不是重新雜湊並將結果與儲存的雜湊值進行比較。

下圖顯示了 crypt()password_hash() 返回值的格式。可以看到,它們是自包含的,包含了未來密碼驗證所需的所有算法和鹽值資訊。


        The components of the value returned by password_hash and crypt: in
        order, the chosen algorithm, the algorithm's options, the salt used,
        and the hashed password.

新增註釋

使用者貢獻的註釋 3 則註釋

alf dot henrik at ascdevel dot com
10 年前
我覺得我應該評論一下這裡的一些回覆。

首先,速度確實是 MD5,還有 SHA1 的一個問題。我為了好玩寫了一個 MD5 破解程式,僅使用我的 CPU,我就可以輕鬆地以每秒約 2 億次雜湊的速度檢查雜湊值。這種速度的主要原因是,在大多數嘗試中,您可以繞過算法中 64 個步驟中的 19 個。對於較長的輸入(> 16 個字元),它將不適用,但我相信有一些方法可以解決它。

如果您在網路上搜尋,您會看到有人聲稱能夠使用 GPU 每秒檢查數十億個雜湊值。如果現在單台電腦就能達到每秒 1000 億次,我不會感到驚訝,而且情況只會越來越糟。這需要一台配備 4 個雙高端 GPU 或類似配置的耗電怪獸,但仍然是可能的。

以下是為什麼每秒 1000 億次是一個問題的原因
假設大多數密碼包含 96 個字元的選項。一個 8 個字元的密碼將有 96^8 = 7,213,895,789,840,000 種組合。
以每秒 1000 億次的速度,那麼需要 7,213,895,789,840,000 / 3600 = 約 20 小時才能算出它的實際內容。請記住,您還需要添加 1-7 個字元的數字。如果您想鎖定單個使用者,20 小時並不算多。

所以本質上
新的雜湊算法被特別設計成不容易在 GPU 上實現是有原因的。

哦,我看到有人提到了 MD5 和彩虹表。如果您閱讀了這裡的數字,我希望您意識到就 MD5 而言,彩虹表變得是多麼的愚蠢和無用。除非 MD5 的輸入非常龐大,否則您將無法與 GPU 競爭。等到儲存媒體能夠產生遠超 3TB/s 的速度時,CPU 和 GPU 的速度將會達到更高的水平。

至於 SHA1,我相信它比 MD5 慢三分之一左右。我自己無法驗證這一點,但根據 MD5 和 SHA1 的數據來看,情況似乎如此。速度問題基本上也和這裡一樣。

這裡的寓意
請務必照做。絕對不要再使用 MD5 和 SHA1 來雜湊密碼。我們都知道,大多數人的密碼並不會很長,這是一個主要的缺點。加長鹽值當然會有幫助,但除非你打算加上幾百個位元組的鹽值,否則總會有快速暴力破解應用程式可以反向工程你的密碼或你使用者的密碼。
swardx at gmail dot com
8 年前
一篇很棒的文章⋯

https://nakedsecurity.sophos.com/2013/11/20/serious-security-how-to-store-your-users-passwords-safely/

嚴謹的安全性:如何安全地儲存使用者密碼

總之,以下是我們對於安全儲存使用者密碼的最低建議

使用強大的隨機數產生器來建立 16 位元組或更長的鹽值。
將鹽值和密碼輸入 PBKDF2 演算法。
使用 HMAC-SHA-256 作為 PBKDF2 的核心雜湊函式。
執行 20,000 次或更多迭代。(2016 年 6 月)
從 PBKDF2 取出 32 位元組(256 位元)的輸出作為最終的密碼雜湊值。
將迭代次數、鹽值和最終雜湊值儲存在你的密碼資料庫中。
定期增加你的迭代次數,以跟上更快的破解工具。

無論如何,不要嘗試自行設計密碼儲存演算法。
tamas at microwizard dot com
3 年前
當我閱讀這些評論時,一些古老的數學課程浮現在我的腦海中,並開始思考。在數學演算法中使用常數並不會改變演算法本身的複雜度。

加鹽的目的是為了避免使用彩虹表(抱歉各位,這是唯一的原因),因為它可以加速(捷徑)「實際」的處理能力。
(((更長的儲存雜湊值和更長的密碼會增加破解的複雜度,而不是單獨加鹽。)))

PHP 加鹽函式會返回檢查密碼所需的所有資訊,因此從更遠的觀點來看,這些資訊應該被視為常數。它也是彩虹表的目標(當然:對於更大得多的彩虹表)。

解決方案是什麼?
解決方案是將密碼雜湊值和鹽值儲存在不同的地方。
實作方式由你決定。任何兩個不同的地方都足夠好。

是的,這會給駭客帶來麻煩。他/她需要了解你的系統。如果沒有重新實作你的整個系統,任何加速密碼破解的方法都對他/她無效。

這是我的淺見。
To Top