新聞中心
大多數(shù)的web開發(fā)者都會遇到設(shè)計用戶賬號系統(tǒng)的需求。賬號系統(tǒng)最重要的一個方面就是如何保護(hù)用戶的密碼。一些大公司的用戶數(shù)據(jù)庫泄露事件也時有發(fā)生,所以我們必須采取一些措施來保護(hù)用戶的密碼,即使網(wǎng)站被攻破的情況下也不會造成較大的危害。如果你還在存儲用戶密碼的MD5,那可真的有點弱了。趕緊來看看這篇文章吧。

保護(hù)密碼最好的的方式就是使用帶鹽的密碼hash(salted password hashing).對密碼進(jìn)行hash操作是一件很簡單的事情,但是很多人都犯了錯。接下來我希望可以詳細(xì)的闡述如何恰當(dāng)?shù)膶γ艽a進(jìn)行hash,以及為什么要這樣做。
重要提醒
如果你打算自己寫一段代碼來進(jìn)行密碼hash,那么趕緊停下吧。這樣太容易犯錯了。這個提醒適用于每一個人,不要自己寫密碼的hash算法 !關(guān)于保存密碼的問題已經(jīng)有了成熟的方案,那就是使用phpass或者本文提供的源碼。
什么是hash
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542Hash算法是一種單向的函數(shù)。它可以把任意數(shù)量的數(shù)據(jù)轉(zhuǎn)換成固定長度的“指紋”,這個過程是不可逆的。而且只要輸入發(fā)生改變,哪怕只有一個bit,輸出的hash值也會有很大不同。這種特性恰好合適用來用來保存密碼。因為我們希望使用一種不可逆的算法來加密保存的密碼,同時又需要在用戶登陸的時候驗證密碼是否正確。
在一個使用hash的賬號系統(tǒng)中,用戶注冊和認(rèn)證的大致流程如下:
1, 用戶創(chuàng)建自己的賬號 2, 用戶密碼經(jīng)過hash操作之后存儲在數(shù)據(jù)庫中。沒有任何明文的密碼存儲在服務(wù)器的硬盤上。 3, 用戶登陸的時候,將用戶輸入的密碼進(jìn)行hash操作后與數(shù)據(jù)庫里保存的密碼hash值進(jìn)行對比。 4, 如果hash值完全一樣,則認(rèn)為用戶輸入的密碼是正確的。否則就認(rèn)為用戶輸入了無效的密碼。 5, 每次用戶嘗試登陸的時候就重復(fù)步驟3和步驟4。
在步驟4的時候不要告訴用戶是賬號還是密碼錯了。只需要顯示一個通用的提示,比如賬號或密碼不正確就可以了。這樣可以防止攻擊者枚舉有效的用戶名。
還需要注意的是用來保護(hù)密碼的hash函數(shù)跟數(shù)據(jù)結(jié)構(gòu)課上見過的hash函數(shù)不完全一樣。比如實現(xiàn)hash表的hash函數(shù)設(shè)計的目的是快速,但是不夠安全。只有加密hash函數(shù)(cryptographic hash functions)可以用來進(jìn)行密碼的hash。這樣的函數(shù)有SHA256, SHA512, RipeMD, WHIRLPOOL等。
一個常見的觀念就是密碼經(jīng)過hash之后存儲就安全了。這顯然是不正確的。有很多方式可以快速的從hash恢復(fù)明文的密碼。還記得那些md5破解網(wǎng)站吧,只需要提交一個hash,不到一秒鐘就能知道結(jié)果。顯然,單純的對密碼進(jìn)行hash還是遠(yuǎn)遠(yuǎn)達(dá)不到我們的安全需求。下一部分先討論一下破解密碼hash,獲取明文常見的手段。#p#
如何破解hash
字典和暴力破解攻擊(Dictionary and Brute Force Attacks)
最常見的破解hash手段就是猜測密碼。然后對每一個可能的密碼進(jìn)行hash,對比需要破解的hash和猜測的密碼hash值,如果兩個值一樣,那么之前猜測的密碼就是正確的密碼明文。猜測密碼攻擊常用的方式就是字典攻擊和暴力攻擊。
Dictionary Attack Trying apple : failed Trying blueberry : failed Trying justinbeiber : failed ... Trying letmein : failed Trying s3cr3t : success!
字典攻擊是將常用的密碼,單詞,短語和其他可能用來做密碼的字符串放到一個文件中,然后對文件中的每一個詞進(jìn)行hash,將這些hash與需要破解的密碼hash比較。這種方式的成功率取決于密碼字典的大小以及字典的是否合適。
Brute Force Attack Trying aaaa : failed Trying aaab : failed Trying aaac : failed ... Trying acdb : failed Trying acdc : success!
暴力攻擊就是對于給定的密碼長度,嘗試每一種可能的字符組合。這種方式需要花費大量的計算機時間。但是理論上只要時間足夠,最后密碼一定能夠破解出來。只是如果密碼太長,破解花費的時間就會大到無法承受。
目前沒有方式可以阻止字典攻擊和暴力攻擊。只能想辦法讓它們變的低效。如果你的密碼hash系統(tǒng)設(shè)計的是安全的,那么破解hash唯一的方式就是進(jìn)行字典或者暴力攻擊了。
查表破解(Lookup Tables)
對于特定的hash類型,如果需要破解大量hash的話,查表是一種非常有效而且快速的方式。它的理念就是預(yù)先計算(pre-compute)出密碼字典中每一個密碼的hash。然后把hash和對應(yīng)的密碼保存在一個表里。一個設(shè)計良好的查詢表結(jié)構(gòu),即使存儲了數(shù)十億個hash,每秒鐘仍然可以查詢成百上千個hash。
如果你想感受下查表破解hash的話可以嘗試一下在CraskStation上破解下下面的sha256 hash。
c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc 08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7 e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904 5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd
反向查表破解(Reverse Lookup Tables)
Searching for hash(apple) in users' hash list... : Matches [alice3, 0bob0, charles8] Searching for hash(blueberry) in users' hash list... : Matches [usr10101, timmy, john91] Searching for hash(letmein) in users' hash list... : Matches [wilson10, dragonslayerX, joe1984] Searching for hash(s3cr3t) in users' hash list... : Matches [bruce19, knuth1337, john87] Searching for hash(z@29hjja) in users' hash list... : No users used this password
這種方式可以讓攻擊者不預(yù)先計算一個查詢表的情況下同時對大量hash進(jìn)行字典和暴力破解攻擊。
首先,攻擊者會根據(jù)獲取到的數(shù)據(jù)庫數(shù)據(jù)制作一個用戶名和對應(yīng)的hash表。然后將常見的字典密碼進(jìn)行hash之后,跟這個表的hash進(jìn)行對比,就可以知道用哪些用戶使用了這個密碼。這種攻擊方式很有效果,因為通常情況下很多用戶都會有使用相同的密碼。
彩虹表 (Rainbow Tables)
彩虹表是一種使用空間換取時間的技術(shù)。跟查表破解很相似。只是它犧牲了一些破解時間來達(dá)到更小的存儲空間的目的。因為彩虹表使用的存儲空間更小,所以單位空間就可以存儲更多的hash。彩虹表已經(jīng)能夠破解8位長度的任意md5hash。彩虹表具體的原理可以參考http://www.project-rainbowcrack.com/
下一章節(jié)我們會討論一種叫做“鹽”(salting)的技術(shù)。通過這種技術(shù)可以讓查表和彩虹表的方式無法破解hash。#p#
加鹽(Adding Salt)
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007查表和彩虹表的方式之所以有效是因為每一個密碼的都是通過同樣的方式來進(jìn)行hash的。如果兩個用戶使用了同樣的密碼,那么一定他們的密碼hash也一定相同。我們可以通過讓每一個hash隨機化,同一個密碼hash兩次,得到的不同的hash來避免這種攻擊。
具體的操作就是給密碼加一個隨即的前綴或者后綴,然后再進(jìn)行hash。這個隨即的后綴或者前綴成為“鹽”。正如上面給出的例子一樣,通過加鹽,相同的密碼每次hash都是完全不一樣的字符串了。檢查用戶輸入的密碼是否正確的時候,我們也還需要這個鹽,所以鹽一般都是跟hash一起保存在數(shù)據(jù)庫里,或者作為hash字符串的一部分。
鹽不需要保密,只要鹽是隨機的話,查表,彩虹表都會失效。因為攻擊者無法事先知道鹽是什么,也就沒有辦法預(yù)先計算出查詢表和彩虹表。如果每個用戶都是使用了不同的鹽,那么反向查表攻擊也沒法成功。
下一節(jié),我們會介紹一些鹽的常見的錯誤實現(xiàn)。
錯誤的方式:短的鹽和鹽的復(fù)用
最常見的錯誤實現(xiàn)就是一個鹽在多個hash中使用或者使用的鹽很短。
鹽的復(fù)用(Salt Reuse)
不管是將鹽硬編碼在程序里還是隨機一次生成的,在每一個密碼hash里使用相同的鹽會使這種防御方法失效。因為相同的密碼hash兩次得到的結(jié)果還是相同的。攻擊者就可以使用反向查表的方式進(jìn)行字典和暴力攻擊。只要在對字典中每一個密碼進(jìn)行hash之前加上這個固定的鹽就可以了。如果是流行的程序的使用了硬編碼的鹽,那么也可能出現(xiàn)針對這種程序的這個鹽的查詢表和彩虹表,從而實現(xiàn)快速破解hash。
用戶每次創(chuàng)建或者修改密碼一定要使用一個新的隨機的鹽
短的鹽
如果鹽的位數(shù)太短的話,攻擊者也可以預(yù)先制作針對所有可能的鹽的查詢表。比如,3位ASCII字符的鹽,一共有95x95x95 = 857,375種可能性??雌饋砗孟窈芏唷<偃缑恳粋€鹽制作一個1MB的包含常見密碼的查詢表,857,375個鹽才是837GB?,F(xiàn)在買個1TB的硬盤都只要幾百塊而已。
基于同樣的理由,千萬不要用用戶名做為鹽。雖然對于每一個用戶來說用戶名可能是不同的,但是用戶名是可預(yù)測的,并不是完全隨機的。攻擊者完全可以用常見的用戶名作為鹽來制作查詢表和彩虹表破解hash。
根據(jù)一些經(jīng)驗得出來的規(guī)則就是鹽的大小要跟hash函數(shù)的輸出一致。比如,SHA256的輸出是256bits(32bytes),鹽的長度也應(yīng)該是32個字節(jié)的隨機數(shù)據(jù)。#p#
錯誤的方式:雙重hash和古怪的hash函數(shù)
這一節(jié)討論另外一個常見的hash密碼的誤解:古怪的hash算法組合。人們可能解決的將不同的hash函數(shù)組合在一起用可以讓數(shù)據(jù)更安全。但實際上,這種方式帶來的效果很微小。反而可能帶來一些互通性的問題,甚至有時候會讓hash更加的不安全。本文一開始就提到過,永遠(yuǎn)不要嘗試自己寫hash算法,要使用專家們設(shè)計的標(biāo)準(zhǔn)算法。有些人會覺得通過使用多個hash函數(shù)可以降低計算hash的速度,從而增加破解的難度。通過減慢hash計算速度來防御攻擊有更好的方法,這個下文會詳細(xì)介紹。
下面是一些網(wǎng)上找到的古怪的hash函數(shù)組合的樣例。
md5(sha1(password)) md5(md5(salt) + md5(password)) sha1(sha1(password)) sha1(str_rot13(password + salt)) md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))
不要使用他們!
注意:這部分的內(nèi)容其實是存在爭議的!我收到過大量郵件說組合hash函數(shù)是有意義的。因為如果攻擊者不知道我們用了哪個函數(shù),就不可能事先計算出彩虹表,并且組合hash函數(shù)需要更多的計算時間。
攻擊者如果不知道hash算法的話自然是無法破解hash的。但是考慮到Kerckhoffs’s principle,攻擊者通常都是能夠接觸到源碼的(尤其是免費軟件和開源軟件)。通過一些目標(biāo)系統(tǒng)的密碼–hash對應(yīng)關(guān)系來逆向出算法也不是非常困難。
如果你想使用一個標(biāo)準(zhǔn)的”古怪”的hash函數(shù),比如HMAC,是可以的。但是如果你的目的是想減慢hash的計算速度,那么可以讀一下后面討論的慢速hash函數(shù)部分。基于上面討論的因素,最好的做法是使用標(biāo)準(zhǔn)的經(jīng)過嚴(yán)格測試的hash算法。
hash碰撞(Hash Collisions)
因為hash函數(shù)是將任意數(shù)量的數(shù)據(jù)映射成一個固定長度的字符串,所以一定存在不同的輸入經(jīng)過hash之后變成相同的字符串的情況。加密hash函數(shù)(Cryptographic hash function)在設(shè)計的時候希望使這種碰撞攻擊實現(xiàn)起來成本難以置信的高。但時不時的就有密碼學(xué)家發(fā)現(xiàn)快速實現(xiàn)hash碰撞的方法。最近的一個例子就是MD5,它的碰撞攻擊已經(jīng)實現(xiàn)了。
碰撞攻擊是找到另外一個跟原密碼不一樣,但是具有相同hash的字符串。但是,即使在相對弱的hash算法,比如MD5,要實現(xiàn)碰撞攻擊也需要大量的算力(computing power),所以在實際使用中偶然出現(xiàn)hash碰撞的情況幾乎不太可能。一個使用加鹽MD5的密碼hash在實際使用中跟使用其他算法比如SHA256一樣安全。不過如果可以的話,使用更安全的hash函數(shù),比如SHA256, SHA512, RipeMD, WHIRLPOOL等是更好的選擇。#p#
正確的方式:如何恰當(dāng)?shù)倪M(jìn)行hash
這部分會詳細(xì)討論如何恰當(dāng)?shù)倪M(jìn)行密碼hash。第一個章節(jié)是最基礎(chǔ)的,這章節(jié)的內(nèi)容是必須的。后面一個章節(jié)是闡述如何繼續(xù)增強安全性,讓hash破解變得異常困難。
基礎(chǔ):使用加鹽hash
我們已經(jīng)知道惡意黑客可以通過查表和彩虹表的方式快速的獲得hash對應(yīng)的明文密碼,我們也知道了通過使用隨機的鹽可以解決這個問題。但是我們怎么生成鹽,怎么在hash的過程中使用鹽呢?
鹽要使用密碼學(xué)上可靠安全的偽隨機數(shù)生成器(Cryptographically Secure Pseudo-Random Number Generator (CSPRNG))來產(chǎn)生。CSPRNG跟普通的偽隨機數(shù)生成器比如C語言中的rand(),有很大不同。正如它的名字說明的那樣,CSPRNG提供一個高標(biāo)準(zhǔn)的隨機數(shù),是完全無法預(yù)測的。我們不希望我們的鹽能夠被預(yù)測到,所以一定要使用CSPRNG。下表提供了一些常用語言中的CSPRNG。
每一個用戶,每一個密碼都要使用不同的鹽。用戶每次創(chuàng)建賬戶或者修改密碼都要使用一個新的隨機鹽。永遠(yuǎn)不要重復(fù)使用鹽。鹽的長度要足夠,一個經(jīng)驗規(guī)則就是鹽的至少要跟hash函數(shù)輸出的長度一致。鹽應(yīng)該跟hash一起存儲在用戶信息表里。
存儲一個密碼:
1, 使用CSPRNG生成一個長的隨機鹽。
2, 將密碼和鹽拼接在一起,使用標(biāo)準(zhǔn)的加密hash函數(shù)比如SHA256進(jìn)行hash
3, 將鹽和hash記錄在用戶數(shù)據(jù)庫中
驗證一個密碼:
1, 從數(shù)據(jù)庫中取出用戶的鹽和hash
2, 將用戶輸入的密碼和鹽按相同方式拼接在一起,使用相同的hash函數(shù)進(jìn)行hash
3, 比較計算出的hash跟存儲的hash是否相同。如果相同則密碼正確。反之則密碼錯誤。
在本文的最后,給出了php,C#,Java,Ruby的加鹽密碼hash的實現(xiàn)代碼。
在web應(yīng)用中,要在服務(wù)端進(jìn)行hash:
如果你在寫一個web應(yīng)用,可能會有在客戶端還是服務(wù)端進(jìn)行hash的疑惑。是將密碼在瀏覽器里使用javascript進(jìn)行hash,還是將明文傳給服務(wù)端,在服務(wù)端進(jìn)行hash呢?
即使在客戶端用javascript進(jìn)行了hash,在服務(wù)端依然需要將得到的密碼hash再進(jìn)行hash。如果不這么做的話,認(rèn)證用戶的時候,服務(wù)端是獲取了瀏覽器傳過來的hash跟數(shù)據(jù)庫里的hash比較。這樣子看起來是更安全了,因為沒有明文密碼傳送到服務(wù)端。但是事實上卻不是這樣。
問題在于這樣的話,如果惡意的黑客獲取了用戶的hash,就可以直接用來登陸用戶的賬號了。甚至都不需要知道用戶的明文密碼!也就不需要破解hash了。
這并不是說你完全不能在瀏覽器端進(jìn)行hash。只是如果你要這樣做的話,一定要在服務(wù)端再hash一次。在瀏覽器端進(jìn)行hash是一個不錯的想法,但是在實現(xiàn)的時候一定要考慮到以下幾點:
1, 客戶端密碼hash并不是HTTPS(SSL/TLS)的替代品。如果瀏覽器和服務(wù)器之間的連接是不安全的,中間人(man-in-the-middle)可能通過修改網(wǎng)頁的加載的javascript移除掉hash函數(shù)來得到用戶的明文密碼。
2, 有些瀏覽器可能不支持javascript,有些用戶也會禁用javascript。為了更好的兼容性,需要檢測用戶的瀏覽器是否支持javascript,如果不支持的話就需要在服務(wù)端模擬客戶端hash的邏輯。
3, 客戶端的hash也需要加鹽。一個很容想到的方式就是使用客戶端腳本請求服務(wù)器或得用戶的鹽。記住,不要使用這種方式。因為這樣惡意攻擊者就可以通過這個邏輯來判斷一個用戶名是否有效。因為我們已經(jīng)在服務(wù)端進(jìn)行了恰當(dāng)?shù)募欲}的hash。所以這里使用用戶名跟特定的字符串(比如域名)拼接作為客戶端的鹽是可以的。
使用慢速hash函數(shù)讓破解更加困難:
加鹽可以讓攻擊者無法使用查表和彩虹表的方式對大量hash進(jìn)行破解。但是依然無法避免對單個hash的字典和暴力攻擊。高端的顯卡(GPUs)和一些定制的硬件每秒可以計算數(shù)十億的hash,所以針對單個hash的攻擊依然有效。為了避免字典和暴力攻擊,我們可以采用一種稱為key擴展(key stretching)的技術(shù)。
思路就是讓hash的過程便得非常緩慢,即使使用高速GPU和特定的硬件,字典和暴力破解的速度也慢到?jīng)]有實用價值。通過減慢hash的過程來防御攻擊,但是hash速度依然可以保證用戶使用的時候沒有明顯的延遲。
key擴展的實現(xiàn)是使用一種大量消耗cpu資源的hash函數(shù)。不要去使用自己創(chuàng)造的迭代hash函數(shù),那是不夠的。要使用標(biāo)準(zhǔn)算法的hash函數(shù),比如PBKDF2或者bcrypt。PHP實現(xiàn)可以在這里找到。
這些算法采用了一個安全變量或者迭代次數(shù)作為參數(shù)。這個值決定了hash的過程具體有多慢。對于桌面軟件和手機APP,確定這個參數(shù)的最好方式是在設(shè)備上運行一個標(biāo)準(zhǔn)測試程序得到hash時間大概在半秒左右的值。這樣就可以避免暴力攻擊,也不會影響用戶體驗。
如果是在web應(yīng)用中使用key擴展hash函數(shù),需要考慮可能有大量的計算資源用來處理用戶認(rèn)證請求。攻擊者可能通過這種方式來進(jìn)行拒絕服務(wù)攻擊。不過我依然推薦使用key擴展hash函數(shù),只是迭代次數(shù)設(shè)置的小一點。這個次數(shù)需要根據(jù)自己服務(wù)器的計算能力和預(yù)計每秒需要處理的認(rèn)證請求次數(shù)來設(shè)置。對于拒絕服務(wù)攻擊可以通過讓用戶登陸的時候輸入驗證碼的方式來防御。系統(tǒng)設(shè)計的時候一定要考慮到這個迭代次數(shù)將來可以方便的增加或降低。
如果你擔(dān)心計算機的能力不夠強,而又希望在自己的web應(yīng)用中使用key擴展hash函數(shù),可以考慮在用戶的瀏覽器運行hash函數(shù)。Stanford JavaScript Crypto Library包含了PBKDF2算法。在瀏覽器中進(jìn)行hash需要考慮上面提到的幾個方面。#p#
理論上不可能破解的hash:使用加密的key和密碼hash硬件
只要攻擊者能夠驗證一個猜測的密碼是正確還是錯誤,他們都可以使用字典或者暴力攻擊破解hash。更深度的防御方法是加入一個保密的key(secret key)進(jìn)行hash,這樣只有知道這個key的人才能驗證密碼是否正確。這個可以通過兩種方式來實現(xiàn)。一種是hash通過加密算法加密比如AES,或者使用基于key的hash函數(shù)(HMAC)。
這個實現(xiàn)起來并不容易。key一定要做到保密,即使系統(tǒng)被攻破也不能泄露才行。但是如果攻擊者獲取了系統(tǒng)權(quán)限,無論key保存在哪里,都可能被獲取到。所以這個key一定要保存在一個外部系統(tǒng)中,比如專門用來進(jìn)行密碼驗證的物理隔離的服務(wù)器。或是使用安裝在服務(wù)器上特殊硬件,比如YubiHSM。
強烈建議所有大型的服務(wù)(超過10萬用戶)的公司使用這種方式。對于超過100萬用戶的服務(wù)商一定得采用這種方式保護(hù)用戶信息。
如果條件不允許使用專用驗證的服務(wù)器和特殊的硬件,依然從這種方式中受益。大部分?jǐn)?shù)據(jù)庫泄露都是利用了SQL注入技術(shù)。sql注入大部分情況下,攻擊者都沒法讀取服務(wù)器上的任意文件(關(guān)閉數(shù)據(jù)庫服務(wù)器的文件權(quán)限)。如果你生成了一個隨機的key,把它保存在了一個文件里。并且密碼使用了加密key的加鹽hash,單單sql注入攻擊導(dǎo)致的hash泄露并不會影響用戶的密碼。雖然這種方式不如使用獨立的系統(tǒng)來保存key安全,因為如果系統(tǒng)存在文件包含漏洞的話,攻擊者就可能讀取這個秘密文件了。不過,使用了加密key總歸好過沒有使用吧。
需要注意使用key的hash并不是不需要加鹽,聰明的攻擊者總是會找到辦法獲取到key的。所以讓hash在鹽和key擴展的保護(hù)下非常重要。
其他的安全措施
密碼hash僅僅是在發(fā)生安全事故的時候保護(hù)密碼。它并不能讓應(yīng)用程序更加安全。對于保護(hù)用戶密碼hash更多的是需要保護(hù)密碼hash不被偷走。
即使經(jīng)驗豐富的程序也需要經(jīng)過安全培訓(xùn)才能寫出安全的應(yīng)用。一個不錯的學(xué)習(xí)web應(yīng)用漏洞的資源是OWASP。除非你理解了OWASP Top Ten Vulnerability List,否則不要去寫關(guān)系到敏感數(shù)據(jù)的程序。公司有責(zé)任確保所有的開發(fā)者都經(jīng)過了足夠的安全開發(fā)的培訓(xùn)。
通過第三方的滲透測試也是不錯的方式。即使最好的程序員也會犯錯,所以讓安全專家來審計代碼總是有意義的。尋找一個可信賴的第三方或者自己招聘一個安全人員來機型定期的代碼審計。安全評審要在應(yīng)用生命周期的早期就開始并且貫穿整個開發(fā)過程。
對網(wǎng)站進(jìn)行入侵監(jiān)控也十分重要。我建議至少招聘一名全職的安全人員進(jìn)行入侵檢測和安全事件響應(yīng)。如果入侵沒有檢測到,攻擊者可能讓在你的網(wǎng)站上掛馬影響你的用戶。所以迅速的入侵檢測和響應(yīng)也很重要。#p#
經(jīng)常提問的問題
我應(yīng)該使用什么hash算法
可以使用
1, 本文最后介紹的代碼
2, OpenWall的Portable PHP password hashing framework
3, 經(jīng)過充分測試的加密hash函數(shù),比如SHA256, SHA512, RipeMD, WHIRLPOOL, SHA3等
4, 設(shè)計良好的key擴展hash算法,比如PBKDF2,bcrypt,scrypt
5, crypt#Library_Function_crypt.283.29)的安全版本。($2y$, $5$, $6$)
不要使用
1, 過時的hash函數(shù),比如MD5,SHA1
2, crypt的不安全版本。($1$, $2$, $2x$, $3$)
3, 任何自己設(shè)計的算法。
盡管MD5和SHA1并沒有密碼學(xué)方面的攻擊導(dǎo)致它們生成的hash很容易被破解,但是它們年代很古老了,通常都認(rèn)為(可能有一些不恰當(dāng))它們不合適用來進(jìn)行密碼的存儲。所以我不推薦使用它們。對于這個規(guī)則有個例外就是PBKDF2,它使用SHA1作為它的基礎(chǔ)算法。
當(dāng)用戶忘記密碼的時候我應(yīng)該怎樣讓他們重置
在我個人看來現(xiàn)在外面廣泛使用的密碼重置機制都是不安全的,如果你有很高的安全需求,比如重要的加密服務(wù),那么不要讓用戶重置他們的密碼。
大多數(shù)網(wǎng)站使用綁定的email來進(jìn)行密碼找回。通過生成一個隨機的只使用一次的token,這個token必須跟賬戶綁定,然后把密碼重置的鏈接發(fā)送到用戶郵箱中。當(dāng)用戶點擊密碼重置鏈接的時候,提示他們輸入新的密碼。需要注意token一定要綁定到用戶以免攻擊者使用發(fā)送給自己的token來修改別人的密碼。
token一定要設(shè)置成15分鐘后或者使用一次后作廢。當(dāng)用戶登陸或者請求了一個新的token的時候,之前發(fā)送的token都作廢也是不錯的主意。如果token不失效的話,那么就可以用來永久控制這個賬戶了。Email(SMTP)是明文傳輸?shù)膮f(xié)議,而互聯(lián)網(wǎng)上可能有很多惡意的路由器記錄email流量。并且用戶的email賬號也可能被盜。使token盡可能快的失效可以降低上面提到的這些風(fēng)險。
用戶可能嘗試去修改token,所以不要在token里存儲任何賬戶信息。token應(yīng)該是一個不能被預(yù)測的隨機的二進(jìn)制塊(binary blob),僅僅用來進(jìn)行識別的一條記錄。
永遠(yuǎn)不要通過email發(fā)送用戶的新密碼。記得用戶重置密碼的時候要重新生成鹽,不要使用之前舊密碼使用的鹽。
如果我的用戶數(shù)據(jù)庫泄露了,我應(yīng)該怎么辦
第一要做的就是弄明白信息是怎么泄露的,然后把漏洞修補好。
人們可能會想辦法掩蓋這次安全事件,希望沒有人知道。但是,嘗試掩蓋安全事件會讓你的處境變得更糟。因為你不告知你的用戶他的信息和密碼可能泄露了會給用戶帶來更大的風(fēng)險。一定要第一時間通知用戶發(fā)生了安全事件,即使你還沒有完全搞明白黑客到底滲透到了什么程度。在首頁上放一個提醒,然后鏈接到詳細(xì)說明的頁面。如果可能的話給每一個用戶發(fā)送email提醒。
向你的用戶詳細(xì)的說明他的密碼是如何被保護(hù)的,希望是加鹽的hash,即使密碼進(jìn)行了加鹽hash保護(hù),攻擊者依然會進(jìn)行字典和暴力攻擊嘗試破解hash。攻擊者會使用發(fā)現(xiàn)的密碼嘗試登陸其他網(wǎng)站,因為用戶可能在不同的網(wǎng)站都使用了相同的密碼(所謂的撞庫攻擊)。告知你的用戶存在的這些風(fēng)險,建議他們修改使用了相同密碼的地方。在自己的網(wǎng)站上,下次用戶登陸的時候強制他們修改密碼。大部分用戶可能會嘗試使用相同的密碼,為了方便。要設(shè)計足夠的邏輯避免這樣的情況發(fā)生。
即使有了加鹽的hash,攻擊者也可能快速破解一些很弱的弱密碼。為了降低這種風(fēng)險,可以在使用正確密碼的前提下,加一個郵件認(rèn)證,直到用戶修改密碼。
還要告知你的用戶有哪些個人信息存儲在網(wǎng)站上。如果數(shù)據(jù)庫包含信用卡信息,你需要通知你的用戶注意自己近期的賬單,并且最好注銷掉這個信用卡。
應(yīng)該使用怎樣的密碼策略,需要強制使用強密碼么
如果你的服務(wù)不是有很嚴(yán)格的安全需求,那么不要限制你的用戶。我建議在用戶輸入密碼的時候顯示它的強度等級。讓用戶自己決定使用什么強度的密碼。如果你的系統(tǒng)有很強的安全需求,那么強制用戶使用12位以上的密碼,至少包含2個數(shù)字,2個字母,2個字符。
每6個月最多強制用戶修改一次密碼。超過這個次數(shù),用戶就會感到疲勞。他們更傾向于選擇一個弱密碼。更應(yīng)該做的是教育你的用戶,當(dāng)他們感到自己的密碼可能泄露的時候主動修改密碼。
如果攻擊者獲取了數(shù)據(jù)庫權(quán)限,他不能直接替換hash登陸任意賬戶么
當(dāng)然,不過如果他已經(jīng)或得了數(shù)據(jù)庫權(quán)限,很可能已經(jīng)可以獲得服務(wù)器上的所有信息了。所以沒有什么必要去修改hash登陸別人賬戶。進(jìn)行密碼hash的目的不是保護(hù)網(wǎng)站不被入侵,而是如果入侵發(fā)生了,可以更好的保護(hù)用戶的密碼。
在SQL注入攻擊中,保護(hù)hash不被替換的方式使用兩個用戶不同權(quán)限的用戶連接數(shù)據(jù)庫。一個具有寫權(quán)限,另外一個只具有只讀的權(quán)限。
為什么需要一些特別的算法比如HMAC,而不是直接把密碼和加密key拼接在一起
(這部分講一些密碼學(xué)的原理,翻譯的不好請見諒)
hash函數(shù),比如MD5,SHA1,SHA2使用了Merkle–Damg?rd construction,這導(dǎo)致算法可能長度擴展攻擊(length extension attacks)。意思就是說給定一個hash H(X),攻擊者可以在不知道X的情況下,可以找到一個H(pad(X)+Y)的值,Y是個其他的字符串。pad(X)是hash函數(shù)使用的填充函數(shù)(padding function)。
這就意味者,對于hash H(key + message),攻擊者可以計算 H(pad(key + message) + extension),并不需要知道加密key。如果這個hash是用在消息認(rèn)證過程中,使用key為了避免消息被修改。這樣的話這個系統(tǒng)就可能失效了,因為攻擊者掌握了一個有效的基于 message+extension的hash。
這種攻擊對于如何快速破解hash還不是很清楚。但是,基于一些風(fēng)險的考慮,不建議使用單純的hash函數(shù)進(jìn)行加密key的hash。也許一個聰明的密碼學(xué)家一天就可以找到使用這種攻擊快速破解hash的方法。所以記得使用HMAC。
鹽應(yīng)該拼在密碼的前面還是后面
這個不重要。選擇一個并且保持風(fēng)格一致就行了。實際中,把鹽放在前面更常見一點。
為什么本文最后提供的hash代碼使用了固定執(zhí)行時間的函數(shù)來比較hash(length-constant)
使用固定的時間來比較hash是為了防止攻擊者在線上的系統(tǒng)中使用基于時間差的攻擊。這樣攻擊者就只能線下破解了。
比較兩個字符串是否相同,標(biāo)準(zhǔn)的方式是先比較第一個字節(jié),然后比較第二個字節(jié),一次類推。只要發(fā)現(xiàn)有一個字節(jié)不同,那么這兩個字符串就是不同了??梢苑祷豧alse的消息了。如果所有字節(jié)比較下來都一樣,那么這兩個字符串就是相同的,可以返回true。這就意味了比較兩個字符串,如果他們相同的長度不一樣,花費的時間不一樣。開始部分相同的長度越長,花費的時間也就越長。
基于這個原理,攻擊者可以先找256個字符串,他們的hash都是以不同的字節(jié)開頭。然后發(fā)送到目標(biāo)服務(wù)器,計算服務(wù)器返回的時間。時間最長的那一個就是第一個字節(jié)hash是正確的。依次類推。攻擊者就可能得到hash更多的字節(jié)。
這種攻擊聽起來好像在網(wǎng)絡(luò)上實現(xiàn)起來比較困難。但是已經(jīng)有人實現(xiàn)過了。所以我們在比較hash的時候采用了花費時間固定的函數(shù)。#p#
本文提供的代碼中 slowequals 函數(shù)是怎么工作的
上一回答講到了我們需要比較時間固定的函數(shù),這部分詳細(xì)講一下代碼的實現(xiàn)。
1. private static boolean slowEquals(byte[] a, byte[] b)
2. {
3. int diff = a.length ^ b.length;
4. for(int i = 0; i < a.length && i < b.length; i++)
5. diff |= a[i] ^ b[i];
6. return diff == 0;
7. }這段代碼使用了異或(XOR)操作符”^”來比較整數(shù)是否相等,而沒有使用”==”操作符。原因在于如果兩個數(shù)完全一致,異或之后的值為零。因為 0 XOR 0 = 0, 1 XOR 1 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1。
所以,第一行代碼如果a.length等于b.length,變量diff等于0,否則的話diff就是一個非零的值。然后,讓a,b的每一個字節(jié)XOR之后再跟diff OR。這樣,只有diff一開始是0,并且,a,b的每一個字節(jié)XOR的結(jié)果也是零,最后循環(huán)完成后diff的值才是0,這種情況是a,b完全一樣。否則最后diff是一個非零的值。
我們使用XOR而不適用”==”的原因是”==”通常編譯成分支的形式。比如C代碼”diff &= a == b” 可能編譯成下面的X86匯編。
MOV EAX, [A] CMP [B], EAX JZ equal JMP done equal: AND [VALID], 1 done: AND [VALID], 0
分支會導(dǎo)致代碼執(zhí)行的時間出現(xiàn)差異。
C代碼的”diff |= a ^ b”編譯之后類似于,
MOV EAX, [A] XOR EAX, [B] OR [DIFF], EAX
執(zhí)行時間跟兩個變量是否相等沒有關(guān)系。
為什么要討論這么多關(guān)于hash的東西
用戶在你的網(wǎng)站上輸入密碼,是相信你的安全性。如果你的數(shù)據(jù)庫被黑了。而用戶密碼又沒有恰當(dāng)?shù)谋Wo(hù),那么惡意的攻擊者就可以利用這些密碼嘗試登陸其他的網(wǎng)站和服務(wù)。進(jìn)行撞庫攻擊。(很多用戶在所有的地方都是使用相同的密碼)這不僅僅是你的網(wǎng)站安全,是你的所有用戶的安全。你要對你用戶的安全負(fù)責(zé)。#p#
PHP PBKDF2 密碼hash代碼
代碼下載
- /*
- * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
- * Copyright (c) 2013, Taylor Hornby
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
- // These constants may be changed without breaking existing hashes.
- define("PBKDF2_HASH_ALGORITHM", "sha256");
- define("PBKDF2_ITERATIONS", 1000);
- define("PBKDF2_SALT_BYTE_SIZE", 24);
- define("PBKDF2_HASH_BYTE_SIZE", 24);
- define("HASH_SECTIONS", 4);
- define("HASH_ALGORITHM_INDEX", 0);
- define("HASH_ITERATION_INDEX", 1);
- define("HASH_SALT_INDEX", 2);
- define("HASH_PBKDF2_INDEX", 3);
- function create_hash($password)
- {
- // format: algorithm:iterations:salt:hash
- $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
- return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
- base64_encode(pbkdf2(
- PBKDF2_HASH_ALGORITHM,
- $password,
- $salt,
- PBKDF2_ITERATIONS,
- PBKDF2_HASH_BYTE_SIZE,
- true
- ));
- }
- function validate_password($password, $correct_hash)
- {
- $params = explode(":", $correct_hash);
- if(count($params) < HASH_SECTIONS)
- return false;
- $pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
- return slow_equals(
- $pbkdf2,
- pbkdf2(
- $params[HASH_ALGORITHM_INDEX],
- $password,
- $params[HASH_SALT_INDEX],
- (int)$params[HASH_ITERATION_INDEX],
- strlen($pbkdf2),
- true
- )
- );
- }
- // Compares two strings $a and $b in length-constant time.
- function slow_equals($a, $b)
- {
- $diff = strlen($a) ^ strlen($b);
- for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
- {
- $diff |= ord($a[$i]) ^ ord($b[$i]);
- }
- return $diff === 0;
- }
- /*
- * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
- * $algorithm - The hash algorithm to use. Recommended: SHA256
- * $password - The password.
- * $salt - A salt that is unique to the password.
- * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
- * $key_length - The length of the derived key in bytes.
- * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
- * Returns: A $key_length-byte key derived from the password and salt.
- *
- * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
- *
- * This implementation of PBKDF2 was originally created by https://defuse.ca
- * With improvements by http://www.variations-of-shadow.com
- */
- function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
- {
- $algorithm = strtolower($algorithm);
- if(!in_array($algorithm, hash_algos(), true))
- trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
- if($count <= 0 || $key_length <= 0)
- trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
- if (function_exists("hash_pbkdf2")) {
- // The output length is in NIBBLES (4-bits) if $raw_output is false!
- if (!$raw_output) {
- $key_length = $key_length * 2;
- }
- return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
- }
- $hash_length = strlen(hash($algorithm, "", true));
- $block_count = ceil($key_length / $hash_length);
- $output = "";
- for($i = 1; $i <= $block_count; $i++) {
- // $i encoded as 4 bytes, big endian.
- $last = $salt . pack("N", $i);
- // first iteration
- $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
- // perform the other $count - 1 iterations
- for ($j = 1; $j < $count; $j++) {
- $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
- }
- $output .= $xorsum;
- }
- if($raw_output)
- return substr($output, 0, $key_length);
- else
- return bin2hex(substr($output, 0, $key_length));
- }
- ?>
#p#
java PBKDF2 密碼hash代碼
代碼下載
- /*
- * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
- * Copyright (c) 2013, Taylor Hornby
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
- import java.security.SecureRandom;
- import javax.crypto.spec.PBEKeySpec;
- import javax.crypto.SecretKeyFactory;
- import java.math.BigInteger;
- import java.security.NoSuchAlgorithmException;
- import java.security.spec.InvalidKeySpecException;
- /*
- * PBKDF2 salted password hashing.
- * Author: havoc AT defuse.ca
- * www: http://crackstation.net/hashing-security.htm
- */
- public class PasswordHash
- {
- public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
- // The following constants may be changed without breaking existing hashes.
- public static final int SALT_BYTE_SIZE = 24;
- public static final int HASH_BYTE_SIZE = 24;
- public static final int PBKDF2_ITERATIONS = 1000;
- public static final int ITERATION_INDEX = 0;
- public static final int SALT_INDEX = 1;
- public static final int PBKDF2_INDEX = 2;
- /**
- * Returns a salted PBKDF2 hash of the password.
- *
- * @param password the password to hash
- * @return a salted PBKDF2 hash of the password
- */
- public static String createHash(String password)
- throws NoSuchAlgorithmException, InvalidKeySpecException
- {
- return createHash(password.toCharArray());
- }
- /**
- * Returns a salted PBKDF2 hash of the password.
- *
- * @param password the password to hash
- * @return a salted PBKDF2 hash of the password
- */
- public static String createHash(char[] password)
- throws NoSuchAlgorithmException, InvalidKeySpecException
- {
- // Generate a random salt
- SecureRandom random = new SecureRandom();
- byte[] salt = new byte[SALT_BYTE_SIZE];
- random.nextBytes(salt);
- // Hash the password
- byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
- // format iterations:salt:hash
- return PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
- }
- /**
- * Validates a password using a&nbs
本文名稱:如何安全的存儲用戶的密碼
文章位置:http://fisionsoft.com.cn/article/dhgpgsc.html


咨詢
建站咨詢
