如何正确对用户密码进行加密?
本文介紹了對密碼哈希加密的基礎(chǔ)知識,以及什么是正確的加密方式。還介紹了常見的密碼破解方法,給出了如何避免密碼被破解的思路。相信讀者閱讀本文后,就會對密碼的加密有一個正確的認(rèn)識,并對密碼正確進行加密措施。
作為一名Web開發(fā)人員,我們經(jīng)常需要與用戶的帳號系統(tǒng)打交道,而這其中最大的挑戰(zhàn)就是如何保護用戶的密碼。經(jīng)常會看到用戶賬戶數(shù)據(jù)庫頻繁被黑,所以我們必須采取一些措施來保護用戶密碼,以免導(dǎo)致不必要的數(shù)據(jù)泄露。保護密碼的最好辦法是使用加鹽密碼哈希( salted password hashing)。
重要警告:請放棄編寫自己的密碼哈希加密代碼的念頭!因為這件事太容易搞砸了。就算你在大學(xué)學(xué)過密碼學(xué)的知識,也應(yīng)該遵循這個警告。所有人都要謹(jǐn)記這點:不要自己寫哈希加密算法! 存儲密碼的相關(guān)問題已經(jīng)有了成熟的解決方案,就是使用?phpass,或者在?defuse/password-hashing?或?libsodium?上的 PHP 、 C# 、 Java 和 Ruby 的實現(xiàn)。在對密碼進行哈希加密的問題上,人們有很多爭論和誤解,可能是由于網(wǎng)絡(luò)上有大量錯誤信息的原因吧。對密碼哈希加密是一件很簡單的事,但很多人都犯了錯。本文將會重點分享如何進行正確加密用戶密碼。
密碼哈希是什么?
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366 hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542哈希算法是一種單向函數(shù)。它把任意數(shù)量的數(shù)據(jù)轉(zhuǎn)換為固定長度的“指紋”,而且這個過程無法逆轉(zhuǎn)。它們有這樣的特性:如果輸入發(fā)生了一點改變,由此產(chǎn)生的哈希值會完全不同(參見上面的例子)。這個特性很適合用來存儲密碼。因為我們需要一種不可逆的算法來加密存儲的密碼,同時保證我們也能夠驗證用戶登陸的密碼是否正確。
在基于哈希加密的帳號系統(tǒng)中,用戶注冊和認(rèn)證的大致流程如下。
在步驟4中,永遠不要告訴用戶輸錯的究竟是用戶名還是密碼。就像通用的提示那樣,始終顯示:“無效的用戶名或密碼。”就行了。這樣可以防止攻擊者在不知道密碼的情況下枚舉出有效的用戶名。
應(yīng)當(dāng)注意的是,用來保護密碼的哈希函數(shù),和數(shù)據(jù)結(jié)構(gòu)課學(xué)到的哈希函數(shù)是不同的。例如,實現(xiàn)哈希表的哈希函數(shù)設(shè)計目的是快速查找,而非安全性。只有加密哈希函數(shù)( cryptographic hash function)才可以用來進行密碼哈希加密。像 SHA256 、 SHA512 、 RIPEMD 和 WHIRLPOOL 都是加密哈希函數(shù)。
人們很容易認(rèn)為,Web開發(fā)人員所做的就是:只需通過執(zhí)行加密哈希函數(shù)就可以讓用戶密碼得以安全。然而并不是這樣。有很多方法可以從簡單的哈希值中快速恢復(fù)出明文的密碼。有幾種易于實施的技術(shù),使這些“破解”的效率大為降低。網(wǎng)上有這種專門破解MD5的網(wǎng)站,只需提交一個哈希值,不到一秒鐘就能得到破解的結(jié)果。顯然,單純的對密碼進行哈希加密遠遠達不到我們的安全要求。下一節(jié)將討論一些用來破解簡單密碼哈希常用的手段。
如何破解哈希?
字典攻擊和暴力攻擊( Dictionary and Brute Force Attacks)
字典攻擊 | 暴力攻擊 |
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! |
破解哈希加密最簡單的方法是嘗試猜測密碼,哈希每個猜測的密碼,并對比猜測密碼的哈希值是否等于被破解的哈希值。如果相等,則猜中。猜測密碼攻擊的兩種最常見的方法是字典攻擊和暴力攻擊 。
字典攻擊使用包含單詞、短語、常用密碼和其他可能用做密碼的字符串的字典文件。對文件中的每個詞都進行哈希加密,將這些哈希值和要破解的密碼哈希值比較。如果它們相同,這個詞就是密碼。字典文件是通過大段文本中提取的單詞構(gòu)成,甚至還包括一些數(shù)據(jù)庫中真實的密碼。還可以對字典文件進一步處理以使其更為有效:如單詞 “hello” 按網(wǎng)絡(luò)用語寫法轉(zhuǎn)成 “h3110” 。
暴力攻擊是對于給定的密碼長度,嘗試每一種可能的字符組合。這種方式會消耗大量的計算,也是破解哈希加密效率最低的辦法,但最終會找出正確的密碼。因此密碼應(yīng)該足夠長,以至于遍歷所有可能的字符組合,耗費的時間太長令人無法承受,從而放棄破解。
目前沒有辦法來組織字典攻擊或暴力攻擊。只能想辦法讓它們變得低效。如果密碼哈希系統(tǒng)設(shè)計是安全的,破解哈希的唯一方法就是進行字典攻擊或暴力攻擊遍歷每一個哈希值了。
查表法( Lookup Tables)
Searching: 5f4dcc3b5aa765d61d8327deb882cf99: FOUND: password5 Searching: 6cbe615c106f422d23669b610b564800: not in database Searching: 630bf032efe4507f2c57b280995925a9: FOUND: letMEin12 Searching: 386f43fab5d096a7a66d67c8f213e5ec: FOUND: mcd0nalds Searching: d5ec75d5fe70d428685510fae36492d9: FOUND: p@ssw0rd!對于破解相同類型的哈希值,查表法是一種非常高效的方式。主要理念是預(yù)先計算( pre-compute)出密碼字典中的每個密碼的哈希值,然后把他們相應(yīng)的密碼存儲到一個表里。一個設(shè)計良好的查詢表結(jié)構(gòu),即使包含了數(shù)十億個哈希值,仍然可以實現(xiàn)每秒鐘查詢數(shù)百次哈希。
如果你想感受查表法的速度有多快,嘗試一下用 CrackStation 的?free hash cracker?來破解下面的 SHA256。
c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f9045206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd反向查表法( Reverse Lookup Tables)
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這種攻擊允許攻擊者無需預(yù)先計算好查詢表的情況下同時對多個哈希值發(fā)起字典攻擊或暴力攻擊。
首先,攻擊者從被黑的用戶帳號數(shù)據(jù)庫創(chuàng)建一個用戶名和對應(yīng)的密碼哈希表,然后,攻擊者猜測一系列哈希值并使用該查詢表來查找使用此密碼的用戶。通常許多用戶都會使用相同的密碼,因此這種攻擊方式特別有效。
彩虹表( Rainbow Tables)
彩虹表是一種以空間換時間的技術(shù)。與查表法相似,只是它為了使查詢表更小,犧牲了破解速度。因為彩虹表更小,所以在單位空間可以存儲更多的哈希值,從而使攻擊更有效。能夠破解任何最多8位長度的 MD5 值的彩虹表已經(jīng)出現(xiàn)。
接下來,我們來看一種謂之“加鹽( salting)”的技術(shù),能夠讓查表法和彩虹表都失效。
加鹽( Adding Salt)
hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1 hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007查表法和彩虹表只有在所有密碼都以完全相同的方式進行哈希加密才有效。如果兩個用戶有相同的密碼,他們將有相同的密碼哈希值。我們可以通過“隨機化”哈希,當(dāng)同一個密碼哈希兩次后,得到的哈希值是不一樣的,從而避免了這種攻擊。
我們可以通過在密碼中加入一段隨機字符串再進行哈希加密,這個被加的字符串稱之為鹽值。如上例所示,這使得相同的密碼每次都被加密為完全不同的字符串。我們需要鹽值來校驗密碼是否正確。通常和密碼哈希值一同存儲在帳號數(shù)據(jù)庫中,或者作為哈希字符串的一部分。
鹽值無需加密。由于隨機化了哈希值,查表法、反向查表法和彩虹表都會失效。因為攻擊者無法事先知道鹽值,所以他們就沒有辦法預(yù)先計算查詢表或彩虹表。如果每個用戶的密碼用不同的鹽再進行哈希加密,那么反向查表法攻擊也將不能奏效。
接下來,我們看看加鹽哈希通常會有哪些不正確的措施。
錯誤的方法:短鹽值和鹽值復(fù)用
最常見的錯誤,是多次哈希加密使用相同的鹽值,或者鹽值太短。
鹽值復(fù)用( Salt Reuse)
一個常見的錯誤是每次都使用相同的鹽值進行哈希加密,這個鹽值要么被硬編碼到程序里,要么只在第一次使用時隨機獲得。這樣的做法是無效的,因為如果兩個用戶有相同的密碼,他們?nèi)匀粫邢嗤墓V?。攻擊者仍然可以使用反向查表法對每個哈希值進行字典攻擊。他們只是在哈希密碼之前,將固定的鹽值應(yīng)用到每個猜測的密碼就可以了。如果鹽值被硬編碼到一個流行的軟件里,那么查詢表和彩虹表可以內(nèi)置該鹽值,以使其更容易破解它產(chǎn)生的哈希值。
用戶創(chuàng)建帳號或者更改密碼時,都應(yīng)該用新的隨機鹽值進行加密。
短鹽值( Short Slat)
如果鹽值太短,攻擊者可以預(yù)先制作針對所有可能的鹽值的查詢表。例如,如果鹽值只有三個 ASCII 字符,那么只有 95x95x95=857,375種可能性。這看起來很多,但如果每個查詢表包含常見的密碼只有 1MB,857,375個鹽值總共只需 837GB,一塊時下不到100美元的 1TB硬盤就能解決問題了。
出于同樣的原因,不應(yīng)該將用戶名用作鹽值。對每一個服務(wù)來說,用戶名是唯一的,但它們是可預(yù)測的,并且經(jīng)常重復(fù)應(yīng)用于其他服務(wù)。攻擊者可以用常見用戶名作為鹽值來建立查詢表和彩虹表來破解密碼哈希。
為使攻擊者無法構(gòu)造包含所有可能鹽值的查詢表,鹽值必須足夠長。一個好的經(jīng)驗是使用和哈希函數(shù)輸出的字符串等長的鹽值。例如, SHA256 的輸出為256位(32字節(jié)),所以該鹽也應(yīng)該是32個隨機字節(jié)。
雙重哈希和古怪的哈希函數(shù)
本節(jié)將介紹另一種常見的密碼哈希的誤解:古怪哈希的算法組合。人們很容易沖昏頭腦,嘗試不同的哈希函數(shù)相結(jié)合一起使用,希望讓數(shù)據(jù)會更安全。但在實踐中,這樣做并沒有什么好處。它帶來了函數(shù)之間互通性的問題,而且甚至可能會使哈希變得更不安全。永遠不要試圖去創(chuàng)造你自己的哈希加密算法,要使用專家設(shè)計好的標(biāo)準(zhǔn)算法。有人會說,使用多個哈希函數(shù)會降低計算速度,從而增加破解的難度。但是使破解過程變慢還有更好的辦法,我們將在后面講到。
下面是在網(wǎng)上見過的古怪的哈希函數(shù)組合的一些例子。
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))))
不要使用其中任何一種。
注意:此部分是有爭議的。我收到了一些電子郵件,他們認(rèn)為古怪的哈希函數(shù)是有意義的,理由是,如果攻擊者不知道系統(tǒng)使用哪個哈希函數(shù),那么攻擊者就不太可能預(yù)先計算出這種古怪的哈希函數(shù)彩虹表,于是破解起來要花更多的時間。
當(dāng)攻擊者不知道哈希加密算法的時候,是無法發(fā)起攻擊的。但是要考慮到柯克霍夫原則,攻擊者通常會獲得源代碼(尤其是免費或者開源軟件)。通過系統(tǒng)中找出密碼-哈希值對應(yīng)關(guān)系,很容易反向推導(dǎo)出加密算法。使用一個很難被并行計算結(jié)果的迭代算法(下面將予以討論),然后增加適當(dāng)?shù)柠}值防止彩虹表攻擊。
如果你真的想用一個標(biāo)準(zhǔn)的“古怪”的哈希函數(shù),如 HMAC ,亦無不可。但是,如果你目的是想降低哈希計算速度,那么可以閱讀下面有關(guān)密鑰擴展的部分。
如果創(chuàng)造新的哈希函數(shù),可能會帶來風(fēng)險,構(gòu)造希函數(shù)的組合又會導(dǎo)致函數(shù)互通性的問題。它們帶來一點的好處和這些比起來微不足道。很顯然,最好的辦法是,使用標(biāo)準(zhǔn)、經(jīng)過完整測試的算法。
哈希碰撞( Hash Collisions)
由于哈希函數(shù)將任意大小的數(shù)據(jù)轉(zhuǎn)化為定長的字符串,因此,必定有一些不同的輸入經(jīng)過哈希計算后得到了相同的字符串的情況。加密哈希函數(shù)( Cryptographic hash function)的設(shè)計初衷就是使這些碰撞盡量難以被找到?,F(xiàn)在,密碼學(xué)家發(fā)現(xiàn)攻擊哈希函數(shù)越來越容易找到碰撞了。最近的例子是MD5算法,它的碰撞已經(jīng)實現(xiàn)了。
碰撞攻擊是指存在一個和用戶密碼不同的字符串,卻有相同的哈希值。然而,即使是像MD5這樣的脆弱的哈希函數(shù)找到碰撞也需要大量的專門算力( dedicated computing power),所以在實際中“意外地”出現(xiàn)哈希碰撞的情況不太可能。對于實用性而言,加鹽 MD5 和加鹽 SHA256 的安全性一樣。盡管如此,可能的話,要使用更安全的哈希函數(shù),比如 SHA256 、 SHA512 、 RipeMD 或 WHIRLPOOL 。
如何正確進行哈希加密
本節(jié)介紹了究竟應(yīng)該如何對密碼進行哈希加密。第一部分介紹基礎(chǔ)知識,這部分是必須的。后面闡述如何在這個基礎(chǔ)上增強安全性,使哈希加密變得更難破解。
基礎(chǔ)知識:加鹽哈希( Hashing with Salt)
我們已經(jīng)知道,惡意攻擊者使用查詢表和彩虹表,破解普通哈希加密有多么快。我們也已經(jīng)了解到,使用隨機加鹽哈??梢越鉀Q這個問題。但是,我們使用什么樣的鹽值,又如何將其混入密碼中?
鹽值應(yīng)該使用加密的安全偽隨機數(shù)生成器( Cryptographically Secure Pseudo-Random Number Generator,CSPRNG )產(chǎn)生。CSPRNG和普通的偽隨機數(shù)生成器有很大不同,如“ C ”語言的rand()函數(shù)。顧名思義, CSPRNG 被設(shè)計成用于加密安全,這意味著它能提供高度隨機、完全不可預(yù)測的隨機數(shù)。我們不希望鹽值能夠被預(yù)測到,所以必須使用 CSPRNG 。下表列出了一些當(dāng)前主流編程平臺的 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 |
每個用戶的每一個密碼都要使用獨一無二的鹽值。用戶每次創(chuàng)建帳號或更改密碼時,密碼應(yīng)采用一個新的隨機鹽值。永遠不要重復(fù)使用某個鹽值。這個鹽值也應(yīng)該足夠長,以使有足夠多的鹽值能用于哈希加密。一個經(jīng)驗規(guī)則是,鹽值至少要跟哈希函數(shù)的輸出一樣長。該鹽應(yīng)和密碼哈希一起存儲在用戶帳號表中。
存儲密碼的步驟:
校驗密碼的步驟:
在 Web 應(yīng)用中,永遠在服務(wù)端上進行哈希加密
如果您正在編寫一個 Web 應(yīng)用,你可能會疑惑究竟在哪里進行哈希加密,是在用戶的瀏覽器上使用 JavaScript 對密碼進行哈希加密呢,還是將明文發(fā)送到服務(wù)端上再進行哈希加密呢?
就算瀏覽器上已經(jīng)用JavaScript哈希加密了,但你你還是要在服務(wù)端上將得到的密碼哈希值再進行一次哈希加密。試想一個網(wǎng)站,將用戶在瀏覽器輸入的密碼經(jīng)過哈希加密,而不是在傳送到服務(wù)端再進行哈希。為了驗證用戶,這個網(wǎng)站將接受來自瀏覽器的哈希值,并和數(shù)據(jù)庫中的哈希值進行匹配即可。因為用戶的密碼從未明文傳輸?shù)椒?wù)端,這樣子看上去更安全,但事實并非如此。
問題是,從客戶端的角度來看,經(jīng)過哈希的密碼,從邏輯上成為用戶的密碼了。所有用戶需要做的認(rèn)證就是將它們的密碼哈希值告訴服務(wù)端。如果一個攻擊者得到了用戶的哈希值,他們可以用它來通過認(rèn)證,而不必知道用戶的明文密碼!所以,如果攻擊者使用某種手段拖了網(wǎng)站的數(shù)據(jù)庫,他們就可以隨意使用每個人的帳號直接訪問,而無需猜測任何密碼。
這并不是說你不應(yīng)該在瀏覽器進行哈希加密,但是如果你這樣做了,你一定要在服務(wù)端上再進行一次哈希加密。在瀏覽器中進行哈希加密無疑是一個好主意,但實現(xiàn)的時候要考慮以下幾點:
- 客戶端密碼哈希加密不是 HTTPS(SSL/TLS)的替代品。如果瀏覽器和服務(wù)端之間的連接是不安全的,那么中間人攻擊可以修改 JavaScript 代碼,刪除加密函數(shù),從而獲取用戶的密碼。
- 某些瀏覽器不支持 JavaScript ,還有一些用戶在瀏覽器中禁用 JavaScript 功能。因此,為了更好的兼容性,您的應(yīng)用應(yīng)該檢測瀏覽器是否支持 JavaScript ,如果不支持,就需要在服務(wù)端模擬客戶端進行哈希加密。
- 客戶端的哈希加密同樣需要加鹽。顯而易見的解決方案是使客戶端腳本向服務(wù)端請求用戶的鹽值。但是不提倡這樣做,因為它可以讓攻擊者能夠在不知道密碼的情況下檢測用戶名是否有效。既然你已經(jīng)在服務(wù)端上對密碼進行了加鹽哈希(使用合格的鹽值),那么在客戶端,將用戶名(或郵箱)加上網(wǎng)站特有的字符串(如域名)作為客戶端的鹽值也是可行的。
使密碼更難破解:慢哈希函數(shù)( Slow Hash Function)
加鹽可以確保攻擊者無法使用像查詢表和彩虹表攻擊那樣對大量哈希值進行破解,但依然不能阻止他們使用字典攻擊或暴力攻擊。高端顯卡( GPU )和定制的硬件每秒可以進行十億次哈希計算,所以這些攻擊還是很有效的。為了降低使這些攻擊的效率,我們可以使用一個叫做密鑰擴展( key stretching)的技術(shù)。
這樣做的初衷是為了將哈希函數(shù)變得非常慢,即使有一塊快速的 GPU 或定制的硬件,字典攻擊和暴力攻擊也會慢得令人失去耐心。終極目標(biāo)是使哈希函數(shù)的速度慢到足以令攻擊者放棄,但由此造成的延遲又不至于引起用戶的注意。
密鑰擴展的實現(xiàn)使用了一種 CPU 密集型哈希函數(shù)( CPU-intensive hash function)。不要試圖去創(chuàng)造你自己的迭代哈希加密函數(shù)。迭代不夠多的話,它可以被高效的硬件快速并行計算出來,就跟普通的哈希一樣。要使用標(biāo)準(zhǔn)的算法,比如 PBKDF2 或 bcrypt 。你可以在這里找到 PBKDF2 在 PHP 上的實現(xiàn)。
這類算法采取安全因子或迭代次數(shù)作為參數(shù)。此值決定哈希函數(shù)將會如何緩慢。對于桌面軟件或智能手機應(yīng)用,確定這個參數(shù)的最佳方式是在設(shè)備上運行很短的性能基準(zhǔn)測試,找到使哈希大約花費半秒的值。通過這種方式,程序可以盡可能保證安全而又不影響用戶體驗。
如果您想在一個 Web 應(yīng)用使用密鑰擴展,須知你需要額外的計算資源來處理大量的身份認(rèn)證請求,并且密鑰擴展也容易讓服務(wù)端遭受拒絕服務(wù)攻擊( DoS )。盡管如此,我還是建議使用密鑰擴展,只不過要設(shè)定較低一些的迭代次數(shù)。這個次數(shù)需要根據(jù)自己服務(wù)器的計算能力和預(yù)計每秒需要處理的認(rèn)證請求次數(shù)來設(shè)置。消除拒絕服務(wù)的威脅可以通過要求用戶每次登陸時輸入驗證碼( CAPTCHA )來做到。系統(tǒng)設(shè)計時要將迭代次數(shù)可隨時方便調(diào)整。
如果你擔(dān)心計算帶來負(fù)擔(dān),但又想在 Web 應(yīng)用中使用密鑰擴展,可以考慮在瀏覽器中使用 JavaScript 完成。斯坦福大學(xué)的?JavaScript 加密庫就包含了 PBKDF2 的實現(xiàn)。迭代次數(shù)應(yīng)設(shè)置足夠低,以適應(yīng)速度較慢的客戶端,如移動設(shè)備。同時,如果用戶的瀏覽器不支持 JavaScript ,服務(wù)端應(yīng)該接手進行計算??蛻舳嗣荑€擴展并不能免除服務(wù)端端進行哈希加密的需要。你必須對客戶端生成的哈希值再次進行哈希加密,就跟普通口令的處理一樣。
不可能破解的哈希加密:密鑰哈希和密碼哈希設(shè)備
只要攻擊者可以使用哈希來檢查密碼的猜測是對還是錯,那么他們可以進行字典攻擊或暴力攻擊。下一步是將密鑰( secret key)添加到哈希加密,這樣只有知道密鑰的人才可以驗證密碼。有兩種實現(xiàn)的方式,使用ASE算法對哈希值加密;或者使用密鑰哈希算法?HMAC?將密鑰包含到哈希字符串中。
實現(xiàn)起來并沒那么容易。這個密鑰必須在任何情況下,即使系統(tǒng)因為漏洞被攻陷,也不能被攻擊者獲取。如果攻擊者完全進入系統(tǒng),密鑰不管存儲在何處,總能被找到。因此,密鑰必須密鑰必須被存儲在外部系統(tǒng),例如專用于密碼驗證一個物理上隔離的服務(wù)端,或者連接到服務(wù)端,例如一個特殊的硬件設(shè)備,如?YubiHSM?。
我強烈建議所有大型服務(wù)(超過10萬用戶)使用這種方式。我認(rèn)為對于任何超過100萬用戶的服務(wù)托管是非常有必要的。
如果您難以負(fù)擔(dān)多個服務(wù)端或?qū)S糜布馁M用,依然有辦法在標(biāo)準(zhǔn)的Web服務(wù)端上使用密鑰哈希技術(shù)。大多數(shù)數(shù)據(jù)庫被拖庫是由于?SQL 注入攻擊,因此,不要給攻擊者進入本地文件系統(tǒng)的權(quán)限(禁止數(shù)據(jù)庫服務(wù)訪問本地文件系統(tǒng),如果有此功能的話)。如果您生成一個隨機密鑰并將其存儲在一個通過 Web 無法訪問的文件上,然后進行加鹽哈希加密,那么得到的哈希值就不會那么容易被破解了,就算數(shù)據(jù)庫已經(jīng)遭受注入攻擊,也是安全的。不要將密鑰硬編碼到代碼中,應(yīng)該在安裝應(yīng)用時隨機生成。這么做并不像使用一個獨立的系統(tǒng)那樣安全,因為如果 Web 應(yīng)用存在 SQL 注入點,那么有可能存在其他一些問題,如本地文件包含漏洞( Local File Inclusion ),攻擊者可以利用它讀取本地密鑰文件。無論如何,這個措施總比沒有好。
請注意,密鑰哈希并不意味著無需進行加鹽。高明的攻擊者最終會想方設(shè)法找到密鑰,因此,對密碼哈希仍然需要進行加鹽和密鑰擴展,這一點非常重要。
其他安全措施
密碼哈希僅僅在安全受到破壞時保護密碼。它并不能使整個應(yīng)用更加安全。首先有很多事必須完成,來保證密碼哈希值(和其他用戶數(shù)據(jù))不被竊取。
即使是經(jīng)驗豐富的開發(fā)人員也必須學(xué)習(xí)安全知識,才能編寫安全的應(yīng)用。此處有關(guān)于Web應(yīng)用漏洞的重要資源:?The Open Web Application Security Project (OWASP)。還有一個很好的介紹:?OWASP Top Ten Vulnerability List?。除非你理解了列表中的所有漏洞,否則不要去嘗試編寫一個處理敏感數(shù)據(jù)的Web應(yīng)用程序。雇主也有責(zé)任確保所有開發(fā)人員在安全應(yīng)用開發(fā)方面經(jīng)過充分的培訓(xùn)。
對您的應(yīng)用進行第三方“滲透測試”是一個很好的主意。即使最好的程序員也可能會犯錯,所以,讓安全專家審計代碼尋找潛在的漏洞是有意義的。找一個值得信賴的機構(gòu)(或招聘人員)來定期審計代碼。安全審計應(yīng)該從開發(fā)初期就著手進行,并貫穿整個開發(fā)過程。
監(jiān)控您的網(wǎng)站來發(fā)現(xiàn)入侵行為也很重要。我建議至少雇用一名全職人員負(fù)責(zé)監(jiān)測和處理安全漏洞。如果某個漏洞沒被發(fā)現(xiàn),攻擊者可能通過網(wǎng)站利用惡意軟件感染訪問者,因此,檢測漏洞并及時處理是極為重要的。
常見疑問
我應(yīng)該使用什么樣的哈希算法?
可以使用:
- 精心設(shè)計的密鑰擴展算法如?PBKDF2?、bcrypt?和?scrypt?。
- OpenWall的的?Portable PHP password hashing framework。
- PBKDF2在PHP、C#、Java和Ruby的實現(xiàn)。
- crypt?的安全版本。
不可使用:
- 快速加密哈希函數(shù),如 MD5 、SHA1、SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3等。
- crypt()的不安全版本。
- 任何自己設(shè)計的加密算法。只應(yīng)該使用那些在公開領(lǐng)域中的、由經(jīng)驗豐富的密碼學(xué)家完整測試過的技術(shù)。
盡管目前還沒有一種針對MD5或SHA1非常高效的攻擊手段,但它們過于古老以至于被廣泛認(rèn)為不足以用來存儲密碼(可能有些不恰當(dāng))。所以我不推薦使用它們。但是也有例外,PBKDF2中經(jīng)常使用SHA1作為它底層的哈希函數(shù)。
當(dāng)用戶忘記密碼時如何重置密碼?
這是我個人的觀點:當(dāng)下所有廣泛使用的密碼重置機制都是不安全的。如果你對高安全性有要求,如加密服務(wù),那么就不要讓用戶重設(shè)密碼。
大多數(shù)網(wǎng)站向那些忘記密碼的用戶發(fā)送電子郵件來進行身份認(rèn)證。要做到這一點,需要隨機生成一個一次性使用的令牌( token ),直接關(guān)聯(lián)到用戶的帳號。然后將這個令牌混入一個重置密碼的鏈接中,發(fā)送到用戶的電子郵箱。當(dāng)用戶點擊包含有效令牌的密碼重置鏈接,就提示他們輸入新密碼。確保令牌只對一個帳號有效,以防攻擊者從郵箱獲取到令牌后用來重置其他用戶的密碼。
令牌必須在15分鐘內(nèi)使用,且一旦使用后就立即作廢。當(dāng)用戶登錄成功時(表明還記得自己的密碼), 或者重新請求令牌時,使原令牌失效是一個好做法。如果令牌永不過期,那么它就可以一直用于入侵用戶的賬號。電子郵件(SMTP)是一個純文本協(xié)議,網(wǎng)絡(luò)上有很多惡意路由在截取郵件信息。在用戶修改密碼后,那些包含重置密碼鏈接的郵件在很長時間內(nèi)缺乏保護,因此,盡早使令牌盡快過期,來降低用戶信息暴露給攻擊者的風(fēng)險。
攻擊者能夠篡改令牌,因此不要把帳號信息和失效時間存儲在其中。它們應(yīng)該以不可猜測的二進制形式存在,并且只用來識別數(shù)據(jù)庫中某條用戶的記錄。
千萬不要通過電子郵件向用戶發(fā)送新密碼。記得在用戶重置密碼時隨機生成一個新的鹽值用來加密,不要重復(fù)使用已用于密碼哈希加密的舊鹽值。
如果帳號數(shù)據(jù)庫被泄漏或入侵,應(yīng)該怎么做?
你的首要任務(wù)是,確定系統(tǒng)被暴露到什么程度,然后修復(fù)攻擊者利用的的漏洞。如果你沒有應(yīng)對入侵的經(jīng)驗,我強烈建議聘請第三方安全公司來做這件事。
捂住一個漏洞并期待沒人知道,是不是很省事,又誘人?但是這樣做只會讓你的處境變得更糟糕,因為你在用戶不知情的情況下,將它們的密碼和個人信息置于暴露風(fēng)險之中。就算你還沒有完全發(fā)生什么事情時,你也應(yīng)該盡快通知用戶。例如在首頁放置一個鏈接,指向?qū)Υ藛栴}更為詳細(xì)的說明;如果可能的話通過電子郵件發(fā)送通知給每個用戶告知目前的情況。
向用戶說明他們的密碼究竟是如何被保護的:最好是使用了加鹽哈希。但是,即使用了加鹽哈希,惡意黑客仍然可以使用字典攻擊和暴力攻擊。如果用戶在很多服務(wù)使用相同的密碼,惡意黑客會利用他們找到的密碼去嘗試登陸其他網(wǎng)站。告知用戶這個風(fēng)險,建議他們修改所有類似的密碼,不論密碼用在哪個服務(wù)上。強制他們下次登錄你的網(wǎng)站時更改密碼。大多數(shù)用戶會嘗試“修改”自己的密碼為原始密碼,以便記憶。您應(yīng)該使用當(dāng)前密碼哈希值以確保用戶無法做到這一點。
就算有加鹽哈希的保護,也存在攻擊者快速破解其中一些弱口令密碼的可能性。為了減少攻擊者使用這些密碼的機會,應(yīng)該對這些密碼的帳號發(fā)送認(rèn)證電子郵件,直到用戶修改了密碼??蓞⒖记懊嫣岬降膯栴}:當(dāng)用戶忘記密碼時如何重置密碼?這其中有一些實現(xiàn)電子郵件認(rèn)證的要點。
另外告訴你的用戶,網(wǎng)站存儲了哪些個人信息。如果您的數(shù)據(jù)庫包括信用卡號碼,您應(yīng)該通知用戶仔細(xì)檢查近期賬單并銷掉這張信用卡。
應(yīng)該使用什么樣的密碼策略?是否應(yīng)該使用強密碼?
如果您的服務(wù)沒有嚴(yán)格的安全要求,那么不要對用戶進行限制。我建議在用戶輸入密碼時,頁面顯示出密碼強度,由他們自己決定需要多安全的密碼。如果你有特殊的安全需求,那就應(yīng)該實施長度至少為12個字符的密碼,并且至少需要兩個字母、兩個數(shù)字和兩個符號。
不要過于頻繁地強制你的用戶更改密碼,最多每半年一次,超過這個次數(shù),用戶就會感到疲勞。相反,更好的做法是教育用戶,當(dāng)他們感覺密碼可能泄露時主動修改,并且提示用戶不要把密碼告訴任何人。如果這是一個商業(yè)環(huán)境,鼓勵員工利用工作時間熟記并使用他們的密碼。
如果攻擊者入侵了數(shù)據(jù)庫,他不能直接替換哈希值登陸任意帳號么?
是的,但如果有人入侵您的數(shù)據(jù)庫,他們很可能已經(jīng)能夠訪問您的服務(wù)端上的所有內(nèi)容,這樣他們就不需要登錄到您的帳號,就可以獲得他們想要的東西。密碼哈希(對網(wǎng)站而言)的目的不是為了保護被入侵的網(wǎng)站,而是在入侵已經(jīng)發(fā)生時保護數(shù)據(jù)庫中的密碼。
你可以通過給數(shù)據(jù)庫連接設(shè)置兩種權(quán)限,防止密碼哈希在遭遇注入攻擊時被篡改。一種權(quán)限用于創(chuàng)建用戶,一種權(quán)限用于用戶登陸?!皠?chuàng)建用戶”的代碼應(yīng)該能夠讀寫用戶表;但“用戶登陸”的代碼應(yīng)該只能夠讀取用戶表而不能寫入。
為什么要使用一種像HMAC的特殊算法,而不是只將密鑰混入密碼?
如 MD5、SHA1、SHA2 和 Hash 函數(shù)使用 Merkle–Damg?rd ,這使得它們很容易受到所謂的長度擴展攻擊( length extension attack)。意思是給定的哈希值 H(X),對于任意的字符串 Y,攻擊者可以計算出 H(pad(X)+Y) 的值,而無需知道 X 的值。其中, pad(X) 是哈希函數(shù)的填充函數(shù)。
這意味著,攻擊者不知道密鑰的情況下,仍然可以根據(jù)給定的哈希值 H(key+message) 計算出 H(pad(key+message)+extension) 。如果該哈希值用于身份認(rèn)證,并依靠其中的密鑰來防止攻擊者篡改消息,這方法已經(jīng)行不通。因為攻擊者無需知道密鑰也能構(gòu)造出包含 message+extension 的一個有效的哈希值。
目前尚不清楚攻擊者如何利用這種攻擊來快速破解密碼哈希。然而,由于這種攻擊的出現(xiàn),不建議使用普通的哈希函數(shù)對密鑰進行哈希加密。將來也許某個高明的密碼學(xué)家有一天發(fā)現(xiàn)利用長度擴展攻擊的新思路,從而更快的破解密碼,所以還是使用 HMAC 為好。
鹽值應(yīng)該加到密碼之前還是之后?
無所謂,選擇一個并保持風(fēng)格一致即可,以免出現(xiàn)互操作方面的問題。鹽值加到密碼之前較為普遍。
為何本文的哈希代碼都以固定時間比較哈希值?
使用固定的時間來比較哈希值可以防止攻擊者在在線系統(tǒng)使用基于時間差的攻擊,以此獲取密碼的哈希值,然后進行本地破解。
比較兩個字節(jié)序列(字符串)是否相同的標(biāo)準(zhǔn)做法是,從第一個字節(jié)開始,每個字節(jié)逐一順序比較。只要發(fā)現(xiàn)某個字節(jié)不同,就可以知道它們是不同的,立即返回false。如果遍歷整個字符串沒有找到不同的字節(jié),可以確認(rèn)兩個字符串就是相同的,可以返回true。這意味著比較兩個字符串,如果它們相同的長度不一樣,花費的時間不一樣。開始部分相同的長度越長,花費的時間也就越長。
例如,字符串 “XYZABC” 和 “abcxyz” 的標(biāo)準(zhǔn)比較,會立即看到,第一個字符是不同的,就不需要檢查字符串的其余部分。相反,當(dāng)字符串 “aaaaaaaaaaB” 和 “aaaaaaaaaaZ” 進行比較時,比較算法就需要遍歷最后一位前所有的 “a” ,然后才能知道他們是不同的。
假設(shè)攻擊者試圖入侵一個在線系統(tǒng),這個系統(tǒng)限制了每秒只能嘗試一次用戶認(rèn)證。還假設(shè)攻擊者已經(jīng)知道密碼哈希所有的參數(shù)(鹽值、哈希函數(shù)的類型等),除了密碼的哈希值和密碼本身。如果攻擊者能精確測量在線系統(tǒng)耗時多久去比較他猜測的密碼和真實密碼,那么他就能使用時序攻擊獲取密碼的哈希值,然后進行離線破解,從而繞過系統(tǒng)對認(rèn)證頻率的限制。
首先攻擊者準(zhǔn)備256個字符串,它們的哈希值的第一字節(jié)包含了所有可能的情況。他將每個字符串發(fā)送給在線系統(tǒng)嘗試登陸,并記錄系統(tǒng)響應(yīng)所消耗的時間。耗時最長的字符串就是第一字節(jié)相匹配的。攻擊者知道第一字節(jié)后,并可以用同樣的方式繼續(xù)猜測第二字節(jié)、第三字節(jié)等等。一旦攻擊者獲得足夠長的哈希值片段,他就可以在自己的機器上來破解,不受在線系統(tǒng)的限制。
在網(wǎng)絡(luò)上進行這種攻擊似乎不可能。然而,有人已經(jīng)實現(xiàn)了,并已證明是實用的。這就是為什么本文提到的代碼,它利用固定時間去比較字符串,而不管有多大的字符串。
“慢比較( slowequals)”函數(shù)如何工作?
前一個問題解釋了為什么“慢比較”是必要的,現(xiàn)在來解釋代碼如何工作。
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;}該代碼使用異或運算符“^”來比較兩個整數(shù)是否相等,而不是“==”運算符。下面解釋原因。當(dāng)且僅當(dāng)兩位相等時,異或的結(jié)果將是零。這是因為:
0 XOR 0 = 0,1 XOR 1 = 0,0 XOR 1 = 1,1 XOR 0 = 1
如果我們將其應(yīng)用到整數(shù)中每一位,當(dāng)且僅當(dāng)字節(jié)兩個整數(shù)各位都相等,結(jié)果才是0。
所以,在代碼的第一行中,如果a.length等于b.length ,相同的話得到0,否者得到非零值。然后使用異或比較數(shù)組中各字節(jié),并且將結(jié)果和diff求或。如果有任何一個字節(jié)不相同,diff就會變成非零值。因為或運算沒有“置0”的功能,所以循環(huán)結(jié)束后diff是0的話只有一種可能,那就是循環(huán)前兩個數(shù)組長度相等(a.length == b.length),并且數(shù)組中每一個字節(jié)都相同(每次異或的結(jié)果都非0)。
我們需要使用XOR,而不是“==”運算符比較整數(shù)的原因是,“==”通常是編譯成一個分支的語句。例如,C語言代碼中“ diff &= a == b”可能編譯以下x86匯編:
MOV EAX, [A] CMP [B], EAX JZ equal JMP done equal: AND [VALID], 1 done: AND [VALID], 0其中的分支導(dǎo)致代碼運行的時間不固定,決定于兩個整數(shù)相等的程度和CPU內(nèi)部的跳轉(zhuǎn)預(yù)測機制(branch prediction)。
而C語言代碼“diff |= a ^ b”會被編譯為下面的樣子,它執(zhí)行的時間和兩個變量是否相等無關(guān)。
MOV EAX,[A] XOR EAX,[B] OR [DIFF],EAX為何要進行哈希?
用戶在你的網(wǎng)站上輸入密碼,是因為他們相信你能保證密碼的安全。如果你的數(shù)據(jù)庫遭到黑客攻擊,而用戶的密碼又不受保護,那么惡意黑客可以利用這些密碼嘗試登陸其他網(wǎng)站和服務(wù)(大多數(shù)用戶會在所有地方使用相同的密碼)。這不僅僅關(guān)乎你網(wǎng)站的安全,更關(guān)系到用戶的安全。你有責(zé)任負(fù)責(zé)用戶的安全。
ref:http://www.infoq.com/cn/articles/how-to-encrypt-the-user-password-correctly/
總結(jié)
以上是生活随笔為你收集整理的如何正确对用户密码进行加密?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: s3cmd详解
- 下一篇: 支持多种格式的播放器js代码