密碼一般不以明文儲存——一個含所有明文密碼的檔案是太誘人的目標,所以改用單向雜湊函式。最有名的是基於 DES 的 crypt()。
單向雜湊與 salt#
char *crypt(const char *key, const char *salt);key是使用者輸入的明文密碼。salt是從[a-zA-Z0-9./]選的兩字元字串,用來以 4096 種方式之一擾動演算法。
這是數學上不可逆的單向雜湊:只憑雜湊無法還原原始密碼。輸出的雜湊會把 salt 前綴進去。同一密碼配不同 salt 會產生不同雜湊值:
reader@hacking:~/booksrc $ ./crypt_test test je
password "test" with salt "je" hashes to ==> jeHEAX1m66RV.
reader@hacking:~/booksrc $ ./crypt_test test xy
password "test" with salt "xy" hashes to ==> xyVSuHLjceD92
認證時,從密碼檔取出該使用者的 salt,把輸入密碼過同一個單向雜湊;若輸入正確,產出的雜湊會與檔中儲存的相符。這讓認證正常運作,卻從不需儲存明文密碼。
字典攻擊#
密碼檔中的加密密碼其實沒那麼沒用。雖然數學上無法反轉雜湊,卻可以用該雜湊的 salt 值,快速把字典裡每個字都雜湊一遍,再與目標雜湊比對——若相符,那個字就是明文密碼。
crypt_crack.c 用檔案串流函式(fopen、fgets)逐行讀字典、以正確 salt 雜湊、比對。用 /usr/share/dict/words 破解 jeHEAX1m66RV.:
trying word: test ==> jeHEAX1m66RV.
The hash "jeHEAX1m66RV." is from the plaintext password "test".
這正是「不該用字典詞或基於字典詞的密碼」的原因。缺點是:若原密碼不是字典詞(如
h4R%),字典攻擊就找不到。自訂字典常用不同語言、標準變形(字母轉數字)、或在字尾加數字——字典越大破得越多,但處理也越久。
窮舉暴力破解#
嘗試每種可能組合的字典攻擊就是窮舉暴力破解。技術上能破解任何可想像的密碼,但可能得等到你曾孫的曾孫都不願再等。
分散到多台機器只帶來線性加速:一千台各每秒 10,000 次仍需 22 年多——線性加速相較「多一個字元帶來的金鑰空間成長」微不足道。
反過來,移除字元則指數縮小:四字元密碼只有 95⁴(約 8,400 萬)種,假設每秒 10,000 次破解,兩小時多就能破完。所以像
h4R%這種非字典密碼也能在合理時間內破解。除了避開字典詞,密碼長度也很重要——複雜度指數上升,長度加倍到八字元就能把破解時間推進不合理的範圍。
Solar Designer 的 John the Ripper 先字典攻擊、後窮舉暴力,是同類中最流行的工具:
reader@hacking:~/booksrc $ sudo john /etc/shadow
Loaded 2 passwords with 2 different salts (FreeBSD MD5 [32/32])
testing7 (jose)
雜湊查表與時間/空間權衡#
為什麼純查表不可行,以及機率矩陣如何取巧
若把所有可能密碼的雜湊都預先算好存進可搜尋的資料結構,任何密碼都能在搜尋時間內破解(二分搜尋約 O(log₂ N))。
時間/空間權衡攻擊(time/space trade-off):運算力與儲存空間之間處處存在權衡(MP3 用壓縮省空間但增運算;計算機用查表省運算)。
這個方法用有損壓縮:不做精確查表,而是輸入一個密碼雜湊時回傳數千個「可能的明文」,再快速檢查以收斂到原密碼。以四字元密碼(固定 salt)為例,儲存空間比完整查表減少 88%、須暴力破解的金鑰空間縮小約 1,018 倍——假設每秒 10,000 次,八秒內就能破解任何四字元密碼(相較窮舉的兩小時)。
它建一個三維二元矩陣,把雜湊值的部分與明文值的部分關聯起來。x 軸把明文拆成兩對字元、列舉成長 9,025 位元的向量;y 軸把密文拆成四段三字元區塊。每種明文都被雜湊,用密文找到對應欄,把該列的明文列舉位元打開。當密文縮成較小區塊,碰撞不可避免——輸入一個雜湊時,會回傳數個候選明文對,再用四個向量做位元 AND 收斂。
矩陣大小由鴿籠原理決定:目標是讓每個向量略少於半滿 1(實務約 42% 飽和),使查詢後留下的候選數控制在可快速檢查的範圍。