為什麼雜湊鹽不會使雜湊無效?
我不是密碼學專家。我在 teamtreehouse.com 上觀看了有關使用 Express 和 Mongo 進行使用者身份驗證課程的一部分關於散列和加鹽的影片。
- 我從影片中了解到,雜湊是儲存在數據庫中的密碼的表示形式,用於防止說公司工作人員複製密碼(雜湊將適用於身份驗證中給出的密碼),並且首先通過在 a 中傳遞密碼來創建散列函式(在這種情況下為簡單散列函式)。
- 我也知道加鹽雜湊是隨機化雜湊。
我從影片中不明白的是,如果我們更改雜湊(加鹽),每次重新進行身份驗證,如何正確評估雜湊。換句話說,它怎麼可能仍然匹配密碼。
編輯:也許我應該問:創建鹽的機制是什麼,將其與雜湊並列,並確保密碼符合雜湊+鹽的組合。
直接回答
創建鹽的機制是什麼,將其與雜湊並列,並製作$$ s $$確定密碼將符合雜湊+鹽組合?
- 是什麼產生了鹽?- 隨機數生成器,在生成或更改使用者密碼時使用。
- 什麼將鹽與雜湊並列?- 如何準確儲存隨機數生成的結果是一個實現決策;它對算法沒有影響。重要的是鹽連接到儲存的值,並且(再次,單獨地)連接到 hashed 的值。
- 是什麼確保密碼符合雜湊+鹽的組合?- 這裡需要考慮兩個階段:密碼生成和密碼檢查。
在密碼生成期間,會創建一個新的隨機鹽。然後從這個鹽和使用者密碼生成要儲存的散列,並且新生成的鹽(作為明文)和散列(從兩個部分生成)都儲存在密碼數據庫中。
檢查密碼時,會從密碼數據庫中檢索這兩個欄位。檢索到的鹽與新輸入的密碼相結合,該組合的結果被散列。如果組合散列與之前儲存的散列匹配,則操作成功。因此,使用者只需要記住他們的密碼;明文鹽是從密碼數據庫中檢索到的。因為雜湊首先是從這兩個東西的組合中生成的,所以再次組合它們會再次生成相同的值。
在生成密碼和稍後檢查該密碼時使用相同的鹽值這一事實確保密碼+鹽組合在生成時和以後檢查時將具有相同的雜湊值。
長篇解釋
初始狀態:無鹽
假設您有一個密碼:
123
…以及一個加密散列函式,該函式將該密碼散列為一個字元串:
GBIQ
這樣
checkPassword 123 GBIQ
(如下實現)發出GOOD
.理解加密散列函式的設計是不可能通過暴力搜尋以外的任何方式進行反轉(即,嘗試所有可能的輸入並將它們的輸出與您的任何輸出進行比較),這一點至關重要’試圖找到)。
這有什麼問題
…現在,沒有竊取您密碼數據庫的人可以提前計算出
GBIQ
密碼的雜湊值123
,因此他們可以使用其雜湊值創建一個包含數百萬個常用密碼的大型數據庫,並將其與任何他們竊取的密碼數據庫。(此外,他們可以查看哪些使用者具有相同的雜湊值,並立即知道這些使用者具有相同的密碼)。因此,如果他們確實竊取了您的密碼數據庫,他們只需將其包含的雜湊值與他們已經找出明文密碼的雜湊值的大列表進行比較——他們找到的任何匹配項都是他們可以侵入的帳戶!
添加鹽
假設您想阻止這些攻擊——所以,您所做的就是生成一個隨機值,以在使用者更改密碼時用作“鹽”。因此,當使用者將密碼設置為 時
123
,假設您隨機生成 salt111
。然後,您獲取 的雜湊值111|123
,並得到 的結果P2C/
。然後,您將鹽和雜湊(鹽和密碼一起)儲存為:111|P2C/
(
P2C/
不同於5a3H
因為鹽是雜湊值的一部分,即使密碼沒有改變)。當使用者輸入他們的密碼時123
,您從儲存中檢索該111|
前綴,將其添加到前面,然後散列111|123
。如果結果是P2C/
,那麼您就知道使用者輸入的密碼是正確的。因此,當您為密碼生成新雜湊
123
而不指定鹽時,每個雜湊都是不同的:$ hashPassword 123 3sm|BVve $ hashPassword 123 gwu|00Eq $ hashPassword 123 84c|akWi
…但其中任何一個都通過了檢查:
$ checkPassword 123 '3sm|BVve' GOOD $ checkPassword 123 'gwu|00Eq' GOOD $ checkPassword 123 '84c|akWi' GOOD
…並更改任何部分(鹽或實際密碼)都會破壞它:
$ checkPassword 123 'ABC|BVve' BAD $ checkPassword 123 '3sm|NOOP' BAD
為什麼這行得通?salt 不僅是數據庫前綴,而且還是散列函式本身的輸入前綴。因此,每個雜湊值都因鹽而異。
優點/效果
彩虹桌攻擊被擊敗
現在,有人不能只提前計算
GBIQ
出雜湊值123
——相反,如果他們想提前計算出可能的雜湊值,他們需要計算並儲存所有可能的雜湊值salt。即使使用少量的鹽,這也會很快變得困難(因為鹽比人工生成的密碼更隨機):8 位的鹽將工作量(和所需的儲存空間)乘以 256;16 位乘 65,536;32 位乘以 4,294,967,296。因此,竊取密碼數據庫並看到條目的人
ABC|BVve
需要弄清楚可以附加哪些字元串ABC|
並將其輸入雜湊函式以BVve
作為輸出。因為123
是一個非常簡單的密碼(四個字節遠不足以成為足夠長的散列),他們可能會很快找到一些東西——但他們必須只攻擊那個密碼,而不是僅僅在他們的數據庫之間進行大型數據庫合併偷了和他們預先計算好的那個;並且使用強密碼和適當長的散列,這種攻擊可能需要很長時間。即使是相同的密碼也有不同的雜湊值
此外,如果您有第二個使用者將他們的密碼設置為
123
,那麼他們(如果您的鹽足夠長且足夠隨機)幾乎肯定會使用不同的鹽。所以假設第二個使用者得到了 salt222
。由於222|123
具有完全不同的散列111|123
(而111|123
散列到5a3H
,可能222|123
散列到CJq3
,因此該條目儲存在數據庫中222|CJq3
),因此查看密碼數據庫的人無法知道這兩個使用者的密碼相同(並且因此,如果他們想找出 CEO 的密碼,他們需要做的就是賄賂看門人,或者其他任何使用相同密碼的人)。範例實現
無鹽
上面實際上執行了前面範例中使用的
123
->轉換:GBIQ
hashPassword() { local password=$1 openssl dgst -sha256 -binary <<<"$password" \ | openssl enc -base64 \ | head -c 4 \ && printf '\n' }
因此,根據數據庫條目檢查密碼只是一種直接比較:
checkPassword() { local userPassword=$1 databaseEntry=$2 if [[ $(hashPassword "$userPassword") = "$databaseEntry" ]]; then echo "GOOD" else echo "BAD" fi }
鹽漬的
使用 salt,對密碼進行散列處理的過程會發生變化:它現在接受要在檢查中使用的鹽,並且——要生成在先前執行中使用的相同散列——必須再次給予相同的鹽。
它還具有生成新隨機鹽的能力(儘管我們可能會將這個責任放在呼叫者身上):
hashPassword() { local password=$1 salt=$2 if ! [[ $salt ]]; then # generate new random bytes for salt salt=$(openssl rand -base64 4 | head -c 3) fi # put salt at the front of our output printf '%s|' "$salt" # then also generate the hash WITH THE SALT AS PART OF THE HASHED VALUE openssl dgst -sha256 -binary <<<"${salt}|${password}" \ | openssl enc -base64 \ | head -c 4 \ && printf '\n' } checkPassword() { local userPassword=$1 databaseEntry=$2 salt hash # split the database entry into the salt and the hash IFS='|' read -r salt hash <<<"$databaseEntry" # use that salt with the user's plaintext password to generate a hash if [[ $(hashPassword "$userPassword" "$salt") = "$databaseEntry" ]]; then echo GOOD else echo BAD fi }