為什麼不能計算兩次哈希,以及在什麼情況下不能計算兩次哈希?

在寫一個小應用程序時我遇到的一個問題:

1. 這個小應用的功能是:接收用戶提供的三個字元串,返回這三個字元串的哈希。

2. 需要避免惡意攻擊者根據輸出的哈希值,通過查彩虹表得到用戶提供的那三個字元串。

3. 這個應用需要盡量避免使用資料庫。

4. 這個應用在任意時刻,接收相同的輸入時,需要提供相同的輸出。

我的想法:

既然是為了避免被查彩虹表,那麼我對輸入的可見字元串哈希兩次是否能解決問題?因為大部分彩虹表不會收錄不可見字元串的哈希值。

我的問題:

根據我查到的資料: 對同一個輸入計算兩次哈希是危險的,可能會導致演算法之間的相互影響。所以應該避免進行多次哈希,應該選擇加鹽。

加鹽密碼哈希:如何正確使用 - 文章 - 伯樂在線

但是我對我查到資料的理解是這樣:計算兩次哈希的安全性與公開鹽值後的安全性是相同的。

最後的問題:

我的理解是對的嗎?

進行多次哈希比起進行一次不加鹽的哈希更危險嗎?

進行多次哈希會導致演算法之間的相互影響嗎?有實例嗎?


題主最大的問題是沒有真正理解黑客「破解」hash後的密碼的技術原理。

假如一個系統允許最大16位密碼,密碼允許大小寫字元、數字以及標點等符號——我懶得算了,假設有100種取值吧。那麼,這個系統可能的密碼數(密鑰空間)就是100^16級別。

這麼大的密鑰空間,窮舉肯定是不可能了。

攻擊者可以換一個思路:用戶設置密碼,自己肯定得記住;想記住,那麼太過詭異的字元組合就不太可能,並且用戶的密碼往往也不會太長(除非專業用戶,很少有人會如此自虐)。

所以,他可以列出所有單詞、縮寫,再組合不同單詞甚至加上大小寫變化;然後再搭配不同符號作為單詞之間的分割——這個空間仍然很大,但比起100^16,那就小太多了。現代計算機(集群)跑上個把月或更長時間,差不多就能窮舉出來了。

不僅如此,他還可以弄到流傳在黑市上的、之前泄露的明文密碼:那麼,普通用戶可能想到的密碼,應該就很難超脫這個範圍了。

這叫「字典攻擊」;比起暴力窮舉,效率已經可以接受了。尤其是攻擊可下載到本地的內容時,「跑字典」早已是基礎課了。

但,遠程攻擊時,只要伺服器加一個 重試間隔 和/或 重試次數 限制,字典攻擊就失效了。

攻擊者可以再想深一步:相對於數學原理上的無解,網路伺服器的防禦總是有機可乘的。他可以想辦法攻擊伺服器,拿到伺服器上存儲的密鑰啊——伺服器肯定要存密鑰相關信息的,不然用戶怎麼登錄?

不過,稍微會玩的,在伺服器上存儲的都是已經hash過的密碼;理論上是不可從hash結果反推出密碼的。

hash函數的值域極大,理論上不可能在有效時間內窮舉整個hash值域,從而反推出原始密碼。

但是,攻擊者可以拿出自己的密碼字典,用同樣的演算法計算字典里的密碼的hash值——現代計算機速度夠快、存儲容量也夠大,完全可以做到「把字典里的密碼的hash值全部計算出來、存儲到類資料庫結構里」。

不僅如此,他還可以利用「彩虹表」暴力覆蓋幾乎全部短密鑰空間,使得只要你的密碼位數和用到的符號種類(大小寫字元、數字、標點符號等)不夠多,就逃不過他的手心(什麼是彩虹表? - 網路安全 - 知乎;隨便感謝某匿名用戶提醒,避免了我誤導他人)。

一旦這個工作完成(視字典大小/要覆蓋的密鑰長度,大概需要幾小時到幾個月吧),那麼只要用戶密碼落入覆蓋範圍,利用黑客從伺服器上偷來的hash值一反查,就可以知道密碼明文了。

事實上,攻擊者可以邊計算邊搜索,並且優先計算那些「大眾密碼」——很可能從伺服器拿到加密密鑰後幾分鐘,那些用簡單的「大眾密碼」的用戶就完蛋了。也許不到三天,一半以上的用戶已經被扒的乾乾淨淨了。

不管你如何取hash函數——不管是n次hash還是先hash再反序再用加密,或者再加幾道工序也一樣——攻擊者都可以用同樣的演算法處理他的密碼字典、以及生成對應的彩虹表,從而直接由hash值找出對應的密碼。

換句話說,無論如何加強hash函數,都無法抵抗這類攻擊。

那麼,為什麼「加鹽」就可以抵抗彩虹表了呢?

因為,「加鹽」相當於強行在用戶密碼前後附加若干隨機字元——注意每個用戶加的「鹽」應該各不相同,不然攻擊者只要把這個固定的「鹽」字元串附加進去行了,和n次hash沒兩樣,並不能給黑客帶來更多麻煩。

反之,如果每個用戶加的「鹽」各不相同,那麼攻擊者將不得不對每個用戶跑字典/生成彩虹表——假設你有一萬用戶,那麼他的彩虹表就將膨脹一萬倍:前面提到過,做這種準備需要的時間,經常是以天甚至月為單位的,一萬天就是三十多年,一萬月……

若你有幾百萬用戶……

這個時間,足夠網站發現入侵痕迹並提醒用戶換密碼了。

哪怕攻擊者只試「大眾密碼」,「必須為每個用戶跑字典」的限制也可以盡量拖延時間,把損失降到最低。

可見,加鹽顯著增加了攻擊者的時間/空間成本,使得彩虹表攻擊不再合算;配合安全審計等工作,就足以保護用戶數據安全了(當然,用簡單密碼的小白用戶……還是自求多福吧)。


這裡有兩個問題,第一個是兩次哈希事實上是另一種哈希演算法,當然加鹽也是另一種哈希演算法,但是加鹽的演算法有很多種,別人不一定有彩虹表,但是兩次哈希的只有一種,很可能別人手上就有。

另一個問題就是兩次哈希導致值域變小,因為哈希函數的值域大小總是會小於等於定義域,所以多次哈希後,就有可能造成值域縮小。當然,用於密碼哈希的時候,這個問題並不那麼重要(相較於彩虹表攻擊)。


理論上,如果你的哈希函數足夠好,那你多次哈希的安全性和你一次哈希是一樣的。

如果你的哈希函數不夠好(例如說映射並不均勻),那多次哈希的安全性只會更差。

所以這麼干只可能更糟,而不可能更好。


一切都是為了提高攻擊者破解密碼的成本。關鍵是你的數據是否有價值讓人花費資源進行破解。破解成本從低到高依次為:常用hash演算法--&>兩次hash--&>加固定鹽--&>加隨機鹽

眾所周知,出於安全考慮,現在的資料庫不直接存儲明文密碼,而是存儲密碼hash後的摘要。用戶輸入密碼後,後台將輸入hash後再和資料庫比對。這樣的好處是即使攻擊者拖來了資料庫,也無法得知密碼。他必須找到一個值,使其經過同樣的hash演算法後得到的摘要與密碼相同。這個值可能就是密碼,也可能與密碼不同。hash的過程是不可逆並伴隨信息損失的,不同的值hash後可能會有相同的結果,我們稱之為碰撞

對於常用的hash演算法,比如md5,我們可以很方便地找到一種叫彩虹表的東西。你可以把彩虹表看作一個特殊的,超大的資料庫。只要花費少量時間,絕大部分的md5摘要都能被反查出對應的原文。

所以我們現在說對密碼直接md5是不安全的。因為脫褲後,利用彩虹表直接反查就能得到與密碼hash後摘要相同的字元串。同理,因為兩次hash的做法也比較常見,雖然不像md5一樣爛大街,對密碼進行兩次常用的hash運算也已經有現成的彩虹表。

再來看看加固定鹽的情況,如果你所做的只是在用戶密碼後面加上一個「1」然後md5,效果。。。聊勝於無吧。這樣的鹽與其說是防範手段倒不如說是心理安慰。彩虹表基本可以覆蓋較短(視表的大小而定,其實也不能算短)空間內的所有字元組合。而在這樣的空間內,原文發生碰撞的概率微乎其微。舉個例子,密碼是10個字元以內的ascii碼字元,用戶的密碼是dj1Hsk,加鹽後變成dj1Hsk1,md5後為d1151dfb723be8b0999568ee11e850b5。反查彩虹表後,照樣可以把dj1Hsk1揪出來,再減去1,密碼也就到手了。輕鬆愉快~固定鹽幾乎沒有起到任何作用。

那鹽應該怎麼加呢?保險的方法是加到原本的信息量大於hash後摘要的信息量為止。舉個喪心病狂點的例子——我把整本《哈姆雷特》作為鹽加到密碼後面。這樣如果還能有人通過hash摘要反推出我們的原文——密碼+哈姆雷特,那他一定是可以不受熱力學第二定律制約的完美生物了。

固定鹽一般是指鹽的選取僅和密碼有關——比如說我存儲的是 hash(密碼+hash(密碼)+固定字元串)。這樣可以避免別人使用現成的彩虹表。小網站其實做到這種程度也足夠安全了(當然還有更推薦的方法比如加隨機鹽)。其實這種做法相當於「定製」了一個自己的hash函數。這個hash函數是全站通用的。但是如果數據足夠值錢,不排除有人從零開始,或是窮舉,或是字典攻擊,總之造出一個巨大的hash-原文表來,然後把數據一鍋端了。

然後就是比較推薦的做法——加隨機鹽。隨機鹽的取值加入了和密碼無關的因素,比如用戶名。這樣相當於每個用戶的hash函數都不相同,避免了被一張表一鍋端的情況,大大增加了破解成本。做到這種地步,基本上可以認為足夠安全了

另:彩虹表只是一種特殊的節省存儲空間的手段。彩虹表覆蓋到的每一條值都是老老實實一個一個計算出來的,相比直接hash還多了還原的步驟。造一個彩虹表比直接從零開始窮舉更費事。更何況反函數的取用更是一個技術活兒。所以我們看到的彩虹表都是md5,sha1等常用演算法的。一台電腦幾個月時間就想做一個彩虹表簡直天方夜譚。攻擊者拿到資料庫也根本不會去做個彩虹表來造福大眾。分割線下的是彩虹表的簡介。

---------------------------------------------我是分割線-------------------------------------------------------

簡單說來,彩虹表的思路就是對hash後的摘要進行「分類」後再進行破解。把散列值比做鎖的話,它就像一個滿是抽屜的大柜子。先找到鎖的鑰匙在哪個抽屜里,在從抽屜里拿出鑰匙按順序來試。說它是「空間換時間」,空間的佔用就是抽屜的數目,抽屜越多,每個抽屜裡面的鑰匙可能就越少,破解需要的時間也就隨之減少。

那麼如何知道鑰匙在哪個抽屜里呢?彩虹表用了一個非常巧妙的辦法。我們已經有了一個hash函數,記作H函數,現在我們來構造一個R函數,這個函數能將H函數生成的值逆向變化為和原文相同格式的值。

舉個例子,構造H(x)=x^2 mod 37 ;R(x)=2*x+3 (這兩個函數的構造問題很大,只是舉個栗子而已,輕拍)。然後我們任取一個數作為起始值(比如59),交替進行H和R變換:

59|3--&>9|7--&>17|30--&>63|10--&>23|11--&>25|33--&>69|25--&>53|34--&>71|9--&>21|34

於是我們就得到了這樣一條長長的鏈條,稱為彩虹鏈,每一個鏈節的後半部分是前半部分H變換的結果。提取鏈條頭尾的59和21,好,這就是一個抽屜! 彩虹表不存儲整條鏈,只存儲鏈條的頭尾,大大節省了空間。

那麼這樣的鏈條怎麼用呢?比如說我知道了密碼hash後的值是11,那麼我交替進行R變換和H變換,得到這樣的鏈條:

11--&>25|33--&>69|25--&>53|34--&>71|9--&>21

這時我對照彩虹表,發現21是一條鏈的尾部。於是我取出頭部的59開始變換:

59|3--&>9|7--&>17|30--&>63|10--&>23|11

恩,11出現了。我知道了23hash後的值是11。密碼破解完成!

你看,彩虹表的鏈條只存儲了頭尾兩個信息,你卻可以推出整條鏈來。要想把每個值的md5記錄下來,計算機的硬碟容量是遠遠不夠的。但如果只是記錄鏈條頭尾,硬碟表示我可以一戰!這也是為什麼彩虹表一般只有幾百G卻能破解絕大多數密碼的原因。


彩虹表是基於你的演算法針對構造的。進行多次哈希本質上不過是構造了一個全新的哈希函數,當然防不了彩虹表攻擊。


哈希兩次到底是指用(hash1(x), hash2(x))還是hash1(hash2(x))?


@invalid s 的答案寫的很好了。不過看評論有些人還是不理解為什麼要加鹽,以及為什麼要不同用戶加不同的鹽。現在假設我是攻擊者,你是系統設計者,我們一步步模擬看你怎樣防禦我的攻擊。

這裡的攻擊就是我在已經拖庫的情況下要得到某用戶的明文密碼。使用U,P為用戶輸入的賬號密碼。這裡只談資料庫存中存儲密碼的設計的安全,資料庫本身的安全(防止被拖庫)、密碼傳輸中的安全(https)等其它的安全問題是另外的話題,不考慮。

1、你的設計是明文保存密碼,資料庫中存儲為:

username password
Bob mima

驗證方式為:

P == password

顯然,我在拖庫之後就可以知道Bob的密碼是mima,這都不需要什麼攻擊手段。

2、密碼用hash保存(這裡使用MD5,實際中不要使用,因為它不安全)。資料庫中存儲為:

username password
Bob 3f572fcb0f9af03848738946954b8c43

驗證方式為:

hash(P) == password

這樣,我就需要找到一個字元串X,使得:

hash(X) == password

即便X不等於P也不要緊,只要能通過驗證方式就行了。那麼我怎麼辦呢?

彩虹表。彩虹表是什麼見前面答主的回答。

3、密碼用hash保存並加鹽(固定鹽)。資料庫中存儲為:

username salt password
Bob yan d71a180cbae2ee9d908cd7acce04f2be
Alice yan 52b22bb13d188b4dd43954a1da7e69fa

驗證方式為:

hash(P + salt) == password

這樣,我就需要找到一個字元串X,使得:

hash(X + salt) == password

那麼原來的彩虹表就沒有用了,但是這樣我可生成一個彩虹表專門用來查加了"yan"的字元串,這樣雖然成本有點大,時間有點久,不過我只需要生成一次這個表,就可以攻擊所有的用戶。這樣也是可以接受的。

4、密碼用hash保存並加鹽(隨機鹽)。資料庫中存儲為:

username salt password
Bob yan d71a180cbae2ee9d908cd7acce04f2be
Alice yan2 4002c52f3d33f52eb65dae8af4bd2b79

驗證方式還是:

hash(P + salt) == password

這樣的話我可能就要放棄攻擊了。

比我像在3中一樣,生成了一個專門查加了"yan"的字元串的字元,這樣只能得到Bob的密碼。而要得到Alice的密碼,我需要又生成一個專門的彩虹表。這樣的成本是不可接受的。

另外要注意在實踐中:

1、不要使用不安全的hash函數,最好是使用sha512這樣比較安全的hash函數,或者bcrypt這樣的慢hash函數。

2、加的鹽不能太短,要足夠隨機。

補充:

顯然:對於多次hash(不管多少次,頂多是需要多計算一點),其實和加一個固定鹽是一樣的。


兩次哈希本身實質上是和一次哈希是等價的,簡單來說就是

y = H(x)

由於H是自定義的,因此H(x)當然可以等同於 F(G(x))


兩次Hash之後碰撞的概率會增大

原來你的字元串原文來自於字母數字元號組合

hash一次之後就只剩下數字了

再進行第2次Hash的時候,你相當於對一串給定格式的字元串做hash,碰撞的概率是增加了的

和加鹽做對比,hash(hash(raw))的演算法相當於只有一種,給定一個raw那麼拿到的密文肯定是一樣的,你加鹽Hash了,如果破解者不知道你的鹽值,那麼你的給定一個原文raw有無限種可能的結果


md5(用戶id+輸入字元串+系統設置的密碼)


安全方面, 總是希望哪怕攻擊者能夠知道演算法,也能夠盡量的保護數據, 比方說加密演算法從來都不將安全寄托在在演算法本身的保密上, 而是使用密鑰來保證安全, 只要密鑰不泄露就好.

兩次 hash 實際上是等於實現一個新的hash演算法, 別人根據這個計算新的彩虹表就可以了. 而且就算你不告訴別人你怎麼組合的兩次hash, 黑客只要自己註冊一個賬號,設置密碼, 獲取到hash的密碼值, 反正hash演算法就那麼多, 排列組合一下,他就可以知道你怎麼做的hash了。

加鹽靈活的多, 比如你更改加鹽字元串, 結果就會大大不同, 加鹽的字元串就好像密鑰一樣. 這樣, 我們可以在系統第一次安裝的時候或者某個合適的時機, 採集某個隨機數據, 作為加鹽的數值, 這樣每個系統的加鹽的結果都不同, 假如你能夠很好的保護這個加鹽的隨機數據的話, 別人基本上是無法計算彩虹表的.


要注意的一點是,加鹽的時候,每次 hash 使用的 salt 應該是獨立隨機生成的。這樣如果計算彩虹表,就需要對每個要反解的 hash 都計算一個彩虹表。

而如果用兩遍 hash 的方法,還是只要計算一個彩虹表就可以反查生成的所有 hash.

至於多次 hash 的比進行一次 hash 更危險的問題,@Ivony 講的已經很準確了,反覆 hash 可能會縮小值域,增加碰撞概率。


哈希值不是 不可見字元串 啊,

而是只限於0-9,A-F這16個字元,比普通可見字元的範圍窄多了——這就是說攻擊的嘗試次數可以大大減少


一般原文哈希之後會縮短,會出現多個明文映射到一個哈希值的現象,也就是碰撞。

因為碰撞所以哈希函數沒有逆運算

但是哈希值是固定長度的,哈希值的哈希值長度不變,也就是一一的映射,沒有碰撞,也就可以寫出反函數。二次哈希沒有意義。(推測,未經論證)


彩虹表需要構造一個逆函數,將hash值映射回明文密碼,若干明文密文對首位相連形成一個環狀。

如果是多次hash,那麼這個逆函數就可以和hash函數是同一個,只要查找到密文屬於某個環,那麼順著此環查找可以構造出之前任意次數的hash。 而且不用真正找出明文。

簡言之就是多次hash不僅沒有增加加密強度,反而縮小的明文的空間。


因為hash兩次的表早有了,你要複雜就多加點鹽,再考慮多hash幾次才有效果


推薦閱讀:

參加CCBC 併到達終點是什麼感受?
為什麼CTR模式不是CCA安全的?
十二宮殺手的密碼為何至今無人破譯,難在哪?
如何評價王小雲成為中國科學院院士?
如何理解「量子通訊的信號安全是以犧牲通訊的穩定性為代價」這種說法?

TAG:數學 | 編程 | 計算機科學 | 密碼學 | 哈希函數 |