一次忘記密碼引發(fā)的算法思考
前兩天準備登陸某網(wǎng)站的時候,在嘗試了幾次常用密碼失敗后,我點擊了“忘記密碼”,嫻熟地填入手機號碼,隨即就收到了一條來自陌生號碼的短信,里面包含著一個六個數(shù)字串,我將這個數(shù)字串填入網(wǎng)站提供的輸入框,就進入了密碼重置流程。
這里有一點細節(jié),值得我們注意,為什么我忘記了密碼,你不直接把原密碼返回給我?而是給我一個不相關(guān)的口令來重置密碼?
前段時間,華住(某大型連鎖酒店)再次發(fā)生脫庫事件。由于內(nèi)部程序員失誤,將數(shù)據(jù)庫密碼公開于Github上,讓人拿走了數(shù)億用戶的開戶記錄和他們的登陸信息(包括密碼)。假如這些密碼用明文存儲,那不法分子盜取數(shù)據(jù)庫后,拿到明文密碼,就可以輕而易舉的拿郵箱和密碼去嘗試登陸你的各種社交網(wǎng)站,甚至各種金融帳戶。要知道有很多人其實對賬戶安全不夠重視,金融賬戶和社交賬號都使用同一個密碼。在如此數(shù)量級下(上億)的帳戶密碼泄露,對于互聯(lián)網(wǎng)安全將是一場巨大的浩劫。
如此看來,服務器是不會明文存儲你的用戶密碼的。這也就能解釋,為什么不干脆直接將原密碼返回給你了。因為就連他們也不知道。
那么問題來了,在數(shù)據(jù)庫存儲的到底是什么?它應該是被某種算法加密過的密文,并且無法進行反向破解,保證了被黑客拿到了也能保證數(shù)據(jù)的相對安全。而這個算法,就是我們接下來要介紹對哈希算法。
哈希(Hash)算法,由于其不可反向破解的特性被廣泛用于私密信息的保護和校驗。
哈希算法是一個比較泛的概念,他的具體實現(xiàn)有許多種,大家所熟知的有 MD5,SHA256等。
現(xiàn)在拿 MD5來舉例,MD5消息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數(shù),可以產(chǎn)生出一個128位(16字節(jié))的散列值(hash value)。
但是人類實在看不慣二進制,所以128位的二進制通常會表示成32位的十六進制(由0-9,a-f組成),他們是等價的。
說了那么多,這個散列值到底長啥樣呢。
在 Python 中
- import hashlib
- m1 = hashlib.md5()
- m1.update("hello")
- print(m1.hexdigest())
- # 5d41402abc4b2a76b9719d911017c592
用 shell 就更簡單了
- echo -n hello | md5sum
- # 5d41402abc4b2a76b9719d911017c592 -
有不少人會將 哈希(Hash) 和 加密(Encrypt)混淆起來,其實它們是不一樣的:
- 哈希:將目標文本轉(zhuǎn)換成具有相同長度的、不可逆的字符串,也叫消息摘要,是一對多的映射關(guān)系(即多個明文可能對應同一個哈希值)。
- 加密:將目標文本轉(zhuǎn)換成具有不同長度的、可逆的密文,是一對一的映射關(guān)系(即一個密文只能對應一個明文)
由于哈希算法是一對多的映射,所以不同的輸入是有可能得到了同一個哈希值,這時候就發(fā)生了"哈希碰撞"(collision)。
哈希碰撞雖然發(fā)生概率小,但是一旦發(fā)生,就會產(chǎn)生嚴重的安全問題:
案例一
很多網(wǎng)絡(luò)服務會使用哈希函數(shù),產(chǎn)生一個 token 用于標識用戶的身份和權(quán)限。
如果兩個不同的用戶,得到了同樣的 token,就發(fā)生了哈希碰撞。服務器將把這兩個用戶視為同一個人,這意味著,用戶 B 可以讀取和更改用戶 A 的信息,這無疑帶來了很大的安全隱患。
案例二
我們都用過網(wǎng)絡(luò)支付工具,假如我現(xiàn)在從 A 帳戶轉(zhuǎn)給帳戶 B 1000塊錢,交易信息在網(wǎng)絡(luò)中進行傳輸,有可能被黑客給截持并篡改我們的數(shù)據(jù),將目標帳戶改成黑客自己的帳戶。如此一來,我們的金錢就被竊取了。
如果使用哈希算法,就可以在客戶端處,將轉(zhuǎn)賬的信息進行處理,處理方法是,將要加密的數(shù)據(jù)加上一個約定好的字符串一起進行哈希,生成一個信息摘要。
假如在網(wǎng)絡(luò)傳輸過程中不幸被黑客修改了目標帳戶和轉(zhuǎn)帳金額,等到了支付平臺的服務器端,會將傳輸過來的信息和之前約定好的字符串再次進行哈希。然后和之前那個哈希進行比對,由于之前的數(shù)據(jù)已經(jīng)被篡改了,所以驗證不通過,轉(zhuǎn)帳失敗。從而保證了我們的資金安全。
前面講到了許多哈希在實際生活中的應用,可以發(fā)現(xiàn),哈希被廣泛的應用在安全領(lǐng)域。那哈希真的沒有辦法破解嗎?
當然有,這里舉三個常見的例子。
1. 暴力窮舉法
暴力窮舉法,就是簡單粗暴的枚舉出所有的原文,并計算其哈希值,然后將計算結(jié)果與目標哈希值一一比對。由于原文的可能性有無數(shù)多種,所以這種方法時間復雜度高得離奇,極不可取。需要大量的計算,因此破解速度非常慢,以14位字母和數(shù)字的組合密碼為例,共有1.24×10^25種可能,即使電腦每秒鐘能進行10億次運算,也需要4億年才能破解。
就算有一天,真找到一個和目標哈希值相等的原文,這個原文也不一定是答案,因為哈希沖突的存在,多個原文是有可能有著同一個哈希值。
2. 字典法
反思暴力枚舉法,它其實做了太多無用的計算。一般人的密碼都會取一些有特殊意義的字符,比如生日,名字縮寫等。有人就會把這些常用的高頻率的密碼組合,試先計算并存儲起來。等到要用的時候,直接到數(shù)據(jù)庫里查詢對應的哈希值就行了。
如果說暴力枚舉法,是時間換空間,那字典法就是空間換時間。
需要海量的磁盤空間來儲存數(shù)據(jù),仍以14位字母和數(shù)字的組合密碼為例,生成的密碼32位哈希串的對照表將占用2.64 * 10^14 TB 的存儲空間。如何增加密碼長度或添加符號,需要的時間或磁盤空間將更加難以想象,顯然這兩種方法是難以讓人滿意的。
- (62^14*192)/8/1024/1024/1024/1024=2.64 * 10^14 (GB)
3. 彩虹表法
暴力枚舉和字典法,都只適用于長度較短,組合簡單的密碼。
接下來為大家介紹一種高效的密碼攻擊方法:彩虹表。它可以用于復雜一點的密碼。
彩虹表實質(zhì)上還是屬于字典破解的一種,不過不再是簡單的明文—密碼的對應,為了節(jié)省字典存儲空間,彩虹表省去了能通過計算得出的數(shù)據(jù),達到這點的關(guān)鍵在于設(shè)計出一個函數(shù)族Rk(k=1、2、3、4……)將hash密文空間映射回明文的字符空間。
彩虹表的存儲空間是字典法的 k 分之一,代價是運算次數(shù)至少是原來的 k 倍。
彩虹表確實像它的名字一樣美好,至少黑客眼里是這樣。下表是7位以內(nèi)密碼在不同字符集下構(gòu)造出的彩虹表的情況,彩虹表中哈希鏈的長度和個數(shù)隨著字符集的增長而增長,彩虹表的大小和生成時間也隨之成倍增加。7位數(shù)字組合在彩虹表面前簡直就是秒破,即使最復雜的7位密碼不到一個小時就能破解,如果采用普通的暴力攻擊,破解時間可能需要三周。
這篇文章,寫得比較通俗易懂,其中借鑒了網(wǎng)上一些不錯的文章,算是一篇科普文,想要深入了解,你還需要自己做更多的研究與思考。