iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
2
Modern Web

成為 Modern PHPer系列 第 5

Day 05:密碼儲存的實踐

  • 分享至 

  • twitterImage
  •  

理論上只應該只有自己知道密碼,不過現實生活不總是這麼理想。

前言

「密碼」是構築現代認證系統的基礎,儘管近年提出了諸如 U2F 之類的新興認證方式,但密碼總是認證系統的最初一步。

關於如何儲存密碼也是有各種不同的實作,從明文儲存、加密儲存到雜湊值儲存,加密跟雜湊都具備不同的演算法。

然而,對於密碼儲存的核心思想應該不脫離一個原則:除了使用者本人以外,不應該有其它人能夠知曉

前導知識

為了瞭解現代密碼儲存的方式,我們必須先說明一些前置基礎知識。

加密

在密碼學中,加密(英語:Encryption)是將明文資訊改變為難以讀取的密文內容,使之不可讀的過程。只有擁有解密方法的物件,經由解密過程,才能將密文還原為正常可讀的內容。--維基百科:加密條目

一般而言,加密有幾個特點

  • 密文內容難以推測回明文內容
  • 密文內容在擁有解密的方法時,可以變回明文內容

常見的加密法有 RSAAES 等,如果有興趣的話可以詳加研究,在此不多贅述。

雜湊

雜湊(英語:Hashing)是電腦科學中一種對資料的處理方法,通過某種特定的函式/演算法(稱為雜湊函式/演算法)將要檢索的項與用來檢索的索引(稱為雜湊,或者雜湊值)關聯起來,生成一種便於搜尋的資料結構(稱為雜湊表)。舊譯哈希(誤以為是人名而採用了音譯)。它也常用作一種資訊安全的實作方法,由一串資料中經過雜湊演算法(Hashing algorithms)計算出來的資料指紋(data fingerprint),經常用來識別檔案與資料是否有被竄改,以保證檔案與資料確實是由原創者所提供。--維基百科:雜湊

一般而言,雜湊有幾個特點

  • 雜湊後的資料無法回推回明文資料:在大部份的雜湊演算法中,會遺失資訊
  • 雜湊的用途在於「驗證資料的正確性」:當輸入的內容相同時會產生相同的雜湊資料,憑此驗證來源是合法的

密碼儲存時的抉擇

一般而言,密碼僅用於「驗證」使用者是否合法,所以在大部份的 Use Case 中都會希望直接使用雜湊即可。

在部份較舊的設計中,常常可以見到密碼以明文儲存,這是絕對不應該的:如果今天有個工程師有存取資料庫的權限,那他可以得知所有用戶的密碼,這已經悖逆了「密碼」本質上的意義。

雜湊演算法

雜湊有多種演算法,甚至可以自行設計(並不建議)。以下介紹幾個常見用於密碼儲存的(但不一定是正確的)演算法。

不建議繼續使用的演算法

MD5

對於老式一些的系統,可能會使用 md5 作為雜湊的方式。

$hashed = md5($password);

但此演算法於 1996 年已被證實存在弱點,並在 2009 年發現已可以讓一般電腦在數秒內得出碰撞的方法。

SHA1

SHA1 被喻為 md5 的後繼者,於是在 md5 宣告殞落後,有許多人紛紛轉向 sha1。

$hashed = sha1($password);

然而此方法已被證實存在缺陷,在 2000 年以後已不被大多數的場景所接受,於 2017 年 Google 宣告已攻破(但沒有公開發表破解方式)

現代使用的演算法

以上的演算法除了在密碼學上存在缺陷之外,還有一個對於密碼儲存的致命傷:相同密碼雜湊出來的值都是相同的。

這在原理上是能夠理解的:如果每次雜湊出來的值都不同,又如何能夠比對雜湊後的值?

bcrypt

bcrypt 是專門為了密碼而設計的雜湊函式,它預設加入了鹽(Salt)的機制,這也讓它每次雜湊出來的值都是不一樣的。

$hashed = password_hash($password, PASSWORD_BCRYPT);

由 bcrypt 產生的雜湊值分為幾個部份

  • Plain Text:password
  • Hashed Value:$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');

argon2

bcrypt 是在 1999 年於 USENIX 中展示出來的,當時對於並行運算用於雜湊碰撞還並不成熟,在當時的設計中也未考量這樣的可能性。

在 2014 年,有人使用 ARM 混合 FPGA 的硬體嘗試攻擊這個演算法,並且發表了一篇論文,這讓 bcrypt 的安全性被拿出來重新審視。

在 2015 年,Argon2 贏得了 PHC 大賽,它共有三個版本

  • argon2d:以抵禦平行運算(如 GPU)破解,但可能引入旁道攻擊
  • argon2i:優化過的 argon2d,可以抵抗旁道攻擊
  • argon2id:argon2d 及 argon2i 的混合版本,除非在有明確的需求時,否則建議使用這個版本

在 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 等方法是無法反推回原本用戶密碼的,一般來說我會用以下步驟進行系統遷移

  1. 以舊雜湊方式認證用戶
    • 如果認證成功則將密碼以新雜湊方式寫入同資料庫,並讓使用者登入
    • 如果認證失敗則見 2.
  2. 以新雜湊方式認證用戶
    • 如果認證成功則讓使用者登入
    • 如果認證失敗表示使用者使用不正確的密碼,認證失敗

這樣的方式有些缺陷:資料庫中可能存在許多不同時期的雜湊方法,且經過越多入的迭代會讓認證的邏輯層變得越來越複雜。

有部份的人認為應該將密碼分為 original_passwordnew_password 兩個欄位,無論使用哪個欄位都可以認證使用者,當遷移期眼已到但都未能登入的使用者就直接刪除該帳戶的密碼,若要找回帳戶就循密碼重設的流程進行。

這樣的方式缺點在於需要更動資料庫的欄位,而這對老系統而言是風險比較高的操作。

後記

基本上並不期待開發者會使用諸如 bcrypt 或 argon 之類的現代雜湊演算法,但還是必須強調密碼的儲存請 務必 經過雜湊(非加密,否則若是被拿到密鑰就跟直接存明碼沒有兩樣)再後儲存。


上一篇
Day 04:trait 的使用
下一篇
Day 06: yield 的使用
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Ho.Chun
iT邦新手 5 級 ‧ 2020-09-29 14:49:58

整理得很清楚! 感謝 /images/emoticon/emoticon41.gif

我要留言

立即登入留言