在實用密碼學工具——編碼與解碼中說到了編碼解碼的本質是資訊相同的兩個空間之間的對映關係,今天談論的Hash則是把資訊從任意大小的空間向指定大小的空間對映的一個工具。

在我剛學完C的語法的那個時候,看過某本(國產二手)資料結構書。書上談論的雜湊表讓我整整一天沒有弄明白,這個奇怪的名詞到底說的是什麼。其他的資料結構都可以顧名思義,連結串列就是一個個節點用指標等方式串起來,陣列就是一組元素能夠按數字編號做索引隨時查改。雜湊是個啥?在看了幾個例子,稀裡糊塗地反覆看了幾遍“碰撞解決”後,我打算放棄這部分內容。

現在回頭來看,國產二手貨真是害人不淺,就和應試教育一樣只教會了答案,有沒有懂全憑悟性。於是在寫這篇時,我在一開始就給Hash一個直接的定義。編碼解碼處理的是完全相同的資訊,不管對映前後空間如何變化,編碼和解碼儲存的資訊是相同的,處理前後的空間大小至少要能存放下原有的資訊。相比較而言,Hash是對映到指定大小的空間,不論這個空間是否足夠容納原資訊,也完全不考慮逆向操作的可能,Hash要做到的,只是提取一些原資訊的

特徵

,使得對映後空間中的資訊能夠一定程度上用於

區別

對映前的資訊。

生活中,我們常常做一些可以被認為是Hash的事情。我們會說”那個戴眼鏡的男的,那個穿紅衣服的女的“之類,把一個人用簡單的性別(男/女)+衣著(有限的幾個形容詞)就給“代表”了。Hash出場的場合正是這個,用簡單的方式,去區別原資訊。

網上下載了一個可執行程式檔案,怎麼才能知道下載過程中沒有錯誤,下載的東西沒有被掉包,沒有被安插木馬?算一個Hash值,然後和網站上給出的Hash值對比一下,如果你信任那個網站上的值,則你就(基本上)可以信任你手上的這個檔案(當然,這裡其實有很多細節)。這類Hash的應用叫做Checksum,比較兩段資訊,並不需要每次都完整地從頭對比到尾,只需要預先各算一次Hash值作為各自的Digest,以後只要比較Digest即可判斷是否(很可能)是同一個檔案。當然,從這個意義上來說,其實檔案大小就是一個Hash值。

設計用來處理固定長度內容的,對抗隨機錯誤的CRC和設計用來處理不固定長度內容的,對抗人為修改的密碼學安全的MD系列(MD4、MD5、MD6),SHA系列(SHA-1、SHA-2、SHA-3)Hash有著不同的目標,自然也會有不同的屬性。所謂的密碼學安全是指,Hash運算是單方向的,難以從Hash值知曉原資料的屬性,包括:

給定Hash值H,難以找到其對應的資料(但從資料到H相對很簡單)

給定資料A的Hash值為H,難以構造出Hash值同為H但內容不同的資料B

給定hash值H,難以找出其對應得一對資料A和B(碰撞)

這裡所說的“難以”,不僅僅是對映前資料量大於對映後空間容量的這種“理論上不可能”,還包括對映前資料量小於對映後空間容量,但不經過Hash計算這個單向的過程,“實際上無法區別”哪個對映前資料會對應與對映後的Hash值。

這類Hash函式有這樣兩個特點:

一個是設計的Hash值一般比較長,最常用的MD5是128-bit,SHA-1是160bit,推薦使用的SHA-2至少是224bit,最多到512bit。這是因為,他們面對的輸入空間是無窮的,輸出空間要足夠大才能保證足夠的強度不足以被暴力窮舉法破解,同時也可以顯著減少不同的輸入產生相同輸出的情況(碰撞)。

另一個是Hash值看起來非常隨機,不論輸入資訊如何挑選,輸出的Hash值每一位是0還是1的機率都相同。這也是開始設計時決定的,如果看起來不隨機,那麼就表示可以由某種規律構造輸入資訊,得到某些位的機率比另外一種構造的輸入要大,也就是依照某種規律就能大大降低了找到另一個會“碰撞”的輸入資訊的難度,就不符合“密碼學安全”的假設了。

很不幸的是,目前MD4、MD5、SHA-1均已經被“攻破”,即“難以”的難度可以透過某些方法大大簡化(實際上相比於計算Hash值依然非常困難,依然是單向的)。但是,這並不意味這日常不能使用這些Hash演算法。事實上,MD4廣泛用於電驢(eDonkey2000)網路的檔案標識,MD5也被廣泛用於儲存密碼。僅僅表示,這些演算法得到的Hash已經不如設計之初的安全,有被“破解”的風險。很多不需要安全,但可以利用到這些Hash演算法其他方面屬性的地方,依然可以使用。安全,僅僅是密碼相關Hash函式所需要的屬性。目前推薦使用的是密碼學安全Hash演算法是SHA-2。SHA-3採用了和SHA-2不同的設計理念,防止未來SHA-2被“破解”時沒有安全的方法可以使用。

另一類Hash的應用則是之前提到的“雜湊表”Hash Table,同樣是用簡單的方式去區別原資訊。Hash Table是儲存Key-Value Pair的一種資料結構,計算Value的Hash值作為Key,在這個Key相關的位置儲存這個Value;以後要找修改這個Value時,只需要再次計算Key,就能直接找到它的儲存位置,而不用考慮訪問表中的其他KVP。每次操作的計算複雜度是O(1)——不隨著Hash Table儲存的元素的數量而變化。

當然,上面所描述的Hash Table是perfect hash table,即不會發生碰撞——兩個不同的Value算出的一定是不同的Key;而實際應用時,需要儲存的KVP的數量常常會大於Hash Key空間的容量;即便Hash Key的空間足夠大,依然可能存在一定機率的碰撞,在使用Hash Table時,需要考慮到碰撞後的策略,從效能出發,也要選擇更好的Hash演算法和更合適的Hash值空間大小。一些碰撞少的Hash演算法,還用來產生UUID,使得在一個分散式系統中絕大部分時候都不需要需要統一分配ID。

最後,稍稍提一下Bloom Filter。Hash值相同的資訊不一定相同(碰撞),Hash值不同的資訊一定不相同。Bloom Filter就是把很多資訊的Hash值按位“或”的方式合併起來,這樣,如果某個資訊的Hash值不在Bloom Filter裡,則該資訊一定是第一次經過Bloom Filter;反之,一個資訊的的Hash值在Bloom Filter裡,則有一定機率該資訊已經經過了Bloom Filter。

P。S。 Hash還用來隱藏密碼,實際上是密碼學安全Hash應用的一個特例,日後談論KDF的時候會說到。