Files
2019-06-22 23:41:28 +08:00

100 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 9.5 儲存密碼
過去一段時間以來, 許多的網站遭遇使用者密碼資料洩露事件, 這其中包括頂級的網際網路企業Linkedin, 國內諸如 CSDN該事件橫掃整個國內網際網路隨後又爆出多玩遊戲 800 萬用戶資料被洩露,另有傳言人人網、開心網、天涯社群、世紀佳緣、百合網等社群都有可能成為黑客下一個目標。層出不窮的類似事件給使用者的網上生活造成巨大的影響,人人自危,因為人們往往習慣在不同網站使用相同的密碼,所以一家“暴函式庫”,全部遭殃。
那麼我們作為一個 Web 應用開發者,在選擇密碼儲存方案時, 容易掉入哪些陷阱, 以及如何避免這些陷阱?
## 普通方案
目前用的最多的密碼儲存方案是將明文密碼做單向雜湊後儲存,單向雜湊演算法有一個特徵:無法透過雜湊後的摘要(digest)還原原始資料,這也是“單向”二字的來源。常用的單向雜湊演算法包括 SHA-256, SHA-1, MD5 等。
Go 語言對這三種加密演算法的實現如下所示:
```Go
//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密碼")
fmt.Printf("%x", h.Sum(nil))
```
單向雜湊有兩個特性:
- 1同一個密碼進行單向雜湊得到的總是唯一確定的摘要。
- 2計算速度快。隨著技術進步一秒鐘能夠完成數十億次單向雜湊計算。
結合上面兩個特點,考慮到多數人所使用的密碼為常見的組合,攻擊者可以將所有密碼的常見組合進行單向雜湊,得到一個摘要組合, 然後與資料庫中的摘要進行比對即可獲得對應的密碼。這個摘要組合也被稱為`rainbow table`
因此透過單向加密之後儲存的資料,和明文儲存沒有多大區別。因此,一旦網站的資料庫洩露,所有使用者的密碼本身就大白於天下。
## 進階方案
透過上面介紹我們知道黑客可以用`rainbow table`來破解雜湊後的密碼,很大程度上是因為加密時使用的雜湊演算法是公開的。如果黑客不知道加密的雜湊演算法是什麼,那他也就無從下手了。
一個直接的解決辦法是,自己設計一個雜湊演算法。然而,一個好的雜湊演算法是很難設計的——既要避免碰撞,又不能有明顯的規律,做到這兩點要比想象中的要困難很多。因此實際應用中更多的是利用已有的雜湊演算法進行多次雜湊。
但是單純的多次雜湊,依然阻擋不住黑客。兩次 MD5、三次 MD5 之類別的方法,我們能想到,黑客自然也能想到。特別是對於一些開原始碼,這樣雜湊更是相當於直接把演算法告訴了黑客。
沒有攻不破的盾,但也沒有折不斷的矛。現在安全性比較好的網站,都會用一種叫做“加鹽”的方式來儲存密碼,也就是常說的 “salt”。他們通常的做法是先將使用者輸入的密碼進行一次 MD5或其它雜湊演算法加密將得到的 MD5 值前後加上一些只有管理員自己知道的隨機串,再進行一次 MD5 加密。這個隨機串中可以包括某些固定的串,也可以包括使用者名稱(用來保證每個使用者加密使用的金鑰都不一樣)。
```Go
//import "crypto/md5"
//假設使用者名稱 abc密碼 123456
h := md5.New()
io.WriteString(h, "需要加密的密碼")
//pwmd5 等於 e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
//指定兩個 salt salt1 = @#$% salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"
//salt1+使用者名稱+salt2+MD5 拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
last :=fmt.Sprintf("%x", h.Sum(nil))
```
在兩個 salt 沒有洩露的情況下,黑客如果拿到的是最後這個加密串,就幾乎不可能推算出原始的密碼是什麼了。
## 專家方案
上面的進階方案在幾年前也許是足夠安全的方案,因為攻擊者沒有足夠的資源建立這麼多的`rainbow table`。 但是,時至今日,因為平行計算能力的提升,這種攻擊已經完全可行。
怎麼解決這個問題呢?只要時間與資源允許,沒有破譯不了的密碼,所以方案是 : 故意增加密碼計算所需耗費的資源和時間,使得任何人都不可獲得足夠的資源建立所需的`rainbow table`
這類別方案有一個特點,演算法中都有個因子,用於指明計算密碼摘要所需要的資源和時間,也就是計算強度。計算強度越大,攻擊者建立`rainbow table`越困難,以至於不可繼續。
這裡推薦 `scrypt` 方案scrypt 是由著名的 FreeBSD 黑客 Colin Percival 為他的備份服務 Tarsnap 開發的。
目前 Go 語言裡面支援的函式庫 https://github.com/golang/crypto/tree/master/scrypt
```Go
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
```
透過上面的方法可以取得唯一的相應的密碼值,這是目前為止最難破解的。
## 總結
看到這裡,如果你產生了危機感,那麼就行動起來:
- 1如果你是普通使用者那麼我們建議使用 LastPass 進行密碼儲存和產生,對不同的網站使用不同的密碼;
- 2如果你是開發人員 那麼我們強烈建議你採用專家方案進行密碼儲存。
## links
* [目錄](<preface.md>)
* 上一節:[確保輸入過濾](<09.4.md>)
* 下一節:[加密和解密資料](<09.6.md>)