理論上只應該只有自己知道密碼,不過現實生活不總是這麼理想。
「密碼」是構築現代認證系統的基礎,儘管近年提出了諸如 U2F 之類的新興認證方式,但密碼總是認證系統的最初一步。
關於如何儲存密碼也是有各種不同的實作,從明文儲存、加密儲存到雜湊值儲存,加密跟雜湊都具備不同的演算法。
然而,對於密碼儲存的核心思想應該不脫離一個原則:除了使用者本人以外,不應該有其它人能夠知曉
為了瞭解現代密碼儲存的方式,我們必須先說明一些前置基礎知識。
在密碼學中,加密(英語:Encryption)是將明文資訊改變為難以讀取的密文內容,使之不可讀的過程。只有擁有解密方法的物件,經由解密過程,才能將密文還原為正常可讀的內容。--維基百科:加密條目
一般而言,加密有幾個特點
常見的加密法有 RSA、AES 等,如果有興趣的話可以詳加研究,在此不多贅述。
雜湊(英語:Hashing)是電腦科學中一種對資料的處理方法,通過某種特定的函式/演算法(稱為雜湊函式/演算法)將要檢索的項與用來檢索的索引(稱為雜湊,或者雜湊值)關聯起來,生成一種便於搜尋的資料結構(稱為雜湊表)。舊譯哈希(誤以為是人名而採用了音譯)。它也常用作一種資訊安全的實作方法,由一串資料中經過雜湊演算法(Hashing algorithms)計算出來的資料指紋(data fingerprint),經常用來識別檔案與資料是否有被竄改,以保證檔案與資料確實是由原創者所提供。--維基百科:雜湊
一般而言,雜湊有幾個特點
一般而言,密碼僅用於「驗證」使用者是否合法,所以在大部份的 Use Case 中都會希望直接使用雜湊即可。
在部份較舊的設計中,常常可以見到密碼以明文儲存,這是絕對不應該的:如果今天有個工程師有存取資料庫的權限,那他可以得知所有用戶的密碼,這已經悖逆了「密碼」本質上的意義。
雜湊有多種演算法,甚至可以自行設計(並不建議)。以下介紹幾個常見用於密碼儲存的(但不一定是正確的)演算法。
對於老式一些的系統,可能會使用 md5 作為雜湊的方式。
$hashed = md5($password);
但此演算法於 1996 年已被證實存在弱點,並在 2009 年發現已可以讓一般電腦在數秒內得出碰撞的方法。
SHA1 被喻為 md5 的後繼者,於是在 md5 宣告殞落後,有許多人紛紛轉向 sha1。
$hashed = sha1($password);
然而此方法已被證實存在缺陷,在 2000 年以後已不被大多數的場景所接受,於 2017 年 Google 宣告已攻破(但沒有公開發表破解方式)
以上的演算法除了在密碼學上存在缺陷之外,還有一個對於密碼儲存的致命傷:相同密碼雜湊出來的值都是相同的。
這在原理上是能夠理解的:如果每次雜湊出來的值都不同,又如何能夠比對雜湊後的值?
bcrypt 是專門為了密碼而設計的雜湊函式,它預設加入了鹽(Salt)的機制,這也讓它每次雜湊出來的值都是不一樣的。
$hashed = password_hash($password, PASSWORD_BCRYPT);
由 bcrypt 產生的雜湊值分為幾個部份
password
$2y$10$MourDUxRyw5JowRzW9fgM.x7BXZSn9gqBmoZ70o.5Bh1Ul4bGl6ty
$2y$
:表示使用 Bcrypt 演算法10$
:表示做 10 次 round(將雜湊 10 次)MourDUxRyw5JowRzW9fgM.
:使用的 salt,共 22 個字元x7BXZSn9gqBmoZ70o.5Bh1Ul4bGl6ty
:代表最後 Hash 出來的值因為 rounds 可以自行設定、salt 每次都會生成不同的值(也可以自行設定),所以相同的密碼並不會產生相同的雜湊值。
也因為這樣的特性,如果要驗證 bcrypt 所產生的 Hash,無法利用字串比對的方式進行,應該使用 password_verify()
這個函式驗證:
$success = password_verify('password', '$2y$10$MourDUxRyw5JowRzW9fgM.x7BXZSn9gqBmoZ70o.5Bh1Ul4bGl6ty');
bcrypt 是在 1999 年於 USENIX 中展示出來的,當時對於並行運算用於雜湊碰撞還並不成熟,在當時的設計中也未考量這樣的可能性。
在 2014 年,有人使用 ARM 混合 FPGA 的硬體嘗試攻擊這個演算法,並且發表了一篇論文,這讓 bcrypt 的安全性被拿出來重新審視。
在 2015 年,Argon2 贏得了 PHC 大賽,它共有三個版本
在 PHP 7.2 中直接支援了 argon2 的雜湊方法,成為少數直接在內建函式庫中可以使用 argon 的語言
$hashed = password_hash('password', PASSWORD_ARGON2ID);
因為 argon2 基於 AES(bcrypt 基於 Blowfish),因為目前有 AES-NI 指令集且大多數 x86 及 ARM 都有實現該指令集,所以效能上甚至比 bcrypt 更加出色。
一般來說,大部份的既有系統都不太會直接使用 bcrypt 或 argon2 這類相對新潮的演算法,這也導致資料庫中可能存在用 md5 或 sha1 等舊雜湊的結果。
對於目前仍使用明文密碼儲存的系統,建議可以直接使用 argon2 將資料庫的密碼雜湊,並且直接介接於系統認證層即可。
使用 AES 或 RSA 等加密方案的密碼,與明文密碼類似,先解密之後再直接將密碼雜湊,改寫系統認證層即可。
因為 md5 或 sha1 等方法是無法反推回原本用戶密碼的,一般來說我會用以下步驟進行系統遷移
這樣的方式有些缺陷:資料庫中可能存在許多不同時期的雜湊方法,且經過越多入的迭代會讓認證的邏輯層變得越來越複雜。
有部份的人認為應該將密碼分為 original_password
及 new_password
兩個欄位,無論使用哪個欄位都可以認證使用者,當遷移期眼已到但都未能登入的使用者就直接刪除該帳戶的密碼,若要找回帳戶就循密碼重設的流程進行。
這樣的方式缺點在於需要更動資料庫的欄位,而這對老系統而言是風險比較高的操作。
基本上並不期待開發者會使用諸如 bcrypt 或 argon 之類的現代雜湊演算法,但還是必須強調密碼的儲存請 務必 經過雜湊(非加密,否則若是被拿到密鑰就跟直接存明碼沒有兩樣)再後儲存。