為什麼使用 MD5 存儲密碼非常危險

2021-02-13 真沒什麼邏輯

為什麼這麼設計(Why's THE Design)是一系列關於計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。

很多軟體工程師都認為 MD5 是一種加密算法,然而這種觀點其實是大錯特錯並且十分危險的,作為一個 1992 年第一次被公開的算法,到今天為止已經被發現了一些致命的漏洞,我們在生產環境的任何場景都不應該繼續使用 MD5 算法,無論是對數據或者文件的內容進行校驗還是用於所謂的『加密』。

這篇文章的主要目的是幫助讀者理解 MD5 到底是什麼,為什麼我們不應該繼續使用它,尤其是不應該使用它在資料庫中存儲密碼,作者也希望使用過 MD5 或者明文存儲密碼的開發者們能夠找到更加合理和安全的方式對用戶的這些機密信息進行存儲(這樣也可以間接提高我在各類網站中存儲密碼的安全性)。

概述

與『為什麼我們不能使用 MD5 來存儲密碼?』這一問題相似的其實還有『為什麼我們不能使用明文來存儲密碼?』,使用明文來存儲密碼是一種看起來就不可行的方案,除非我們能夠 100% 保證資料庫中的密碼欄位不會被任何人訪問到,不僅包括潛在的攻擊者,還包括系統的開發者和管理員。

不過這是一個非常理想的情況,在實際的生產環境中,我們不能抵禦來自黑客的所有攻擊,甚至也不能完全阻擋開發者和管理員的訪問,因為我們總需要信任並授權一些人或者程序具有當前資料庫的所有訪問權限,這也就給攻擊者留下了可以利用的漏洞,在抵禦外部攻擊時我們沒有辦法做到全面,只能儘可能提高攻擊者的成本,這也就是使用 MD5 或者其他方式存儲密碼的原因了。

很多開發者對於 MD5 的作用和定義都有著非常大的誤解,MD5 並不是一種加密算法,而是一種摘要算法,我們也可以叫它哈希函數,哈希函數可以將無限鍵值空間中的所有鍵都均勻地映射到一個指定大小的鍵值空間中;一個好的摘要算法能夠幫助我們保證文件的完整性,避免攻擊者的惡意篡改,但是加密算法或者加密的功能是 —— 通過某種特定的方式來編碼消息或者信息,只有授權方可以訪問原始數據,而沒有被授權的人無法從密文中獲取原文。

由於加密需要同時保證消息的秘密性和完整性,所以加密的過程使用一系列的算法,MD5 確實可以在加密的過程中作為哈希函數使用來保證消息的完整性,但是我們還需要另一個算法來保證消息的秘密性,所以由於 MD5 哈希的信息無法被還原,只依靠 MD5 是無法完成加密的。

在任何場景下,我們都應該避免 MD5 的使用,可以選擇更好的摘要算法替代 MD5,例如 SHA256、SHA512。

聊了這麼多對於 MD5 的誤解,我們重新回到今天最開始的題目,『為什麼 MD5 不能用於存儲密碼』,對於這個問題有一個最簡單的答案,也就是 MD5 不夠安全。當整個系統中的資料庫被攻擊者入侵之後,存儲密碼的摘要而不是明文是我們能夠對所有用戶的最大保護。需要知道的是,不夠安全的不只是 MD5,任何摘要算法在存儲密碼這一場景下都不夠安全,我們在這篇文章中就會哈希函數『為什麼哈希函數不能用於存儲密碼』以及其他相關機制的安全性。

設計

既然我們已經對哈希函數和加密算法有了一些簡單的了解,接下來的這一節中分析使用以下幾種不同方式存儲密碼的安全性:

使用哈希存儲密碼;

使用哈希加鹽存儲密碼;

使用加密算法存儲密碼;

使用 bcrypt 存儲密碼;

在分析的過程中可能會涉及到一些簡單的密碼學知識,也會談到一些密碼學歷史上的一些事件,不過這對於理解不同方式的安全性不會造成太大的障礙。

哈希

在今天,如果我們直接使用哈希來存儲密碼,那其實跟存儲明文沒有太多的區別,所有的攻擊者在今天都已經掌握了彩虹表這個工具,我們可以將彩虹表理解成一張預計算的大表,其中存儲著一些常見密碼的哈希,當攻擊者通過入侵拿到某些網站的資料庫之後就可以通過預計算表中存儲的映射來查找原始密碼。

攻擊者只需要將一些常見密碼提前計算一些哈希就可以找到資料庫中很多用於存儲的密碼,Wikipedia 上有一份關於最常見密碼的 列表,在 2016 年的統計中發現使用情況最多的前 25 個密碼佔了調查總數的 10%,雖然這不能排除統計本身的不準確因素,但是也足以說明僅僅使用哈希的方式存儲密碼是不夠安全的。

哈希加鹽

僅僅使用哈希來存儲密碼無法抵禦來自彩虹表的攻擊,在上世紀 70 到 80 年代,早期版本的 Unix 系統就在 /etc/passwrd 中存儲加鹽的哈希密碼,密碼加鹽後的哈希與鹽會被一起存儲在 /etc/passwd 文件中,今天哈希加鹽的策略與幾十年前的也沒有太多的不同,差異可能在於鹽的生成和選擇:

md5(salt, password), salt

加鹽的方式主要還是為了增加攻擊者的計算成本,當攻擊者順利拿到資料庫中的數據時,由於每個密碼都使用了隨機的鹽進行哈希,所以預先計算的彩虹表就沒有辦法立刻破譯出哈希之前的原始數據,攻擊者對每一個哈希都需要單獨進行計算,這樣能夠增加了攻擊者的成本,減少原始密碼被大範圍破譯的可能性。

在這種情況下,攻擊者破解一個用戶密碼的成本其實就等於發現哈希碰撞的概率,因為攻擊者其實不需要知道用戶的密碼是什麼,他只需要找到一個值 value,這個值加鹽後的哈希與密碼加鹽後的哈希完全一致就能登錄用戶的帳號:

hash(salt, value) = hash(salt, password)

這種情況在密碼學中叫做哈希碰撞,也就是兩個不同值對應哈希相同,一個哈希函數或者摘要算法被找到哈希碰撞的概率決定了該算法的安全性,早在幾十年前,我們就在 MD5 的設計中發現了缺陷並且在隨後的發展中找到了低成本快速製造哈希碰撞的方法。

1996 年 The Status of MD5 After a Recent Attack —— 發現了 MD5 設計中的缺陷,但是並沒有被認為是致命的缺點,密碼學專家開始推薦使用其他的摘要算法;

2004 年 How to Break MD5 and Other Hash Functions ——  發現了 MD5 摘要算法不能抵抗哈希碰撞,我們不能在數字安全領域使用 MD5 算法;

2006 年 A Study of the MD5 Attacks: Insights and Improvements —— 創建一組具有相同 MD5 摘要的文件;

2008 年 MD5 considered harmful today —— 創建偽造的 SSL 證書;

2010 年 MD5 vulnerable to collision attacks —— CMU 軟體工程機構認為 MD5 摘要算法已經在密碼學上被破譯並且不適合使用;

2012 年 Flame ——  惡意軟體利用了 MD5 的漏洞並偽造了微軟的數字籤名;

從過往的歷史來看,為了保證用戶敏感信息的安全,我們不應該使用 MD5 加鹽的方式來存儲用戶的密碼,那麼我們是否可以使用更加安全的摘要算法呢?不可以,哈希函數並不是專門用來設計存儲用戶密碼的,所以它的計算可能相對來說還是比較快,攻擊者今天可以通過 GPU 每秒執行上億次的計算來破解用戶的密碼,所以不能使用這種方式存儲用戶的密碼,感興趣的讀者可以了解一下用於恢復密碼的工具 Hashcat。

加密

既然今天的硬體已經能夠很快地幫助攻擊者破解用戶的密碼,那麼我們能否通過其他的方式來取代哈希函數來存儲密碼呢?有些工程師想到使用加密算法來替代哈希函數,這樣能夠從源頭上避免哈希碰撞的的發生,這種方式看起來非常美好,但是有一個致命的缺點,就是我們如何存儲用於加密密碼的秘鑰

既然存儲密碼的倉庫能被洩露,那麼用於存儲秘鑰的服務也可能會被攻擊,我們永遠都沒有辦法保證我們的資料庫和伺服器是安全的,一旦秘鑰被攻擊者獲取,他們就可以輕而易舉地恢復用戶的密碼,因為核對用戶密碼的過程需要在內存對密碼進行解密,這時明文的密碼就可能暴露在內存中,依然有導致用戶密碼洩露的風險。

使用加密的方式存儲密碼相比於哈希加鹽的方式,在一些安全意識和能力較差的公司和網站反而更容易導緻密碼的洩露和安全事故。

bcrypt

哈希加鹽的方式確實能夠增加攻擊者的成本,但是今天來看還遠遠不夠,我們需要一種更加安全的方式來存儲用戶的密碼,這也就是今天被廣泛使用的 bcrypt,使用 bcrypt 相比於直接使用哈希加鹽是一種更加安全的方式,也是我們目前推薦使用的方法,為了增加攻擊者的成本,bcrypt 引入了計算成本這一可以調節的參數,能夠調節執行 bcrypt 函數的成本。

當我們將驗證用戶密碼的成本提高几個數量級時,攻擊者的成本其實也相應的提升了幾個數量級,只要我們讓攻擊者的攻擊成本大於硬體的限制,同時保證正常請求的耗時在合理範圍內,我們就能夠保證用戶密碼的相對安全。

"bcrypt was designed for password hashing hence it is a slow algorithm. This is good for password hashing as it reduces the number of passwords by second an attacker could hash when crafting a dictionary attack. "

bcrypt 這一算法就是為哈希密碼而專門設計的,所以它是一個執行相對較慢的算法,這也就能夠減少攻擊者每秒能夠處理的密碼數量,從而避免攻擊者的字典攻擊。

func main() {
for cost := 10; cost <= 15; cost++ {
startedAt := time.Now()
bcrypt.GenerateFromPassword([]byte("password"), cost)
duration := time.Since(startedAt)
fmt.Printf("cost: %d, duration: %v\n", cost, duration)
}
}

$ go run bcrypt.go
cost: 10, duration: 51.483401ms
cost: 11, duration: 100.639251ms
cost: 12, duration: 202.788492ms
cost: 13, duration: 399.552731ms
cost: 14, duration: 801.041128ms
cost: 15, duration: 1.579692689s

運行上述 代碼片段 時就能發現 cost 和運行時間的關係,算法運行的成本每 +1,當前算法最終的耗時就會翻一倍,這與 bcrypt 算法的實現原理有關,你可以在 Wikipedia 上找到算法執行過程的偽代碼,這可以幫助我們快速理解算法背後的設計。

如果硬體的發展使攻擊者能夠對使用 bcrypt 存儲的密碼進行攻擊時,我們就可以直接提升 bcrypt 算法的 cost 參數以增加攻擊者的成本,這也是 bcrypt 設計上的精妙之處,所以使用 bcrypt 是一種在存儲用戶密碼時比較安全的方式。

總結

這篇文章分析的問題其實是 —— 當資料庫被攻擊者獲取時,我們怎麼能夠保證用戶的密碼很難被攻擊者『破譯』,作為保護用戶機密信息的最後手段,選擇安全並且合適的方法至關重要。攻擊者能否破解用戶的密碼一般取決於兩個條件:

抵禦攻擊者的攻擊的方式其實就是提高單次算法運行的成本,當我們將用戶的驗證耗時從 0.1ms 提升到了 500ms,攻擊者的計算成本也就提升了 5000 倍,這種結果就是之前需要幾小時破解的密碼現在需要幾年的時間。

不論如何,使用 MD5、MD5 加鹽或者其他哈希的方式來存儲密碼都是不安全的,希望各位工程師能夠避免在這樣的場景下使用 MD5,在其他必須使用哈希函數的場景下也建議使用其他算法代替,例如 SHA-512 等。

當然,如何保證用戶機密信息的安全不只是一個密碼學問題,它還是一個工程問題,任何工程開發商的疏漏都可能導致安全事故,所以我們作為開發者在與用於敏感信息打交道時也應該小心謹慎、懷有敬畏之心。到最後,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細思考一下下面的問題:

使用 GPU 每秒可以計算多少 MD5 哈希(數量級)?能夠在多長時間破解使用 MD5 加鹽存儲的密碼?

假設計算一次哈希耗時 500ms,破解 bcrypt 算法生成的哈希需要多長時間?

MD5 哈希 23cdc18507b52418db7740cbb5543e54 對應的原文可能是?談談你使用的工具和破譯的過程。

如果對文章中的內容有疑問或者想要了解更多軟體工程上一些設計決策背後的原因,可以在博客下面留言,作者會及時回複本文相關的疑問並選擇其中合適的主題作為後續的內容。

Reference

Is salted MD5 or salted SHA considered secure?

How to securely hash passwords?

Rainbow table

The MD5 Message-Digest Algorithm · RFC1321

Collision (computer science)

Why You Should Use Bcrypt to Hash Stored Passwords

How can bcrypt have built-in salts?

bcrypt

系列文章


長按二維碼↑

關注『真沒什麼邏輯』公眾號

相關焦點

  • Python 中 MD5 加密
    是計算機廣泛使用的雜湊算法之一(又譯摘要算法、哈希算法),主流程式語言普遍已有 MD5 實現。將數據(如漢字)運算為另一固定長度值,是雜湊算法的基礎原理,MD5 的前身有 MD2、MD3 和 MD4。MD5 的作用是讓大容量信息在用數字籤名軟體籤署私人密鑰前被"壓縮"成一種保密的格式(就是把一個任意長度的字節串變換成一定長的十六進位數字串)。
  • CSharp如何運用MD5算法加密密碼?
    MD5(Message-Digest Algorithm 5)是一種廣泛使用的「消息-摘要算法」。這是一個單項散列函數,數據經過單向散列函數獲取一個固定長度的散列值,資料庫的籤名就是計算資料庫的散列值,MD5算法的散列值為128位。
  • PHP程式設計師:6年前都告訴過你md5密碼不安全,直到今天你還在犯錯
    引言作為php開發人員,很長一段時間以來,很多人一直在使用md5哈希算法來保護密碼數據並生成唯一的哈希算法。但是你應該或多或少聽到過,md5不再安全了!PHP 5.5中有一些密碼身份驗證替代方案,即 sha1,password_hash?為什麼被認為更安全?應該怎麼選擇?學習時間很多研究論文已經證明過了,md5 計算出的哈希值可以被逆向。我們也應該完全停止使用。
  • 一種基於Md5算法的改進加密方法
    由於md5算法的使用不需要支付任何版權費用的,所以在一般的情況下,md5也不失為一種非常優秀的加密算法,被大量公司和個人廣泛使用。雖然王小雲教授公布了破解MD5算法的報告,宣告該算法不再安全,但是對於公司以及普通用戶來說,從算法上來破解MD5非常困難,因此MD5仍然算是一種安全的算法。
  • md5到md5破解的一些科普
    看到網上一些對於md5的介紹還有對於當初王小雲所做的破解有很多的誤解,或者說不理解,然後覺得對於這些事情只要說明白還是比較好理解的說。首先md5其實就是一種hash,或者叫散列函數,有的地方叫雜湊函數,都是一個東西啦,其實他就是一種映射,而平常最常見的就是說md5是不可逆的,為什麼不可逆呢,有人就說就是像有些函數沒有反函數那樣了,其實還是有點抽象,考慮md5是多對一的映射,也就是說很多不同的經過md5變換之後可能會是相同的,那麼既然多對一,自然是不可逆啦╮(╯▽╰)╭,你怎麼會知道他到底是由哪個變換過來的呢。
  • Hash(哈希)密碼破解原理
    3、這就導致破解LM密碼只需7位一分割,然後再逐塊破解,這大大減低了破解的難度。因為最後一塊往往不夠7位,一般瞬間即可得出結果。也就是7位和13位的密碼,在破解者眼裡幾乎是一樣的,因為13位的後6位很快就能破解出來,而且可以根據後6位猜測出前7位的密碼,這就是為什麼我們破解XP和2003密碼很快的原因,因為他們都使用了LM加密方式。
  • MD5 簡介,及其在 Java 中的實現方式
    MD5,Message Digest Algorithm 5,是一種被廣泛使用的信息摘要算法,可以將給定的任意長度數據通過一定的算法計算得出一個 128 位固定長度的散列值。MD5 使用的是散列函數(也稱哈希函數),一定概率上也存在哈希衝突(也稱哈希碰撞),即多個不同的原數據對應一個相同的 MD5 值。不過,經過 MD4、MD3 等幾代算法的優化,MD5 已經充分利用散列的分散性高度避免碰撞的發生。可以看出,MD5 是一種不可逆的算法,也就說,你無法通過得到的 MD5 值逆向算出原數據內容。正是憑藉這些特點,MD5 被廣泛使用。
  • Python 中 MD5 哈希函數的實現
    然後,我們使用md5哈希函數對該值進行編碼。最後,我們使用digest()函數生成了編碼字符串的等效字節。在這裡,我們使用encode()函數將字符串轉換為等效的字節,從而使其被哈希函數接受。然後,我們使用md5函數對其進行編碼,最後,使用hexdigest()函數顯示其十六進位等效項。m
  • 明文存儲密碼,為何連谷歌也無法杜絕這種「蠢事」?
    一些小網站或許資料庫會明文存儲密碼,而知名網際網路公司絕無可能,但後者的業務系統頗為複雜,關聯功能可能意外隱藏著明文存儲漏洞,因此例行安全檢測都額外注意這點。2. 儘管大公司多數明文存儲漏洞沒有造成影響,但也有漏網之魚,被外界捕捉。3. 明文存儲密碼漏洞的主要由燈下黑、設計考慮不周、歷史遺留、過於自信這四個因素造成的。
  • PHP如何使用郵箱找回密碼?
    2、當用戶忘記密碼或用戶名時,點擊登錄頁面的「找回密碼」超連結,打開表單,並輸入註冊用的 email 郵箱,提交。3、系統通過該郵箱,從資料庫中查找到該用戶信息,並更新該用戶的密碼為一個臨時密碼(比如:123456)。
  • CTF中關於md5的一些總結
    _1 = md5($string_1);    $md5_2 = md5($string_2);    if($md5_1 !>import hashlibpayload = string.ascii_letters+string.digitsdef calc_md5(s):    md5 = hashlib.md5(s.encode("utf-8")).hexdigest()    if (md5[0:2] == "ce" and md5[2:].isdigit()) :
  • 61【Linux】一步一步學Linux——md5sum命令(61)
    MD5 全稱是報文摘要算法(Message-Digest Algorithm 5),此算法對任意長度的信息逐位進行計算,產生一個二進位長度為128位(十六進位長度就是32位)的「指紋」(或稱「報文摘要」),不同的文件產生相同的報文摘要的可能性是非常非常之小的。02.
  • 淺談md5弱類型比較和強碰撞
    解法2:使用md5加密後兩個完全相等的兩個字符串來繞過過濾。如何生成兩個不一樣的字符串,但是MD5是一樣的呢。參考如何用不同的數值構建一樣的MD5後,我們可以使用快速MD5碰撞生成器來構建兩個MD5一樣,但內容完全不一樣的字符串。
  • 為什麼有數字密碼,卻沒有漢字密碼?中國電信這樣解釋
    集微網12月23日消息,在日常生活中,密碼真是太常見了,數字密碼、指紋密碼、字母密碼、字符密碼等。但是你有沒有發現密碼類型千千萬,卻偏偏沒有漢字密碼,這是為什麼呢?除此之外,還需要考慮字符編碼,不同的字符編碼對字符的存儲方式可能會不同,因此,若使用漢字作為密碼,其加密存儲過程將十分複雜。
  • 科技:使用密碼管理器來幫助保護自己在線
    LastPass還將加密信息存儲在其雲伺服器上,這意味著您可以在個人電腦以外的計算機上使用LastPass,並輕鬆與家庭成員共享密碼。它甚至配備了密碼生成器,用於創建唯一的密碼。選擇高級套件可以提供一系列額外的身份驗證選項,卓越的技術支持以及在桌面和行動裝置之間同步信息的功能。
  • MD5算法原理及其實現
    什麼是MD5算法MD5訊息摘要演算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函數
  • 為什麼在設置密碼的時候不能輸入中文?你知道嗎?
    日常生活中,密碼使用很常見了。基本上,登錄APP、手機支付、開機解鎖,都需要使用密碼,數字密碼,指紋密碼,字母密碼,各種的密碼唯獨沒有漢字。為什麼密碼不用漢字呈現?是中華文化太博大嗎?「為什麼密碼不能設置漢字?1、需要更高的技術對於使用漢字作為密碼,很顯然是合乎理論的,只不過大多數事物都有等級之分,據悉使用漢字作為輸入密碼的話,需要做到更高級別的技術才可順利進行。
  • md5校驗工具怎麼用
    如何使用md5校驗工具?1.下載Hash軟體。2.打開驗證工具進行瀏覽。3.有多個選項可供選擇。默認為全選。綜上所述,就是如何使用MD5檢驗工具的圖文教程了。
  • 為什麼漢字不能設置密碼?這個問題你想過嗎?
    日常生活中,密碼的使用十分常見。基本上,登錄APP、手機支付、開機解鎖,都需要使用密碼。密碼的形式也多種多樣:數字密碼,指紋密碼,字母密碼等,卻唯獨沒有漢字,這是為什麼呢?如何提高密碼的安全性呢?讓小翼為您解答吧~為什麼不使用漢字作為密碼?原因一:使用傳統有關密碼組成中沒有漢字這一問題,首先要追溯到計算機的發明。