現在比較流行的密碼存儲是對密碼明文做 HASH運算,把得到HASH值存儲到數據庫中。驗證過程就是再次對用戶發過來的明文密碼做 HASH,然后把得到HASH值與數據庫中的存儲的HASH做比較。當然在做HASH時加鹽可以提高密碼的安全性。
密碼的攻擊手段
只要了解了密碼存儲和驗證的原理,就可以想出“破解”之法;如果想要得到一個更安全的密碼存儲和驗證方案就需要去研究一下針對密碼都有哪些攻擊手段。以下內容來自于網絡。
字典和暴力破解攻擊(Dictionary and Brute Force Attacks)
最常見的破解hash手段就是猜測密碼。然后對每一個可能的密碼進行hash,對比需要破解的hash和猜測的密碼hash值,如果兩個值一樣,那么之前猜測的密碼就是正確的密碼明文。猜測密碼攻擊常用的方式就是字典攻擊和暴力攻擊。
字典攻擊是將常用的密碼,單詞,短語和其他可能用來做密碼的字符串放到一個文件中,然后對文件中的每一個詞進行hash,將這些hash與需要破解的密碼hash比較。這種方式的成功率取決于密碼字典的大小以及字典的是否合適。
這種攻擊方式,對于隨機且復雜密碼無效。
查表破解(Lookup Tables)
對于特定的hash類型,如果需要破解大量hash的話,查表是一種非常有效而且快速的方式。它的理念就是預先計算(pre-compute)出密碼字典中每一個密碼的hash。然后把hash和對應的密碼保存在一個表里。
這種攻擊方式,對于隨機且復雜密碼無效。
反向查表破解(Reverse Lookup Tables)
這種方式可以讓攻擊者不預先計算一個查詢表的情況下同時對大量hash進行字典和暴力破解攻擊。
首先,攻擊者會根據獲取到的數據庫數據制作一個用戶名和對應的hash表。然后將常見的字典密碼進行hash之后,跟這個表的hash進行對比,就可以知道用哪些用戶使用了這個密碼。這種攻擊方式很有效果,因為通常情況下很多用戶都會有使用相同的密碼。
這種攻擊方式,對于隨機且復雜密碼無效。
彩虹表 (Rainbow Tables)
彩虹表中存儲的是一個個“散列鏈”。
假設我們有一個密文散列函數H和密碼P。傳統的做法是把H(X)的所有輸出窮舉,查找H(X[y])==H(P),得出P==X[y]。
而彩虹表中的“散列鏈”是為了降低傳統做法對空間的要求。首先需要定義一個衰減函數 R 把散列值變換成另一字符串。通過交替運算H函數和R函數,形成交替的密碼和散列值鏈條。 例如:假設密碼是6個小寫字母,散列值為32位長,鏈條看起來可能是這樣的:
1 aaaaaa ---> 281DAF40 ---> sgfnyd ---> 920ECF10 ---> kiebgt
2 _______H()___________R()_________H()___________R()________
要生成一個表,我們選擇一組隨機的初始密碼,每一個密碼計算一個固定長度K的鏈,并只存儲每一個鏈的第一個和最后一個密碼。第一密碼被稱為始點,最后一個被稱為末點。在上面例舉的鏈中,“aaaaaa”就是始點,“kiebgt”就是末點,其他密碼(或散列值)并不被保存。
假如給定一個散列值h ,我們要反運算(找到對應的密碼),計算出一個鏈,以對h應用R函數開始,然后H函數,然后R函數,一直繼續。如果在該運算過程中的任何點(每次應用R后),我們發現該點的值匹配我們生成的表中的一個末點,那么我們就得到了相應的始點,用這個始點來重新計算鏈。這條鏈會有很高的幾率包含值h,而如果確實包含,鏈中h前面緊接的值就是我們所尋求的密碼p。
例如,如果我們給出的散列值920ECF10,我們將以對其應用R開始計算鏈:
1 920ECF10 ---> kiebgt
2 _________R()________
由于kiebgt是我們的末點之一,我們找到始點aaaaaa并開始跟這個鏈條,直到發現920ECF10:
1 aaaaaa ---> 281DAF40 ---> sgfnyd ---> 920ECF10
2 _______H()___________R()_________H()__________
因此,密碼是“sgfnyd”。
需要注意的是,這條鏈并不一定包含散列值h。因為以h開始的鏈與某一個始點的鏈條可能會合并。例如,一個散列值FB107E70,我們往下計算它的鏈條,會得到kiebgt:
1 FB107E70 ---> bvtdll ---> 0EE80890 ---> kiebgt
2 _________R()_________H()___________R()________
網絡監聽
這不是一種針對密碼儲存的攻擊手段,但卻是得到用戶密碼最有效的手段。
我把網卡狀態設置為“混雜”模式,就可以收到局域內所有主機收發的報文。或者當我有路由器的控制權時,我就可以通過路由器得到所有經過此路由的數據。
如果用戶的密碼以明文方式發送到服務器端做驗證,我就可以很輕松的得到用戶的真實口令。就算是把用戶口令求哈希后再發送到服務器端做驗證,我依然可以得到用戶口令的哈希值;然后我只需要模仿客戶端的行為向服務器發送請求,就能夠得到用戶的所有權限。
最初的密碼存儲和驗證方案
此方案就是對用戶的密碼做一次 MD5 摘要,然后將得到的“摘要信息”存儲到數據庫中。每次用戶登錄時會把用戶名和密碼明文發送到服務器端,服務器端通過用戶名從數據庫中查詢得到密碼的“摘要信息”。然后對密碼明文做 MD5 后與數據庫中存儲的密碼的“摘要信息”做比較;如果一致則登錄成功,如果不一致則提示密碼錯誤。
聽說最早一批的互聯網產品使用的就是這種“密碼方案”。我在最初做用戶登錄功能時(大學里的課程設計中)也是用的這種方案。
缺點
- 利用彩虹表,很容易被攻破。
- 網絡監聽的攻擊方式也能夠得到密碼明文。
所以不建議使用。
加鹽提高安全性
為了應對黑客們用彩虹表破解密碼,我們可以先往明文密碼加鹽,然后再對加鹽之后的密碼用哈希算法加密。所謂的鹽是一個隨機的字符串,往明文密碼里加鹽就是把明文密碼和一個隨機的字符串拼接在一起。由于鹽在密碼校驗的時候還要用到,因此通常鹽和密碼的哈希值是存儲在一起的。
采用加鹽的哈希算法對密碼加密,有一點值得注意。我們要確保要往每個密碼里添加隨機的唯一的鹽,而不是讓所有密碼共享一樣的鹽。如果所有密碼共享統一的鹽,當黑客猜出了這個鹽之后,他就可以針對這個鹽生成一個彩虹表,再將我們加鹽之后的哈希值到他的新彩虹表里去匹配就可以破解密碼了。
缺點
對于“網絡監聽”的攻擊方法沒有安全性可言。
更安全的密碼存儲和驗證策略
因為更安全,所以更復雜。
密碼存儲
密碼加鹽后計算得到 SHA256 哈希值并存儲到數據庫中,同時鹽也要存儲到數據庫。鹽是變化的,修改密碼的時候會生成新的鹽。
密碼驗證
用戶登錄前,服務器需要生成一個會話唯一的 Token
并與 用戶的“當前鹽” 一起發送到客戶端。客戶端拿到Token
和鹽之后需要進行以下步驟的操作:
- 對用戶輸入的密碼加鹽后計算得到一個 HASH 值 p_hash;(如果用戶輸入的密碼正確的話,這個值與數據庫中存儲的密碼是一樣的)
- 把上一步得到的 HASH 值 p_hash 與 用戶名拼接后,以
Token
為 key 使用 hmac_sha256_hex 計算機得到一個新的哈希值 passwd。 - 把用戶名和passwd 發送到服務器端進行密碼驗證
服務器端接收到客戶端驗證密碼的請求后需要進行以下步驟:
- 使用用戶名從數據庫中查詢得到用戶密碼的 HASH 值 passwd_hash
- 把上一步得到的 passwd_hash 與用戶名拼接后,以會話中存儲的
Token
(與之前發給客戶端的Token值相同)為 key 使用 hmac_sha256_hex 計算得到哈希值 password; - 把前端發過來的 passwd 與上一步得到的 password 做比較,如果一致則登錄成功,否則提示密碼錯誤。
- 為了防止有人對網絡進行監聽,抓取用戶向服務器提交的登錄認證請求,然后模擬用戶對抓取到的數據包進行提交,從而獲得服務器的授權;每次授權成功后,服務器需要把用戶登錄時使用的Token立馬設置失效。
優點
以上所有的攻擊手段對此方案的安全性影響都不大。當然了,前提是你的密碼夠復雜。
總結
- 所有簡單且有規律的常用密碼,即使加了鹽,對于“字典和暴力破解攻擊”也是不安全的。
- 所有簡單且有規律的常用密碼,在沒有加鹽或有大量鹽重復的情況下,對于“查表破解”,“反向查表破解”和“彩虹表”攻擊方式是不安全的。
- 所有沒有加鹽就存儲的密碼,無論復雜與否,對于“彩虹表”攻擊方式都是不安全的。
- 所有沒有在前端使用動態鹽對密碼做哈希的密碼驗證方式,對于“網絡監聽”攻擊方式都是不安全的。