一、雜湊碰撞是什麼?
所謂雜湊(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,發生碰撞的機率還要低得多。
七、參考連結
- How Long Should I Make My API Key?, by Sam Corcos
- Birthday problem, by Wikipedia
- Birthday attack, by Wikipedia
(完)