本文介紹了對密碼哈希加密的基礎知識,以及什麼是正確的加密方式。還介紹了常見的密碼破解方法,給出瞭如何避免密碼被破解的思路。相信讀者閱讀本文後,就會對密碼的加密有一個正確的認識,並對密碼正確進行加密措施。php
做爲一名Web開發人員,咱們常常須要與用戶的賬號系統打交道,而這其中最大的挑戰就是如何保護用戶的密碼。常常會看到用戶帳戶數據庫頻繁被黑,因此咱們必須採起一些措施來保護用戶密碼,以避免致使沒必要要的數據泄露。保護密碼的最好辦法是使用加鹽密碼哈希( salted password hashing)。html
重要警告:請放棄編寫本身的密碼哈希加密代碼的念頭!由於這件事太容易搞砸了。就算你在大學學過密碼學的知識,也應該遵循這個警告。全部人都要謹記這點:不要本身寫哈希加密算法! 存儲密碼的相關問題已經有了成熟的解決方案,就是使用 phpass,或者在 defuse/password-hashing 或 libsodium 上的 PHP 、 C# 、 Java 和 Ruby 的實現。在對密碼進行哈希加密的問題上,人們有不少爭論和誤解,多是因爲網絡上有大量錯誤信息的緣由吧。對密碼哈希加密是一件很簡單的事,但不少人都犯了錯。本文將會重點分享如何進行正確加密用戶密碼。java
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366 hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542
哈希算法是一種單向函數。它把任意數量的數據轉換爲固定長度的「指紋」,並且這個過程沒法逆轉。它們有這樣的特性:若是輸入發生了一點改變,由此產生的哈希值會徹底不一樣(參見上面的例子)。這個特性很適合用來存儲密碼。由於咱們須要一種不可逆的算法來加密存儲的密碼,同時保證咱們也可以驗證用戶登錄的密碼是否正確。git
在基於哈希加密的賬號系統中,用戶註冊和認證的大體流程以下。程序員
在步驟4中,永遠不要告訴用戶輸錯的到底是用戶名仍是密碼。就像通用的提示那樣,始終顯示:「無效的用戶名或密碼。」就好了。這樣能夠防止攻擊者在不知道密碼的狀況下枚舉出有效的用戶名。github
應當注意的是,用來保護密碼的哈希函數,和數據結構課學到的哈希函數是不一樣的。例如,實現哈希表的哈希函數設計目的是快速查找,而非安全性。只有加密哈希函數( cryptographic hash function)才能夠用來進行密碼哈希加密。像 SHA256 、 SHA512 、 RIPEMD 和 WHIRLPOOL 都是加密哈希函數。算法
人們很容易認爲,Web開發人員所作的就是:只需經過執行加密哈希函數就可讓用戶密碼得以安全。然而並非這樣。有不少方法能夠從簡單的哈希值中快速恢復出明文的密碼。有幾種易於實施的技術,使這些「破解」的效率大爲下降。網上有這種專門破解MD5的網站,只需提交一個哈希值,不到一秒鐘就能獲得破解的結果。顯然,單純的對密碼進行哈希加密遠遠達不到咱們的安全要求。下一節將討論一些用來破解簡單密碼哈希經常使用的手段。數據庫
字典攻擊編程 |
暴力攻擊數組 |
Trying apple : failed |
Trying aaaa : failed |
Trying blueberry : failed |
Trying aaab : failed |
Trying justinbeiber : failed |
Trying aaac : failed |
… |
… |
Trying letmein : failed |
Trying acdb : failed |
Trying s3cr3t : success! |
Trying acdc : success! |
破解哈希加密最簡單的方法是嘗試猜想密碼,哈希每一個猜想的密碼,並對比猜想密碼的哈希值是否等於被破解的哈希值。若是相等,則猜中。猜想密碼攻擊的兩種最多見的方法是字典攻擊和暴力攻擊 。
字典攻擊使用包含單詞、短語、經常使用密碼和其餘可能用作密碼的字符串的字典文件。對文件中的每一個詞都進行哈希加密,將這些哈希值和要破解的密碼哈希值比較。若是它們相同,這個詞就是密碼。字典文件是經過大段文本中提取的單詞構成,甚至還包括一些數據庫中真實的密碼。還能夠對字典文件進一步處理以使其更爲有效:如單詞 「hello」 按網絡用語寫法轉成 「h3110」 。
暴力攻擊是對於給定的密碼長度,嘗試每一種可能的字符組合。這種方式會消耗大量的計算,也是破解哈希加密效率最低的辦法,但最終會找出正確的密碼。所以密碼應該足夠長,以致於遍歷全部可能的字符組合,耗費的時間太長使人沒法承受,從而放棄破解。
目前沒有辦法來組織字典攻擊或暴力攻擊。只能想辦法讓它們變得低效。若是密碼哈希系統設計是安全的,破解哈希的惟一方法就是進行字典攻擊或暴力攻擊遍歷每個哈希值了。
Searching: 5f4dcc3b5aa765d61d8327deb882cf99: FOUND: password5 Searching: 6cbe615c106f422d23669b610b564800: not in database Searching: 630bf032efe4507f2c57b280995925a9: FOUND: letMEin12 Searching: 386f43fab5d096a7a66d67c8f213e5ec: FOUND: mcd0nalds Searching: d5ec75d5fe70d428685510fae36492d9: FOUND: p@ssw0rd!
對於破解相同類型的哈希值,查表法是一種很是高效的方式。主要理念是預先計算( pre-compute)出密碼字典中的每一個密碼的哈希值,而後把他們相應的密碼存儲到一個表裏。一個設計良好的查詢表結構,即便包含了數十億個哈希值,仍然能夠實現每秒鐘查詢數百次哈希。
若是你想感覺查表法的速度有多快,嘗試一下用 CrackStation 的 free hash cracker 來破解下面的 SHA256。
c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc 08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7 e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904 5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd
Searching for hash(apple) in users' hash list... : Matches [alice3, 0bob0, charles8] Searching for hash(blueberry) in users' hash list... : Matches [usr10101, timmy, john91] Searching for hash(letmein) in users' hash list... : Matches [wilson10, dragonslayerX, joe1984] Searching for hash(s3cr3t) in users' hash list... : Matches [bruce19, knuth1337, john87] Searching for hash(z@29hjja) in users' hash list... : No users used this password
這種攻擊容許攻擊者無需預先計算好查詢表的狀況下同時對多個哈希值發起字典攻擊或暴力攻擊。
首先,攻擊者從被黑的用戶賬號數據庫建立一個用戶名和對應的密碼哈希表,而後,攻擊者猜想一系列哈希值並使用該查詢表來查找使用此密碼的用戶。一般許多用戶都會使用相同的密碼,所以這種攻擊方式特別有效。
彩虹表是一種以空間換時間的技術。與查表法類似,只是它爲了使查詢表更小,犧牲了破解速度。由於彩虹表更小,因此在單位空間能夠存儲更多的哈希值,從而使攻擊更有效。可以破解任何最多8位長度的 MD5 值的彩虹表已經出現。
接下來,咱們來看一種謂之「加鹽( salting)」的技術,可以讓查表法和彩虹表都失效。
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1 hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007
查表法和彩虹表只有在全部密碼都以徹底相同的方式進行哈希加密纔有效。若是兩個用戶有相同的密碼,他們將有相同的密碼哈希值。咱們能夠經過「隨機化」哈希,當同一個密碼哈希兩次後,獲得的哈希值是不同的,從而避免了這種攻擊。
咱們能夠經過在密碼中加入一段隨機字符串再進行哈希加密,這個被加的字符串稱之爲鹽值。如上例所示,這使得相同的密碼每次都被加密爲徹底不一樣的字符串。咱們須要鹽值來校驗密碼是否正確。一般和密碼哈希值一同存儲在賬號數據庫中,或者做爲哈希字符串的一部分。
鹽值無需加密。因爲隨機化了哈希值,查表法、反向查表法和彩虹表都會失效。由於攻擊者沒法事先知道鹽值,因此他們就沒有辦法預先計算查詢表或彩虹表。若是每一個用戶的密碼用不一樣的鹽再進行哈希加密,那麼反向查表法攻擊也將不能奏效。
接下來,咱們看看加鹽哈希一般會有哪些不正確的措施。
最多見的錯誤,是屢次哈希加密使用相同的鹽值,或者鹽值過短。
一個常見的錯誤是每次都使用相同的鹽值進行哈希加密,這個鹽值要麼被硬編碼到程序裏,要麼只在第一次使用時隨機得到。這樣的作法是無效的,由於若是兩個用戶有相同的密碼,他們仍然會有相同的哈希值。攻擊者仍然可使用反向查表法對每一個哈希值進行字典攻擊。他們只是在哈希密碼以前,將固定的鹽值應用到每一個猜想的密碼就能夠了。若是鹽值被硬編碼到一個流行的軟件裏,那麼查詢表和彩虹表能夠內置該鹽值,以使其更容易破解它產生的哈希值。
用戶建立賬號或者更改密碼時,都應該用新的隨機鹽值進行加密。
若是鹽值過短,攻擊者能夠預先製做針對全部可能的鹽值的查詢表。例如,若是鹽值只有三個 ASCII 字符,那麼只有 95x95x95=857,375種可能性。這看起來不少,但若是每一個查詢表包含常見的密碼只有 1MB,857,375個鹽值總共只需 837GB,一塊時下不到100美圓的 1TB硬盤就能解決問題了。
出於一樣的緣由,不該該將用戶名用做鹽值。對每個服務來講,用戶名是惟一的,但它們是可預測的,而且常常重複應用於其餘服務。攻擊者能夠用常見用戶名做爲鹽值來創建查詢表和彩虹表來破解密碼哈希。
爲使攻擊者沒法構造包含全部可能鹽值的查詢表,鹽值必須足夠長。一個好的經驗是使用和哈希函數輸出的字符串等長的鹽值。例如, SHA256 的輸出爲256位(32字節),因此該鹽也應該是32個隨機字節。
本節將介紹另外一種常見的密碼哈希的誤解:古怪哈希的算法組合。人們很容易衝昏頭腦,嘗試不一樣的哈希函數相結合一塊兒使用,但願讓數據會更安全。但在實踐中,這樣作並無什麼好處。它帶來了函數之間互通性的問題,並且甚至可能會使哈希變得更不安全。永遠不要試圖去創造你本身的哈希加密算法,要使用專家設計好的標準算法。有人會說,使用多個哈希函數會下降計算速度,從而增長破解的難度。可是使破解過程變慢還有更好的辦法,咱們將在後面講到。
下面是在網上見過的古怪的哈希函數組合的一些例子。
md5(sha1(password))
md5(md5(salt) + md5(password))
sha1(sha1(password))
sha1(str_rot13(password + salt))
md5(sha1(md5(md5(password) + sha1(password)) + md5(password))))
不要使用其中任何一種。
注意:此部分是有爭議的。我收到了一些電子郵件,他們認爲古怪的哈希函數是有意義的,理由是,若是攻擊者不知道系統使用哪一個哈希函數,那麼攻擊者就不太可能預先計算出這種古怪的哈希函數彩虹表,因而破解起來要花更多的時間。
當攻擊者不知道哈希加密算法的時候,是沒法發起攻擊的。可是要考慮到柯克霍夫原則,攻擊者一般會得到源代碼(尤爲是免費或者開源軟件)。經過系統中找出密碼-哈希值對應關係,很容易反向推導出加密算法。使用一個很難被並行計算結果的迭代算法(下面將予以討論),而後增長適當的鹽值防止彩虹表攻擊。
若是你真的想用一個標準的「古怪」的哈希函數,如 HMAC ,亦無不可。可是,若是你目的是想下降哈希計算速度,那麼能夠閱讀下面有關密鑰擴展的部分。
若是創造新的哈希函數,可能會帶來風險,構造希函數的組合又會致使函數互通性的問題。它們帶來一點的好處和這些比起來微不足道。很顯然,最好的辦法是,使用標準、通過完整測試的算法。
因爲哈希函數將任意大小的數據轉化爲定長的字符串,所以,一定有一些不一樣的輸入通過哈希計算後獲得了相同的字符串的狀況。加密哈希函數( Cryptographic hash function)的設計初衷就是使這些碰撞儘可能難以被找到。如今,密碼學家發現攻擊哈希函數愈來愈容易找到碰撞了。最近的例子是MD5算法,它的碰撞已經實現了。
碰撞攻擊是指存在一個和用戶密碼不一樣的字符串,卻有相同的哈希值。然而,即便是像MD5這樣的脆弱的哈希函數找到碰撞也須要大量的專門算力( dedicated computing power),因此在實際中「意外地」出現哈希碰撞的狀況不太可能。對於實用性而言,加鹽 MD5 和加鹽 SHA256 的安全性同樣。儘管如此,可能的話,要使用更安全的哈希函數,好比 SHA256 、 SHA512 、 RipeMD 或 WHIRLPOOL 。
本節介紹了究竟應該如何對密碼進行哈希加密。第一部分介紹基礎知識,這部分是必須的。後面闡述如何在這個基礎上加強安全性,使哈希加密變得更難破解。
咱們已經知道,惡意攻擊者使用查詢表和彩虹表,破解普通哈希加密有多麼快。咱們也已經瞭解到,使用隨機加鹽哈希能夠解決這個問題。可是,咱們使用什麼樣的鹽值,又如何將其混入密碼中?
鹽值應該使用加密的安全僞隨機數生成器( Cryptographically Secure Pseudo-Random Number Generator,CSPRNG )產生。CSPRNG和普通的僞隨機數生成器有很大不一樣,如「 C 」語言的rand()函數。顧名思義, CSPRNG 被設計成用於加密安全,這意味着它能提供高度隨機、徹底不可預測的隨機數。咱們不但願鹽值可以被預測到,因此必須使用 CSPRNG 。下表列出了一些當前主流編程平臺的 CSPRNG 方法。
Platform |
CSPRNG |
PHP |
mcrypt_create_iv, openssl_random_pseudo_bytes |
Java |
java.security.SecureRandom |
Dot NET (C#, VB) |
System.Security.Cryptography.RNGCryptoServiceProvider |
Ruby |
SecureRandom |
Python |
os.urandom |
Perl |
Math::Random::Secure |
C/C++ (Windows API) |
CryptGenRandom |
Any language on GNU/Linux or Unix |
Read from /dev/random or /dev/urandom |
每一個用戶的每個密碼都要使用獨一無二的鹽值。用戶每次建立賬號或更改密碼時,密碼應採用一個新的隨機鹽值。永遠不要重複使用某個鹽值。這個鹽值也應該足夠長,以使有足夠多的鹽值能用於哈希加密。一個經驗規則是,鹽值至少要跟哈希函數的輸出同樣長。該鹽應和密碼哈希一塊兒存儲在用戶賬號表中。
存儲密碼的步驟:
校驗密碼的步驟:
若是您正在編寫一個 Web 應用,你可能會疑惑究竟在哪裏進行哈希加密,是在用戶的瀏覽器上使用 JavaScript 對密碼進行哈希加密呢,仍是將明文發送到服務端上再進行哈希加密呢?
就算瀏覽器上已經用JavaScript哈希加密了,但你你仍是要在服務端上將獲得的密碼哈希值再進行一次哈希加密。試想一個網站,將用戶在瀏覽器輸入的密碼通過哈希加密,而不是在傳送到服務端再進行哈希。爲了驗證用戶,這個網站將接受來自瀏覽器的哈希值,並和數據庫中的哈希值進行匹配便可。由於用戶的密碼從未明文傳輸到服務端,這樣子看上去更安全,但事實並不是如此。
問題是,從客戶端的角度來看,通過哈希的密碼,從邏輯上成爲用戶的密碼了。全部用戶須要作的認證就是將它們的密碼哈希值告訴服務端。若是一個攻擊者獲得了用戶的哈希值,他們能夠用它來經過認證,而沒必要知道用戶的明文密碼!因此,若是攻擊者使用某種手段拖了網站的數據庫,他們就能夠隨意使用每一個人的賬號直接訪問,而無需猜想任何密碼。
這並非說你不該該在瀏覽器進行哈希加密,可是若是你這樣作了,你必定要在服務端上再進行一次哈希加密。在瀏覽器中進行哈希加密無疑是一個好主意,但實現的時候要考慮如下幾點:
加鹽能夠確保攻擊者沒法使用像查詢表和彩虹表攻擊那樣對大量哈希值進行破解,但依然不能阻止他們使用字典攻擊或暴力攻擊。高端顯卡( GPU )和定製的硬件每秒能夠進行十億次哈希計算,因此這些攻擊仍是頗有效的。爲了下降使這些攻擊的效率,咱們可使用一個叫作密鑰擴展( key stretching)的技術。
這樣作的初衷是爲了將哈希函數變得很是慢,即便有一塊快速的 GPU 或定製的硬件,字典攻擊和暴力攻擊也會慢得使人失去耐心。終極目標是使哈希函數的速度慢到足以令攻擊者放棄,但由此形成的延遲又不至於引發用戶的注意。
密鑰擴展的實現使用了一種 CPU 密集型哈希函數( CPU-intensive hash function)。不要試圖去創造你本身的迭代哈希加密函數。迭代不夠多的話,它能夠被高效的硬件快速並行計算出來,就跟普通的哈希同樣。要使用標準的算法,好比 PBKDF2 或 bcrypt 。你能夠在這裏找到 PBKDF2 在 PHP 上的實現。
這類算法採起安全因子或迭代次數做爲參數。此值決定哈希函數將會如何緩慢。對於桌面軟件或智能手機應用,肯定這個參數的最佳方式是在設備上運行很短的性能基準測試,找到使哈希大約花費半秒的值。經過這種方式,程序能夠儘量保證安全而又不影響用戶體驗。
若是您想在一個 Web 應用使用密鑰擴展,須知你須要額外的計算資源來處理大量的身份認證請求,而且密鑰擴展也容易讓服務端遭受拒絕服務攻擊( DoS )。儘管如此,我仍是建議使用密鑰擴展,只不過要設定較低一些的迭代次數。這個次數須要根據本身服務器的計算能力和預計每秒須要處理的認證請求次數來設置。消除拒絕服務的威脅能夠經過要求用戶每次登錄時輸入驗證碼( CAPTCHA )來作到。系統設計時要將迭代次數可隨時方便調整。
若是你擔憂計算帶來負擔,但又想在 Web 應用中使用密鑰擴展,能夠考慮在瀏覽器中使用 JavaScript 完成。斯坦福大學的 JavaScript 加密庫就包含了 PBKDF2 的實現。迭代次數應設置足夠低,以適應速度較慢的客戶端,如移動設備。同時,若是用戶的瀏覽器不支持 JavaScript ,服務端應該接手進行計算。客戶端密鑰擴展並不能免除服務端端進行哈希加密的須要。你必須對客戶端生成的哈希值再次進行哈希加密,就跟普通口令的處理同樣。
只要攻擊者可使用哈希來檢查密碼的猜想是對仍是錯,那麼他們能夠進行字典攻擊或暴力攻擊。下一步是將密鑰( secret key)添加到哈希加密,這樣只有知道密鑰的人才能夠驗證密碼。有兩種實現的方式,使用ASE算法對哈希值加密;或者使用密鑰哈希算法 HMAC 將密鑰包含到哈希字符串中。
實現起來並沒那麼容易。這個密鑰必須在任何狀況下,即便系統由於漏洞被攻陷,也不能被攻擊者獲取。若是攻擊者徹底進入系統,密鑰無論存儲在何處,總能被找到。所以,密鑰必須密鑰必須被存儲在外部系統,例如專用於密碼驗證一個物理上隔離的服務端,或者鏈接到服務端,例如一個特殊的硬件設備,如 YubiHSM 。
我強烈建議全部大型服務(超過10萬用戶)使用這種方式。我認爲對於任何超過100萬用戶的服務託管是很是有必要的。
若是您難以負擔多個服務端或專用硬件的費用,依然有辦法在標準的Web服務端上使用密鑰哈希技術。大多數數據庫被拖庫是因爲 SQL 注入攻擊,所以,不要給攻擊者進入本地文件系統的權限(禁止數據庫服務訪問本地文件系統,若是有此功能的話)。若是您生成一個隨機密鑰並將其存儲在一個經過 Web 沒法訪問的文件上,而後進行加鹽哈希加密,那麼獲得的哈希值就不會那麼容易被破解了,就算數據庫已經遭受注入攻擊,也是安全的。不要將密鑰硬編碼到代碼中,應該在安裝應用時隨機生成。這麼作並不像使用一個獨立的系統那樣安全,由於若是 Web 應用存在 SQL 注入點,那麼有可能存在其餘一些問題,如本地文件包含漏洞( Local File Inclusion ),攻擊者能夠利用它讀取本地密鑰文件。不管如何,這個措施總比沒有好。
請注意,密鑰哈希並不意味着無需進行加鹽。高明的攻擊者最終會千方百計找到密鑰,所以,對密碼哈希仍然須要進行加鹽和密鑰擴展,這一點很是重要。
密碼哈希僅僅在安全受到破壞時保護密碼。它並不能使整個應用更加安全。首先有不少事必須完成,來保證密碼哈希值(和其餘用戶數據)不被竊取。
即便是經驗豐富的開發人員也必須學習安全知識,才能編寫安全的應用。此處有關於Web應用漏洞的重要資源: The Open Web Application Security Project (OWASP)。還有一個很好的介紹: OWASP Top Ten Vulnerability List 。除非你理解了列表中的全部漏洞,不然不要去嘗試編寫一個處理敏感數據的Web應用程序。僱主也有責任確保全部開發人員在安全應用開發方面通過充分的培訓。
對您的應用進行第三方「滲透測試」是一個很好的主意。即便最好的程序員也可能會犯錯,因此,讓安全專家審計代碼尋找潛在的漏洞是有意義的。找一個值得信賴的機構(或招聘人員)來按期審計代碼。安全審計應該從開發初期就着手進行,並貫穿整個開發過程。
監控您的網站來發現入侵行爲也很重要。我建議至少僱用一名全職人員負責監測和處理安全漏洞。若是某個漏洞沒被發現,攻擊者可能經過網站利用惡意軟件感染訪問者,所以,檢測漏洞並及時處理是極爲重要的。
可使用:
不可以使用:
儘管目前尚未一種針對MD5或SHA1很是高效的攻擊手段,但它們過於古老以致於被普遍認爲不足以用來存儲密碼(可能有些不恰當)。因此我不推薦使用它們。可是也有例外,PBKDF2中常用SHA1做爲它底層的哈希函數。
這是我我的的觀點:當下全部普遍使用的密碼重置機制都是不安全的。若是你對高安全性有要求,如加密服務,那麼就不要讓用戶重設密碼。
大多數網站向那些忘記密碼的用戶發送電子郵件來進行身份認證。要作到這一點,須要隨機生成一個一次性使用的令牌( token ),直接關聯到用戶的賬號。而後將這個令牌混入一個重置密碼的連接中,發送到用戶的電子郵箱。當用戶點擊包含有效令牌的密碼重置連接,就提示他們輸入新密碼。確保令牌只對一個賬號有效,以防攻擊者從郵箱獲取到令牌後用來重置其餘用戶的密碼。
令牌必須在15分鐘內使用,且一旦使用後就當即做廢。當用戶登陸成功時(代表還記得本身的密碼), 或者從新請求令牌時,使原令牌失效是一個好作法。若是令牌永不過時,那麼它就能夠一直用於入侵用戶的帳號。電子郵件(SMTP)是一個純文本協議,網絡上有不少惡意路由在截取郵件信息。在用戶修改密碼後,那些包含重置密碼連接的郵件在很長時間內缺少保護,所以,儘早使令牌儘快過時,來下降用戶信息暴露給攻擊者的風險。
攻擊者可以篡改令牌,所以不要把賬號信息和失效時間存儲在其中。它們應該以不可猜想的二進制形式存在,而且只用來識別數據庫中某條用戶的記錄。
千萬不要經過電子郵件向用戶發送新密碼。記得在用戶重置密碼時隨機生成一個新的鹽值用來加密,不要重複使用已用於密碼哈希加密的舊鹽值。
你的首要任務是,肯定系統被暴露到什麼程度,而後修復攻擊者利用的的漏洞。若是你沒有應對入侵的經驗,我強烈建議聘請第三方安全公司來作這件事。
捂住一個漏洞並期待沒人知道,是否是很省事,又誘人?可是這樣作只會讓你的處境變得更糟糕,由於你在用戶不知情的狀況下,將它們的密碼和我的信息置於暴露風險之中。就算你尚未徹底發生什麼事情時,你也應該儘快通知用戶。例如在首頁放置一個連接,指向對此問題更爲詳細的說明;若是可能的話經過電子郵件發送通知給每一個用戶告知目前的狀況。
向用戶說明他們的密碼到底是如何被保護的:最好是使用了加鹽哈希。可是,即便用了加鹽哈希,惡意黑客仍然可使用字典攻擊和暴力攻擊。若是用戶在不少服務使用相同的密碼,惡意黑客會利用他們找到的密碼去嘗試登錄其餘網站。告知用戶這個風險,建議他們修改全部相似的密碼,不論密碼用在哪一個服務上。強制他們下次登陸你的網站時更改密碼。大多數用戶會嘗試「修改」本身的密碼爲原始密碼,以便記憶。您應該使用當前密碼哈希值以確保用戶沒法作到這一點。
就算有加鹽哈希的保護,也存在攻擊者快速破解其中一些弱口令密碼的可能性。爲了減小攻擊者使用這些密碼的機會,應該對這些密碼的賬號發送認證電子郵件,直到用戶修改了密碼。可參考前面提到的問題:當用戶忘記密碼時如何重置密碼?這其中有一些實現電子郵件認證的要點。
另外告訴你的用戶,網站存儲了哪些我的信息。若是您的數據庫包括信用卡號碼,您應該通知用戶仔細檢查近期帳單並銷掉這張信用卡。
若是您的服務沒有嚴格的安全要求,那麼不要對用戶進行限制。我建議在用戶輸入密碼時,頁面顯示出密碼強度,由他們本身決定須要多安全的密碼。若是你有特殊的安全需求,那就應該實施長度至少爲12個字符的密碼,而且至少須要兩個字母、兩個數字和兩個符號。
不要過於頻繁地強制你的用戶更改密碼,最多每半年一次,超過這個次數,用戶就會感到疲勞。相反,更好的作法是教育用戶,當他們感受密碼可能泄露時主動修改,而且提示用戶不要把密碼告訴任何人。若是這是一個商業環境,鼓勵員工利用工做時間熟記並使用他們的密碼。
是的,但若是有人入侵您的數據庫,他們極可能已經可以訪問您的服務端上的全部內容,這樣他們就不須要登陸到您的賬號,就能夠得到他們想要的東西。密碼哈希(對網站而言)的目的不是爲了保護被入侵的網站,而是在入侵已經發生時保護數據庫中的密碼。
你能夠經過給數據庫鏈接設置兩種權限,防止密碼哈希在遭遇注入攻擊時被篡改。一種權限用於建立用戶,一種權限用於用戶登錄。「建立用戶」的代碼應該可以讀寫用戶表;但「用戶登錄」的代碼應該只可以讀取用戶表而不能寫入。
如 MD五、SHA一、SHA2 和 Hash 函數使用 Merkle–Damg?rd ,這使得它們很容易受到所謂的長度擴展攻擊( length extension attack)。意思是給定的哈希值 H(X),對於任意的字符串 Y,攻擊者能夠計算出 H(pad(X)+Y) 的值,而無需知道 X 的值。其中, pad(X) 是哈希函數的填充函數。
這意味着,攻擊者不知道密鑰的狀況下,仍然能夠根據給定的哈希值 H(key+message) 計算出 H(pad(key+message)+extension) 。若是該哈希值用於身份認證,並依靠其中的密鑰來防止攻擊者篡改消息,這方法已經行不通。由於攻擊者無需知道密鑰也能構造出包含 message+extension 的一個有效的哈希值。
目前尚不清楚攻擊者如何利用這種攻擊來快速破解密碼哈希。然而,因爲這種攻擊的出現,不建議使用普通的哈希函數對密鑰進行哈希加密。未來也許某個高明的密碼學家有一天發現利用長度擴展攻擊的新思路,從而更快的破解密碼,因此仍是使用 HMAC 爲好。
無所謂,選擇一個並保持風格一致便可,以避免出現互操做方面的問題。鹽值加到密碼以前較爲廣泛。
使用固定的時間來比較哈希值能夠防止攻擊者在在線系統使用基於時間差的攻擊,以此獲取密碼的哈希值,而後進行本地破解。
比較兩個字節序列(字符串)是否相同的標準作法是,從第一個字節開始,每一個字節逐一順序比較。只要發現某個字節不一樣,就能夠知道它們是不一樣的,當即返回false。若是遍歷整個字符串沒有找到不一樣的字節,能夠確認兩個字符串就是相同的,能夠返回true。這意味着比較兩個字符串,若是它們相同的長度不同,花費的時間不同。開始部分相同的長度越長,花費的時間也就越長。
例如,字符串 「XYZABC」 和 「abcxyz」 的標準比較,會當即看到,第一個字符是不一樣的,就不須要檢查字符串的其他部分。相反,當字符串 「aaaaaaaaaaB」 和 「aaaaaaaaaaZ」 進行比較時,比較算法就須要遍歷最後一位前全部的 「a」 ,而後才能知道他們是不一樣的。
假設攻擊者試圖入侵一個在線系統,這個系統限制了每秒只能嘗試一次用戶認證。還假設攻擊者已經知道密碼哈希全部的參數(鹽值、哈希函數的類型等),除了密碼的哈希值和密碼自己。若是攻擊者能精確測量在線系統耗時多久去比較他猜想的密碼和真實密碼,那麼他就能使用時序攻擊獲取密碼的哈希值,而後進行離線破解,從而繞過系統對認證頻率的限制。
首先攻擊者準備256個字符串,它們的哈希值的第一字節包含了全部可能的狀況。他將每一個字符串發送給在線系統嘗試登錄,並記錄系統響應所消耗的時間。耗時最長的字符串就是第一字節相匹配的。攻擊者知道第一字節後,並能夠用一樣的方式繼續猜想第二字節、第三字節等等。一旦攻擊者得到足夠長的哈希值片斷,他就能夠在本身的機器上來破解,不受在線系統的限制。
在網絡上進行這種攻擊彷佛不可能。然而,有人已經實現了,並已證實是實用的。這就是爲何本文提到的代碼,它利用固定時間去比較字符串,而無論有多大的字符串。
前一個問題解釋了爲何「慢比較」是必要的,如今來解釋代碼如何工做。
private static boolean slowEquals(byte[] a, byte[] b) { int diff = a.length ^ b.length; for(int i = 0; i < a.length && i < b.length; i++) diff |= a[i] ^ b[i]; return diff == 0; }
該代碼使用異或運算符「^」來比較兩個整數是否相等,而不是「==」運算符。下面解釋緣由。當且僅當兩位相等時,異或的結果將是零。這是由於:
0 XOR 0 = 0,1 XOR 1 = 0,0 XOR 1 = 1,1 XOR 0 = 1
若是咱們將其應用到整數中每一位,當且僅當字節兩個整數各位都相等,結果纔是0。
因此,在代碼的第一行中,若是a.length等於b.length ,相同的話獲得0,否者獲得非零值。而後使用異或比較數組中各字節,而且將結果和diff求或。若是有任何一個字節不相同,diff就會變成非零值。由於或運算沒有「置0」的功能,因此循環結束後diff是0的話只有一種可能,那就是循環前兩個數組長度相等(a.length == b.length),而且數組中每個字節都相同(每次異或的結果都非0)。
咱們須要使用XOR,而不是「==」運算符比較整數的緣由是,「==」一般是編譯成一個分支的語句。例如,C語言代碼中「 diff &= a == b」可能編譯如下x86彙編:
MOV EAX, [A] CMP [B], EAX JZ equal JMP done equal: AND [VALID], 1 done: AND [VALID], 0
其中的分支致使代碼運行的時間不固定,決定於兩個整數相等的程度和CPU內部的跳轉預測機制(branch prediction)。
而C語言代碼「diff |= a ^ b」會被編譯爲下面的樣子,它執行的時間和兩個變量是否相等無關。
MOV EAX,[A] XOR EAX,[B] OR [DIFF],EAX
用戶在你的網站上輸入密碼,是由於他們相信你能保證密碼的安全。若是你的數據庫遭到黑客攻擊,而用戶的密碼又不受保護,那麼惡意黑客能夠利用這些密碼嘗試登錄其餘網站和服務(大多數用戶會在全部地方使用相同的密碼)。這不只僅關乎你網站的安全,更關係到用戶的安全。你有責任負責用戶的安全。