過去一段時(shí)間以來, 許多的網(wǎng)站遭遇用戶密碼數(shù)據(jù)泄露事件, 這其中包括頂級的互聯(lián)網(wǎng)企業(yè)–Linkedin, 國內(nèi)諸如CSDN,該事件橫掃整個(gè)國內(nèi)互聯(lián)網(wǎng),隨后又爆出多玩游戲800萬用戶資料被泄露,另有傳言人人網(wǎng)视译、開心網(wǎng)怠硼、天涯社區(qū)、世紀(jì)佳緣吧秕、百合網(wǎng)等社區(qū)都有可能成為黑客下一個(gè)目標(biāo)。層出不窮的類似事件給用戶的網(wǎng)上生活造成巨大的影響迹炼,人人自危砸彬,因?yàn)槿藗兺?xí)慣在不同網(wǎng)站使用相同的密碼,所以一家“暴庫”斯入,全部遭殃拿霉。
那么我們作為一個(gè)Web應(yīng)用開發(fā)者,在選擇密碼存儲(chǔ)方案時(shí), 容易掉入哪些陷阱, 以及如何避免這些陷阱?
普通方案
目前用的最多的密碼存儲(chǔ)方案是將明文密碼做單向哈希后存儲(chǔ)咱扣,單向哈希算法有一個(gè)特征:無法通過哈希后的摘要(digest)恢復(fù)原始數(shù)據(jù)绽淘,這也是“單向”二字的來源。常用的單向哈希算法包括SHA-256, SHA-1, MD5等闹伪。
//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))
單向哈希有兩個(gè)特性:
1)同一個(gè)密碼進(jìn)行單向哈希沪铭,得到的總是唯一確定的摘要壮池。
2)計(jì)算速度快。隨著技術(shù)進(jìn)步杀怠,一秒鐘能夠完成數(shù)十億次單向哈希計(jì)算椰憋。
結(jié)合上面兩個(gè)特點(diǎn),考慮到多數(shù)人所使用的密碼為常見的組合赔退,攻擊者可以將所有密碼的常見組合進(jìn)行單向哈希橙依,得到一個(gè)摘要組合, 然后與數(shù)據(jù)庫中的摘要進(jìn)行比對即可獲得對應(yīng)的密碼。這個(gè)摘要組合也被稱為rainbow table硕旗。
因此通過單向加密之后存儲(chǔ)的數(shù)據(jù)窗骑,和明文存儲(chǔ)沒有多大區(qū)別。因此漆枚,一旦網(wǎng)站的數(shù)據(jù)庫泄露创译,所有用戶的密碼本身就大白于天下。
進(jìn)階方案
通過上面介紹我們知道黑客可以用rainbow table來破解哈希后的密碼墙基,很大程度上是因?yàn)榧用軙r(shí)使用的哈希算法是公開的软族。如果黑客不知道加密的哈希算法是什么,那他也就無從下手了残制。
一個(gè)直接的解決辦法是立砸,自己設(shè)計(jì)一個(gè)哈希算法。然而初茶,一個(gè)好的哈希算法是很難設(shè)計(jì)的——既要避免碰撞颗祝,又不能有明顯的規(guī)律,做到這兩點(diǎn)要比想象中的要困難很多纺蛆。因此實(shí)際應(yīng)用中更多的是利用已有的哈希算法進(jìn)行多次哈希吐葵。
但是單純的多次哈希规揪,依然阻擋不住黑客桥氏。兩次 MD5、三次 MD5之類的方法猛铅,我們能想到字支,黑客自然也能想到。特別是對于一些開源代碼奸忽,這樣哈希更是相當(dāng)于直接把算法告訴了黑客堕伪。
沒有攻不破的盾,但也沒有折不斷的矛±醪耍現(xiàn)在安全性比較好的網(wǎng)站欠雌,都會(huì)用一種叫做“加鹽”的方式來存儲(chǔ)密碼,也就是常說的 “salt”疙筹。他們通常的做法是富俄,先將用戶輸入的密碼進(jìn)行一次MD5(或其它哈希算法)加密禁炒;將得到的 MD5 值前后加上一些只有管理員自己知道的隨機(jī)串,再進(jìn)行一次MD5加密霍比。這個(gè)隨機(jī)串中可以包括某些固定的串幕袱,也可以包括用戶名(用來保證每個(gè)用戶加密使用的密鑰都不一樣)。
//import "crypto/md5"
//假設(shè)用戶名abc悠瞬,密碼123456
h := md5.New()
io.WriteString(h, "需要加密的密碼")
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
//指定兩個(gè) 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))
在兩個(gè)salt沒有泄露的情況下们豌,黑客如果拿到的是最后這個(gè)加密串,就幾乎不可能推算出原始的密碼是什么了浅妆。
專家方案
上面的進(jìn)階方案在幾年前也許是足夠安全的方案望迎,因?yàn)楣粽邲]有足夠的資源建立這么多的rainbow table。 但是狂打,時(shí)至今日擂煞,因?yàn)椴⑿杏?jì)算能力的提升,這種攻擊已經(jīng)完全可行趴乡。
怎么解決這個(gè)問題呢对省?只要時(shí)間與資源允許,沒有破譯不了的密碼晾捏,所以方案是:故意增加密碼計(jì)算所需耗費(fèi)的資源和時(shí)間蒿涎,使得任何人都不可獲得足夠的資源建立所需的rainbow table。
這類方案有一個(gè)特點(diǎn)惦辛,算法中都有個(gè)因子劳秋,用于指明計(jì)算密碼摘要所需要的資源和時(shí)間,也就是計(jì)算強(qiáng)度胖齐。計(jì)算強(qiáng)度越大玻淑,攻擊者建立rainbow table越困難,以至于不可繼續(xù)呀伙。
這里推薦scrypt方案补履,scrypt是由著名的FreeBSD黑客Colin Percival為他的備份服務(wù)Tarsnap開發(fā)的。
dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)
通過上面的的方法可以獲取唯一的相應(yīng)的密碼值剿另,這是目前為止最難破解的箫锤。
總結(jié)
看到這里,如果你產(chǎn)生了危機(jī)感雨女,那么就行動(dòng)起來谚攒。