一文詳解密碼學Hash算法的安全問題(加鹽+HMAC)

文章- 一文詳解密碼學中的Hash 算法, 我們介紹了密碼學中Hash算法的性質、分類以及使用場景。在使用場景中,我們介紹了這些場景的具體使用方式方法。爲了保持文章的連貫性和可讀性, 對於其中的潛在安全問題,我們簡單一筆帶過。今天我們另開一篇文章,着重介紹密碼學Hash算法的主要安全問題以及對應的解決辦法。希望能大家使用密碼學Hash算法帶來更多維度的考量。

密碼學Hash算法作爲身份驗證的安全問題

上一篇文章我們介紹了怎樣使用密碼學Hash算法的單向特性來作爲用戶身份驗證的方案:

密碼學Hash算法必須具備有單向性,也就是不可逆,不能從Hash值逆向推算出原始值,基於這種特性,我們可以用來進行用戶身份驗證。用戶登錄都需要進行用戶名密碼的校驗,如果我們將用戶密碼存在數據庫裏面,一旦數據庫泄露,那就所有人的密碼都泄露了,這樣的事情前幾年發生了不少!那麼如果我們將用戶的原始密碼進行Hash運算,並只是把Hash值保存在數據庫裏面,當用戶登錄時,我們計算用戶輸入密碼的Hash值,並與數據庫的Hash值進行比較,如果相同則驗證通過,不同則失敗。而用戶的明文密碼不進行保存,這樣一來,萬一數據庫泄露了,也不會一下子泄露了全部的用戶密碼

簡單地說 用戶的密碼在系統中變成了 f ( p ) = h ( p ), 而驗證的時候, 計算h(p’) = h ( p ), 其中p爲用戶設置的密碼,p’爲登錄時用戶輸入的密碼,這樣系統就可以不用明文保存用戶的密碼。比如常見密碼password1 用SHA256保存在數據庫就變成了0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e , 登錄時系統校驗 SHA(input)是否等於這個Hash值,相同則通過驗證。

感覺這樣很可行,就連繫統管理員直接查DB也不知道你的密碼是什麼,因爲目前常用的Hash算法不可逆的特性都還沒被打破,也就是基本沒辦法從Hash值反推出原始消息。

重要聲明

我們這裏將會根據計算機網絡安全所遇到的問題進行介紹,目的是提高我們開發人員的密碼安全意識,提供系統的攻防能力!一定要在合法的前提下進行網絡安全的攻防演練!

彩虹表

什麼是彩虹表

道高一尺魔高一丈!反推不行,那我就乾脆“正推”。上一篇文章我們介紹Hash算法的時候有一個特性相同的輸入消息總是能得到相同的Hash值. 那如果有一個Hash值的表,裏面包含了原文以及Hash值,如果我的Hash值跟你係統的Hash值一樣,那我就可以知道你的密碼是什麼了!!!比如你的密碼是password1, 然後數據庫存的是SHA的值0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e, 這時如果攻擊者的密碼錶中 有如下數據:

sequence hash password
1 e5e8b2d214db8f3689be77f6fde9b64164b3e792efb329e9a9b53993055d6c8e strongPassword1
2 0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e password1

經過匹配,很快就可以知道第2條數據的hash值跟你密碼的hash值是一樣的,那麼他就可以推斷出你的密碼是password1, 因爲Hash值相同。 這個表就叫做彩虹表(rainbow table) , 彩虹表都是很大的,才能更快更準地 破解 搞到的密碼庫。一般的彩虹表都要100GB以上的, 而像 CMD5 這種提供公開Hash算法的反向查詢(就是你給個Hash值,它能查出原文是什麼),他需要的彩虹表則是更大了,聲稱有90萬億條,數據庫的大小都有500TB了:

本站針對md5、sha1等全球通用公開的加密算法進行反向查詢,通過窮舉字符組合的方式,創建了明文密文對應查詢數據庫,創建的記錄約90萬億條,佔用硬盤超過500TB,查詢成功率95%以上,很多複雜密文只有本站纔可查詢。自2006年已穩定運行十餘年,國內外享有盛譽。

假設我們的密碼是 “strongpassword” 的SHA256爲05926fd3e6ec8c13c5da5205b546037bdcf861528e0bdb22e9cece29e567a1bc, 在CMD5反查的結果爲:
在這裏插入圖片描述

當然,爲了生存,免費的賬戶只能查詢簡單的原文組成規則,複雜的,比如大小寫+字母+數字等,則要收費

彩虹表從哪來的

比較合理合法的做法是自己窮舉字符組合,然後算出各種組合在各種Hash算法下的值。也有人提供了彩虹表生成工具,比如RainbowCrack , 它可以根據你要的密碼複雜度,生成對應的彩虹表。

還有一種情況,黑客通過SQL注入等手段,侵入到目標網站去,然後把用戶密碼錶給導出來,這種行爲叫做 “脫庫”。萬一這個網站心比較大,密碼都是明文保存的,那就公共事件了,直接密碼泄露了。這時就會有人根據這些真實密碼 去積累彩虹表的數據了。 所以大家一定要給用戶密碼做好Hash等密碼保護手段,同時要做好SQL注入等安全問題

彩虹表怎麼用

“脫庫” 這個黑客用到的術語,而這個術語一般會伴隨着 “撞庫” 而來。脫庫積累了彩虹表的數據,那麼下一步就要根據已推測出來的密碼,逐一到目標網站去嘗試登陸,登陸成功就得到驗證過的密碼了,這個過程就叫 “撞庫”. “撞庫” 行爲是用戶驗證模塊的開發人員要特別注意的,我們需要對這種行爲作出各種防護處理。比如常見的如果密碼錯誤5次就要等幾分鐘再可以重試,或者像銀行這種,試錯3次就只能第二天才能嘗試,再錯幾次可能就要去櫃檯了。這種就極大地阻止了撞庫的成功率,降低了他們的投入產出比,從而放棄啃這塊硬骨頭。

Hash加鹽

看到上面可能的安全問題,難道我們就不能用Hash進行密碼保存來作爲身份驗證碼?也是可以的! 只不過再多做一點事情讓它更安全一點就行。

靜態鹽

既然常見的密碼hash很容易被推算出來,那麼我可以在原來密碼的基礎上,加上一些其它字符串再計算Hash,當驗證的時候我也在用戶輸入的密碼加上對應的字符串再進行驗證,這樣別人再反推就很難了。比如前面的strongpassword 一下就個CMD5查出來了,如果我混進一個隨機字符串ghuarxlheq後面, 變成strongpasswordghuarxlheq , 這會計算出來的hash爲664d9d6484cd8988d7a3c313c03b3df148ad01dd7b6d434deb231e0b34a2b7bb, 那麼CMD5就查不出來了:

在這裏插入圖片描述
使用靜態鹽有一個潛在問題,如果某一個密碼碰巧被猜出來了,或者鹽被泄露出來,而由於其它密碼也使用了相同的鹽,那就它們被攻破的可能性就很大。

隨機鹽

加鹽確實能很大程度地避免彩虹表攻擊,但是上面說到的靜態鹽確實存在着風險,有點像密碼學中講到的 前向安全性 。 如果每個密碼都用隨機生成的鹽,那麼即使某一個密碼泄露,或者某一個鹽泄露了,受影響也只是一個密碼而已,其它密碼基本都還安全。這樣隨機生成的鹽就叫做 隨機鹽,其實就是一串隨機字符。

鹽的存儲

既然每一密碼都有一個獨立的鹽,那麼這個鹽該怎麼存呢? 如果條件允許,把鹽也獨立存放在一張表,這樣即使密碼錶泄露了,鹽表沒泄露,那麼基本還是安全的。但這樣就隨着帶來了性能問題,畢竟每次身份驗證都需要查詢兩張表。如果擔心性能,可以把鹽跟密碼存在同一張表,這樣身份驗證的時候就只需要查詢一張表,當然隨着而來就是表泄露的時候同時泄露了密碼hash跟鹽值。不過話說回來,即使拿到hash以及鹽,也彩虹表攻擊的難度也增大了很多,你需要知道加鹽的算法,然後把整個彩虹表重新算一遍hash值。當然,架構設計不就是各種平衡取捨嘛! 需要根據實際業務系統需求選擇不同的密碼保存方式。

還有一種是不額外產生鹽,而是用用戶表中的已有字段,通過自己的算法產生一個鹽,然後進行Hash計算。比如用 ID+Email的hash值作爲鹽值 等等。這些都可以自己很方便地實現!

Java加鹽Hash的實現

實現加鹽Hash不是特別複雜,一般根據自己的加鹽算法,把原文跟鹽連在一起,再進行hash計算,比如下面的實現:

 public static String sha256WithSalt(String content, String salt) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    String forHashContent = content + salt;
    digest.update(forHashContent.getBytes());
    return HexUtils.toHexString(digest.digest());
  }

HMAC

Hash一致性校驗的問題

上一篇文章我們介紹了用Hash進行文件或者消息內容一致性的校驗,在大多數情況都是沒問題的,比如對於docker鏡像文件的下載。但在一些特殊情況下,比如JWT 口令,這種需要設計用戶的身份信息的場景下,如果有人惡意串改內容,比如把裏面的用戶權限從只讀 改成讀寫來騙過驗證系統,那麼就會有風險。尤其是在現在的很多微服務架構中,很多設計都是以JWT進行統一身份認證。而爲了提供性能,很多微服務在收到JWT口令時,不會去調用統一認證平臺進行校驗,而是選擇自行校驗這個口令的真實性,因爲裏面有Hash值。

在這裏插入圖片描述

這種情況我們,我們就需要對Hash進行更爲安全的處理,比如簡單的HMAC。

HMAC

HMAC - Hash-base Message Authentication Code, 基於Hash的消息驗證碼。從名字就可以看出它主要是用來驗證消息的一致性以及真實性。HMAC算法的基本思想也跟加鹽類似,只不過實現方式不同。加鹽算是簡單粗暴地把原文跟鹽加在一起進行hash運算,而HMAC則是將密碼key補位,然後與明文分組進行異或運算,並且將該輸出與下一個分組進行異或運算,直到算出最後的Hash值。具體算法可以觀摩一下Coursera課程用到的這張圖:

在這裏插入圖片描述

JWT的一般驗證方式是 token統一認證平臺將約定的key通知給各個微服務,大家在收到JWT之後,用這個key去計算JWT的Hash,再與JWT的hash值進行比較來實現消息驗證。比如下圖,我們用了mySSOCenter作爲HMAC的key進行消息的Hash運算得到一個簽名,同時也可以進行消息的驗證:

在這裏插入圖片描述

Java HMAC的實現

JDK中自帶了HMAC的實現,下面的代碼就可以計算以及驗證Hash了:

  public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
    String content = "this is origin content";
    String secret = "rootPassword";
    String hmac = generateHmac(ALGORITHM_HmacSHA1, content, secret);
    System.out.println("hmac -> "+ hmac);
  }

  public static String generateHmac(String algorithm, String content, String key)
      throws NoSuchAlgorithmException, InvalidKeyException {
    SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(secretKey);
    byte[] hashByte = mac.doFinal(content.getBytes());
    return HexUtils.toHexString(hashByte);
  }

好了,關於密碼學Hash算法的安全問題到此就告了一段落了,希望能有助於大家理解系統中密碼保存的機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章