1. 程式人生 > >資料結構與算法系列16--雜湊演算法

資料結構與算法系列16--雜湊演算法

什麼的雜湊演算法?

將任意長度的二進位制值串對映為固定長度的二進位制值串,這個對映的規則就是雜湊演算法。而通過原始資料對映後得到的二進位制值串就是雜湊值。

一個優秀的雜湊演算法應該滿足哪幾點?

  • 從原始資料計算得到的雜湊值,不能反向推匯出原始資料的值。
  • 對輸入的資料非常敏感,只要對原始資料做一點改變,小到一個Bit的改變,最後得到的雜湊值都大不相同。
  • 雜湊衝突的概率要很小,對於不同的資料,雜湊出來的雜湊值相同的概率要非常非常之小。
  • 雜湊演算法的執行效率要高,對於較長的文字,也能快速計算出雜湊值。

雜湊演算法的應用:

雜湊演算法的應用有很多,分別有安全加密、唯一標識、資料校驗、雜湊函式、負載均衡、資料分片、分散式儲存等。

應用一,安全加密:

最常用於加密的雜湊演算法是MD5(MD5 Message-Digest Algorithm,MD5 訊息摘要演算法)和SHA(Secure Hash Algorithm,安全雜湊演算法)。
對於應用於加密的雜湊演算法來說,上面提到的幾點要求中,有兩項是非常重要的,就是

  • 從原始資料計算得到的雜湊值,不能反向推匯出原始資料的值。
  • 雜湊衝突的概率要很小,對於不同的資料,雜湊出來的雜湊值相同的概率要非常非常之小。

對於雜湊衝突的問題,事情上我們是無法完全避免的,只能儘可能的讓這個概率變小。為什麼無法完全避免呢 ?
這裡是基於組合數學中一個非常基礎的的理論,叫做鴿巢原理(也叫抽屜原理)。這個原理是說,如果有10個鴿巢,有11只鴿子,那肯定有1個鴿巢中的鴿子數量多於1個,換句話說就是,肯定有2只鴿子在1個鴿巢內。應用到這裡是怎麼理位解呢?我們知道,雜湊值的長度是有限的,比如MD5,它的雜湊值是固定的128位,能表示的資料範圍有限,最大是2^128個數據,但我們知道現實世界中,可雜湊的資料是無窮的,所以基於前面我們講的鴿巣原理,如果我們對2 ^128+1個數據進行求雜湊值,那就必然會存在雜湊值相同的情況。說到這裡,你應該可以明白,如果我們選取的雜湊演算法雜湊出來的值越長(表示的資料越大),那它發生雜湊衝突的概率就會越低,比如SHA要比MD5雜湊出來的值的長度要大。
有人可能會說,那這樣還是有可能被破解的

,確實,理論上是可以通過窮舉的方法來找到雜湊值相同的情況,但事實是即便存在這樣的雜湊衝突,但是因為雜湊值的範圍非常大,衝突的概率極低,即便像MD5這樣的雜湊演算法,有2^128個數據,這個資料其實已經接近是一個天文資料了,雜湊衝突發生的概率要小於1/2 ^128,如果想通過窮舉的方法來找到跟這個MD5值相同的資料,那耗費的時間將是一個天文資料,所以在有限的時間和資源下,雜湊演算法還是很難被破解的。
那是不是選擇越複雜、越難破解的雜湊演算法就好呢?
並不是的,越複雜、越難破解的雜湊演算法,他們計算雜湊值的時間就會長,比如SHA-256、SHA-1。因此我們在實際的開發中,要權衡好計算時長和破解難度來選擇相應的加密演算法。
最後再補充一下,沒有絕對的安全加密,有的只是增加攻擊的成本而已。

應用二,唯一標識

如何在海量相簿中,搜尋一張圖片是否存在?
我們可以為每一張圖片取一個唯一標識,或者說訊息摘要。比如我們可以取一張圖片的二進位制碼串的前100位元組,中間100位元組,再取最後100位元組,然後將這300位元組按順序放到一塊,通過雜湊演算法(比如使用MD5)計算這300位元組的雜湊值,用這個這個雜湊值來做唯一標識,通過這個標識來判斷這個圖片是否存在相簿中,這樣可以減少很多工作量。

應用三,雜湊函式

前面我們講到,雜湊函式是設計一個散列表的關鍵,他直接關係到雜湊衝突的概率和雜湊的效能。不過,相對雜湊演算法的其他應用,雜湊函式對雜湊衝突的要求要低得多。即便個別出現雜湊衝突,我們也可以通過開發定址法或者連結串列法來解決。另外雜湊函式對於雜湊演算法計算得到的值是否可以逆向推匯出原始資料也沒有要求,它更加關注雜湊後的值是否會平均分佈。而且雜湊函式對於效能要求比較高,更加追求效率。

如何防止資料庫中的使用者資訊被“脫庫”?

我們可以應用上面講到的雜湊演算法,資料庫中對於使用者密碼這樣的敏感資訊,我們可以對使用者提交的明文的密碼進行雜湊運算後,再將密碼的雜湊值儲存到資料庫中,這樣即使被“脫庫”,黑客拿到的也只是一串雜湊值而已。
上面那樣還不夠安全?
事實上,很多使用者的密碼設定得比較簡單,比如123456,111111,000000等,黑客可以通過維護一個常用密碼的字典來進行猜測,這樣簡單的密碼其實很容易被猜出來。針對這樣的字典攻擊,作為普通的使用者群體,我們應該有意識的設定比較複雜的密碼;而作為開發人員,我們可以引入一個salt(鹽),跟使用者的密碼組合在一起,增加密碼的複雜度,然後將組合後的密碼通過雜湊演算法運算加密,再將他儲存到資料庫中,進一步增加破解的難度。

雜湊演算法在分散式系統的應用

應用四,負載均衡

負載均衡演算法(或者說是排程策略)有很多,比如隨機分配、輪詢、加權輪詢等。那如果我們想實現一個會話粘滯的負載均衡演算法呢?
比較直接的方法是,我們通過維護一張對映關係表,這張表的內容是客戶端IP或者會話id和伺服器編號對應的對映關係,每次客戶端發起請求,我們拿到客戶端的IP後,在對映表中找到此IP對應對映的伺服器編號,然後再請求對應編號的伺服器。這種方法雖然簡單,但是有幾個弊端:

  • 如果客戶端數量非常大,那對映關係表也會非常大,非常浪費記憶體空間。
  • 客戶端上線,下線,伺服器擴容,縮容都會導致對映關係失效,這樣維護一個對映關係表的成本就會非常大。

如果我們藉助雜湊演算法,就可以解決上面兩個弊端。具體怎麼做呢?
我們運用雜湊演算法對客戶端ip或者會話id進行求雜湊值,然後將這個雜湊值與伺服器列表長度進行取模運算,得到的結果就是應該被路由到的伺服器編號。這樣我們就可以把同一個ip過來的所有請求都路由到後端同一臺伺服器上。

應用五,資料分片

例子:怎樣統計使用者‘搜尋‘關鍵字’出現的次數?
假設現在我們有一個1T的檔案,裡面記錄著使用者的搜尋關鍵字的記錄,現在我們要統計出每個關鍵字出現的次數?要怎麼統計呢?
首先,1T的檔案這麼大,我們不可能放在一臺機器的記憶體中,而且用一臺機器來處理這麼大的資料肯定也不行,那樣耗費的時間會非常長。針對這兩個問題,我們可以將資料進行分片處理,放到多臺機器上分別處理,這樣效率就會大大提高。
具體做法:假如我們現在有4臺機器,我們先從搜尋記錄的檔案中依次讀取出每個搜尋關鍵字,然後通過雜湊演算法計算此關鍵字的雜湊值,將此雜湊值與機器個數(這裡是4)進行取模運算,得到的結果就是要被分配到的機器的編號。這樣相同的關鍵字就會被分配到同一臺機器上進行處理,每個機器分別統計關鍵詞出現的次數,最後將各個機器計算的結果合併到一起就是我們最終要得到的結果。其實這裡應用到的思想就是MapReduce的基本設計思想。
類似處理海量資料的例子還有很多,針對類問題,我們都可以採用多機分散式處理,藉助這種分片思路,可以突破單機記憶體,CPU等資源的限制。

應用六,分散式儲存

現如今的網際網路面對的是海量的資料,為了進一步提高資料讀取和寫入的速度,我們一般都採用分散式儲存的方式來儲存資料,比如分散式快取。我們快取的資料也是非常大的,所以我們也要將快取資料儲存到多個機器上。
那我們怎麼分配好資料? 我們可以藉助前面講到的資料分片的思想,通過雜湊演算法對資料求雜湊值,然後與機器的個數做取模運算,得到的值就是要分配給的機器編號。
但是這裡有個問題,如果我們想要增加機器的數量了,那這個時候就很麻煩了,我們所有的資料都要重新計算雜湊值,然後重新搬移到新的機器上,這樣之前快取的資料就一下子都失效了。關於這個問題,我們可以使用一致性雜湊演算法進行解決,具體一致性雜湊演算法是什麼,已經如何解決這個問題,大家可以看看這篇文章,個人覺得講得通俗易懂。