Recca Chao 的 gitHub page

推廣網站開發,包含 Laravel 和 Kotlin 後端撰寫、自動化測試、讀書心得等。Taiwan Kotlin User Group 管理員。

View on GitHub

翻譯自

https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Password_Storage_Cheat_Sheet.md


簡介

因為多數使用者都會在不同的服務裡用相同的密碼,用即使服務本身或者資料庫被攻擊者破解,密碼也不會外流的方式儲存密碼,就變得非常重要。如同密碼學裡面的許多領域一樣,要做到這件事情需要考慮非常多的因素。幸運的是,多數現代的程式語言和框架都提供了內建的方式來儲存密碼,將複雜度減少了很多。

這份小抄提供了儲存密碼上,所需要考慮各個不同面向的指引。

簡而言之:

內容

背景

雜湊 vs 加密

雜湊和加密是常常被混淆或者誤用的兩個詞彙。這兩個詞彙關鍵的不同點是,雜湊是單向函數(或者說,不可能從雜湊「解密」出原本的值),而加密則是雙向函數。

幾乎所有的情況下,密碼都不應該被加密,而是應該被雜湊。因為,這樣攻擊者幾近不可能從雜湊後的結果逆推出原本的密碼。

只有在非常罕見的情況下,因為你必須保有原本的密碼,所以才必須使用加密的方式。

必須保有原本密碼的情況可能有:

有可能解密出密碼是一件非常嚴重的資安風險,所以必須要全面評估過風險。如果可能的話,應該盡量選用其他架構,來避免將密碼以加密的形式進行儲存。

這份小抄主要針對密碼雜湊,對加密後的資料儲存可以參考加密儲存資料的小抄

攻擊者如何破解密碼雜湊

雖然攻擊者不能夠從雜湊之中「解密」出原本的密碼,在某些情況下還是可以「破解」這些雜湊的。基本做法是:

之後可以針對大量可能的密碼重複這幾個步驟,直到找出對應的雜湊。有很多方法可以找出可能的密碼,比方說:

破解的過程並不保證會成功,成功率取決於以下幾個因素:

用現代演算法雜湊過的強密碼,對攻擊者來說應該是實際上不可能破解的。

雜湊概念

Salting

salting(加鹽)是在雜湊過程中,對每個密碼個別再加上一串唯一且隨機的字串。由於每個使用者的 salt 都是不同的,攻擊者破解密碼時,就不能只計算一次雜湊值,直接和所有的密碼雜湊比較,而是必須針對不同的 salt 個別計算出雜湊值並進行比較。這讓破解大量的密碼所要消耗的時間顯著的提升。

salt 也可以在攻擊者嘗試用 rainbow tables 或基於資料庫的內容預先計算出的雜湊列表進行攻擊時,提供多一層的保護。

最後,除非攻擊者破解其雜湊,salt 也可以避免兩個使用者使用相同密碼時被攻擊者發現。因為使用者所帶不同的 salt 可以保證其雜湊結果也不相同。

現代的雜湊演算法像是 Argon2 或者 Bcrypt 會自動在密碼上加 salt,所以不需要再進行額外的處理。不過,如果是使用古老的演算法那麼就需要手動加上 salt。自己加上 salt 的流程有:

Peppering

pepper(加胡椒)可以在 salt 以外多加上一層保護。pepper 和 salt 很像,不過有兩個關鍵的不同點:

pepper 的用意是保證如果攻擊者只拿到了資料庫的內容,那麼該攻擊者不可能破解任何的密碼。舉例來說,攻擊者可能是透過 SQL injection 的攻擊取得了資料庫的備份。

pepper 應該是隨機產生,並至少有 32 個字。pepper 要儲存在應用的設定檔內,以適當的檔案權限保護,使用作業系統提供的安全 API 進行存取或放在硬體安全模組(HSM)裡面。

傳統上 pepper 和 salt 一樣都是用字串連接的方式,在進行雜湊前和密碼組合在一起,像是 hash($pepper . $password) 的結構

另一種方法是將密碼用一般的方式算出雜湊值,然後用對稱式加密法加密該雜湊後,再儲存到資料庫內。該加密的金鑰就等同於 pepper 的效果。這種方式避免了傳統 pepper 的一些問題,並且讓汰換原本 pepper 的流程變得比較簡單。

缺點

pepper 主要的問題之一是長期維護上的困難。更換使用中的 pepper 會導致所有資料庫中的密碼失效,所以即使 pepper 被攻擊者取得了也很難簡單的更換掉。

其中一個解決方式是將 pepper 的 ID 和加密的密碼儲存在一起。當 pepper 更新時,使用新的 pepper 加密的密碼可以一併更新所儲存的 pepper ID。雖然這代表專案內需要儲存所有正在使用中的 pepper,不過這確實提供了當 pepper 不再安全時可以替換的方法。

Work Factors

work factor 基本上是針對一個密碼,雜湊加密重複運作的次數(實際上通常會是 2^work 次)。work factor 的意義是讓計算雜湊更花時間,進而提高攻擊者破解的難度。work factor 通常會一併儲存在雜湊後的結果裡面。

調整 work factor 時,要在安全性和效能之間達成一個平衡。提高 work factor 會讓攻擊者更難以破解,但是也會讓驗證使用者更花時間。如果將 work factor 設置得太高,那麼可能會降低系統的效能,另外也可能變成被攻擊者利用,以大量的嘗試登入來消耗伺服器的 CPU 計算能力,達成拒絕服務(denial of service,DoS)攻擊。

針對 work factor 沒有絕對的準則,這會取決於使用者的數量以及伺服器的效能。通常需要在應用運作的伺服器上實驗看看才能決定 work factor 的大小。

一般來說,計算雜湊的時間應該要小於一秒。當然流量更高的網站計算的時間應該要比一秒左右更少。

升級 Work Factor

有 work factor 一個關鍵的好處,是可以隨著硬體越來越進步和便宜時一起提升運算時間。以摩爾定律(同樣價格下的計算效能每十八個月會翻倍)作為粗略的估計,這代表每十八個月 work factor 應該加一。

升級 work factor 最常見的做法是等到用戶下次登入,並用新的 work factor 計算雜湊。這代表不同的雜湊可能是以不同的 work factor 計算出來的,也代表如果用戶遲遲沒有重新登入,密碼雜湊可能一直無法更新。根據應用的屬性不同,可能可以將長期未登入用戶的密碼雜湊刪除,並要求他們下次登入時輸入新的密碼,以避免資料庫內儲存老舊且較不安全的雜湊結果。

有些狀況下,是可以在不取得原始密碼的狀況下直接升級 work factor 的。不過目前多數的雜湊演算法像是 Bcrypt 和 PBKDF2 都不支援這麼做。

最長密碼長度

有的雜湊演算法,像是 Bcrypt,有輸入最長長度的限制。以 Bcrypt 來說多數的實作是 72 個字。(有一些報告指出部分實作可能有更短的長度限制,但是在本文撰寫時還沒得到確認)。

使用 Bcrypt 時,應該要加上密碼最長 64 字的限制,這樣既可以允許使用者選擇足夠長的密碼,也可以避免碰觸到演算法長度的限制,同時避免透露出使用的加密是 Bcrypt 這件事情。

另外,由於現代演算法的計算比較消耗計算資源,如果允許用戶使用非常長的密碼,可能會有潛在的拒絕服務(denial of service,DoS)問題,比方說 2013 年 Django 公布的弱點。

為了要避免上述的兩個問題,應該要限制密碼的最長長度。如果使用 Bcrypt 應該要設置為 64 個字(因為演算法本身以及其實作的限制),其他的演算法則設置為 64 到 128 個字之內。

預先雜湊密碼

另一個處理密碼最長長度的方式是先用快的雜湊法,像是 SHA-256,預先雜湊使用者所提供的密碼,然後將雜湊的結果使用安全的演算法像是 Bcrypt 再次進行雜湊(bcrypt(sha256($password)))。這個做法可以解決使用者輸入任意長度密碼的問題,不過也產生了一些弱點,讓攻擊者破解變得比較簡單。

如果攻擊者可以從兩個不同的地方取得密碼,第一個地方是使用 bcrypt(sha256($password)) 儲存,第二個地方則是使用 sha256($password)。那麼,攻擊者可以用第二個地方所取得的 SHA-256 雜湊結果當作候選密碼來嘗試破解(安全性較高)第一個地方的密碼雜湊。如果兩個地方有相同的密碼,那麼攻擊者就可以略過比較難破解的 Bcrypt 這一層,只需要破解較簡單的 SHA-256 雜湊就可以取得密碼。

預先以 SHA-256 雜湊也代表了攻擊者需要暴力破解的次數可以從 64 字密碼的 2^420 減少到 SHA-256 所有可能結果的 2^256 次,不過這兩個數字都足夠大到不會有任何實質性的危害。

最後,使用預先雜湊的話要保證第一次雜湊的結果編碼是十六進位或者 base64 編碼,因為有的雜湊演算法像是 Bcrypt [輸入包含 null] (https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html) 的話會出現異常行為。

綜上所述,比較好的做法還是限制密碼最長長度,預先雜湊一次的做法只能在特殊的情境下使用,並且必須加上特定的步驟,以避免上述的問題發生。

密碼雜湊演算法

現代演算法

現在已經有許多專門設計來安全儲存密碼的雜湊演算法。不像 MD5 和 SHA-1 這些演算法以快為設計目的,這些現代演算法設計上以耗時為設計目的,並且可以透過調整參數(work factor)來決定運算有多耗時。

下面列出三個主要應該考慮的演算法。

Argon2id

Argon2 是 2015 密碼雜湊競賽的獲勝者。該演算法有三個不同的版本,如果可以的話應該使用 Argon2id,因為這個版本可以同時防備旁道攻擊(side channel attack)和 GPU 為基礎的攻擊。

和其他演算法只要調整 work factor 不同,Argon2 可以調整三個不同的參數。所以要根據環境正確調校參數比較困難。演算法規格內包含了正確調整參數的指導。如果你無法準確的調整這些參數,像 Bcrypt 這類比較簡單的演算法會是更好的選擇。

PBKDF2

PBKDF2 NIST 建議的演算法,並且有 FIPS140 認證的實作。如果有要求上述條件的話,應該優先選擇此演算法。另外 .NET 框架預設支援此演算法,所以可以看到很多 ASP.NET 的應用使用該演算法。

PBKDF2 可以基於多種不同的雜湊演算法和 HMAC 一起搭配使用。HMAC-SHA-256 有非常多系統支援,並且也是 NIST 建議的作法之一。

PBKDF2 的 work factor 代表的是雜湊運算的次數,至少必須設置到 10,000,如果在安全性要求更高的環境上應該設置到 100,000 更為合適。

Bcrypt

Bcrypt 是目前支援度最廣的演算法,並且應該是優先的選擇。除非有特殊的需求必須使用 PBKDF2,或者團隊有專門的知識可以調校 Argon2。

Bcrypt 預設的 work factor 是 10,除非系統老舊或者是低耗能系統,不然一般來說應該要提升到至少 12。

古老的演算法

有些狀況下,我們無法使用現代的雜湊演算法。這通常是因為使用了老舊的系統或環境。只要可能的話,應該使用提供現代演算法的第三方套件。如果老舊的演算法像是 MD5 和 SHA-1 是僅有選項的話,有一些方式可以提高安全性:

這裡要強調,即使採取了以上這些步驟,古老的演算法還是不和使用現代演算法一樣安全。只有在沒有其他選擇時才使用古老演算法。

更新古老的雜湊

對過去使用 MD5 或 SHA-1 等古老演算法雜湊密碼的應用來說,應該要將密碼改用現代且安全的演算法重新處理過以保證安全性。

當使用者下次輸入正確密碼時(通常是重新登入的時候),應該使用新的現代演算法重新雜湊。將長期未登入的使用者視為過期並移除他們的密碼雜湊,並要求他們輸入新密碼是一個不錯的方式,這樣任何老舊且較不安全的雜湊對攻擊者來說就沒有用處了。

不過這也代表了,老舊且安全性較低的密碼雜湊必須要到使用者下次登入才會更新,如果使用者遲遲不登入,可能會被一直儲存下去。有兩種方法可以解決這個問題。

其中一個方法,是將長期未登入的使用者視為過期並移除他們的密碼雜湊,在這些使用者下次登入時,請他們重新輸入密碼。雖然這樣提升了安全性,不過相對使用者比較不友善一些。移除大量用戶的密碼可能會對客服單位造成問題,或者可能被使用者理解為資料庫已經被攻擊者找到漏洞。不過,如果演算法升級和移除舊密碼的時間點有合理的緩衝時間,那麼多數活躍的使用者應該已經更新過密碼了。

另一個方法是使用現有的密碼雜湊,然後將這些雜湊用比較安全的演算法重新處理過一遍。比方說,以前的系統儲存的密碼是 md5($password),那麼更新後的系統可以簡單地用 bcrypt(md5($password)) 作為新密碼雜湊。這樣重複加密方式可以避免需要取得密碼明文才能更新演算法的問題,不過會延伸出在預先雜湊裡面討論過的弱點,導致攻擊者可以比較簡單的破解這些雜湊。所以,這些雜湊結果應該更新成直接使用新演算法加密後的結果。

自定義演算法

撰寫自定義的密碼學相關程式,比方說雜湊演算法,是非常難的。因此絕對不應該在學術練習之外實際使用。

任何使用未知或者自己撰寫的雜湊演算法能帶來的潛在好處,跟可能帶有的弱點相比,是遠遠不能比較的。

不要這麼做