哈希碰撞與生日攻擊


一、哈希碰撞是什么?

所謂哈希(hash),就是將不同的輸入映射成獨一無二的、固定長度的值(又稱"哈希值")。它是最常見的軟件運算之一。

如果不同的輸入得到了同一個哈希值,就發生了"哈希碰撞"(collision)。

舉例來說,很多網絡服務會使用哈希函數,產生一個 token,標識用戶的身份和權限。


AFGG2piXh0ht6dmXUxqv4nA1PU120r0yMAQhuc13i8

上面這個字符串就是一個哈希值。如果兩個不同的用戶,得到了同樣的 token,就發生了哈希碰撞。服務器將把這兩個用戶視為同一個人,這意味着,用戶 B 可以讀取和更改用戶 A 的信息,這無疑帶來了很大的安全隱患。

黑客攻擊的一種方法,就是設法制造"哈希碰撞",然后入侵系統,竊取信息。

二、如何防止哈希碰撞?

防止哈希碰撞的最有效方法,就是擴大哈希值的取值空間。

16個二進制位的哈希值,產生碰撞的可能性是 65536 分之一。也就是說,如果有65537個用戶,就一定會產生碰撞。哈希值的長度擴大到32個二進制位,碰撞的可能性就會下降到 4,294,967,296 分之一。

更長的哈希值意味着更大的存儲空間、更多的計算,將影響性能和成本。開發者必須做出抉擇,在安全與成本之間找到平衡。

下面就介紹,如何在滿足安全要求的前提下,找出哈希值的最短長度。

三、生日攻擊

哈希碰撞的概率取決於兩個因素(假設哈希函數是可靠的,每個值的生成概率都相同)。

  • 取值空間的大小(即哈希值的長度)
  • 整個生命周期中,哈希值的計算次數

這個問題在數學上早有原型,叫做"生日問題"(birthday problem):一個班級需要有多少人,才能保證每個同學的生日都不一樣?

答案很出人意料。如果至少兩個同學生日相同的概率不超過5%,那么這個班只能有7個人。事實上,一個23人的班級有50%的概率,至少兩個同學生日相同;50人班級有97%的概率,70人的班級則是99.9%的概率(計算方法見后文)。

這意味着,如果哈希值的取值空間是365,只要計算23個哈希值,就有50%的可能產生碰撞。也就是說,哈希碰撞的可能性,遠比想象的高。實際上,有一個近似的公式。

上面公式可以算出,50% 的哈希碰撞概率所需要的計算次數,N 表示哈希的取值空間。生日問題的 N 就是365,算出來是 23.9。這個公式告訴我們,哈希碰撞所需耗費的計算次數,跟取值空間的平方根是一個數量級。

這種利用哈希空間不足夠大,而制造碰撞的攻擊方法,就被稱為生日攻擊(birthday attack)。

四、數學推導

這一節給出生日攻擊的數學推導。

至少兩個人生日相同的概率,可以先算出所有人生日互不相同的概率,再用 1 減去這個概率。

我們把這個問題設想成,每個人排隊依次進入一個房間。第一個進入房間的人,與房間里已有的人(0人),生日都不相同的概率是365/365;第二個進入房間的人,生日獨一無二的概率是364/365;第三個人是363/365,以此類推。

因此,所有人的生日都不相同的概率,就是下面的公式。

上面公式的 n 表示進入房間的人數。可以看出,進入房間的人越多,生日互不相同的概率就越小。

這個公式可以推導成下面的形式。

那么,至少有兩個人生日相同的概率,就是 1 減去上面的公式。

五、哈希碰撞的公式

上面的公式,可以進一步推導成一般性的、便於計算的形式。

根據泰勒公式,指數函數 ex 可以用多項式展開。

如果 x 是一個極小的值,那么上面的公式近似等於下面的形式。

現在把生日問題的1/365代入。

因此,生日問題的概率公式,變成下面這樣。

假設 d 為取值空間(生日問題里是 365),就得到了一般化公式。

上面就是哈希碰撞概率的公式。

六、應用

上面的公式寫成函數。


const calculate = (d, n) => { const exponent = (-n * (n - 1)) / (2 * d) return 1 - Math.E ** exponent; } calculate(365, 23) // 0.5000017521827107 calculate(365, 50) // 0.9651312540863107 calculate(365, 70) // 0.9986618113807388 

一般來說,哈希值由大小寫字母和阿拉伯數字構成,一共62個字符(10 + 26 + 26)。如果哈希值只有三個字符的長度(比如abc),取值空間就是 62 ^ 3 = 238,328,那么10000次計算導致的哈希碰撞概率是100%。


calculate(62 ** 3, 10000) // 1 

哈希值的長度增加到5個字符(比如abcde),碰撞的概率就下降到5.3%。


calculate(62 ** 5, 10000) // 0.05310946204730993 

現在有一家公司,它的 API 每秒會收到100萬個請求,每個請求都會生成一個哈希值,假定這個 API 會使用10年。那么,大約一共會計算300萬億次哈希。能夠接受的哈希碰撞概率是1000億分之一(即每天發生一次哈希碰撞),請問哈希字符串最少需要多少個字符?

根據上面的公式倒推,就會知道哈希值的最短長度是22個字符(比如BwQ1W6soXkA1PU120r0yMA),計算過程略。

22個字符的哈希值,就能保證300萬億次計算里面,只有1000億分之一的概率發生碰撞。常用的 SHA256 哈希函數產生的是64個字符的哈希值,每個字符的取值范圍是0~9和a~f,發生碰撞的概率還要低得多。

七、參考鏈接

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM