前言
在很多網(wǎng)站的早期,甚至是現(xiàn)在仍然有一些網(wǎng)站,當(dāng)你點擊忘記密碼功能時,你的郵箱會收到一封郵件,然后里面赫然寫著你的密碼,很多普通用戶還會覺得慶幸,總算是找回來了,殊不知,這是多么可怕地一件事,說明了網(wǎng)站是“幾乎是”明文存儲你的密碼,一旦數(shù)據(jù)用戶數(shù)據(jù)泄露或者被拖庫,那么用戶密碼將赤裸裸的暴露了,想想之前幾次互聯(lián)網(wǎng)密碼泄露事件。
那么如何解決呢?
加密
為了不讓密碼明文存儲,我們需要對密碼進行加密,這樣即使數(shù)據(jù)庫用戶密碼暴露,也是加密后的。但是如何讓加密后的數(shù)據(jù)難以解密呢?我們現(xiàn)在比較流行的做法就是把密碼進行Hash存儲。
Hash
哈希算法將任意長度的二進制值映射為較短的固定長度的二進制值,這個小的二進制值稱為哈希值。哈希值是一段數(shù)據(jù)唯一且極其緊湊的數(shù)值表示形式.典型的哈希算法包括MD2、MD4、MD5和SHA-1
Hash算法是給消息生成摘要,那么什么是摘要呢?
舉個例子:
比如你給你女朋友寫了一封郵件,確保沒被人改過,你可以生成這樣一份摘要“第50個字是我,第100個字是愛,第998個字是你”,那么你女朋友收到這個摘要,檢查一下你的郵件就可以了。
Hash算法有兩個非常主要的特征:
不能通過摘要來反推出原文
原文的非常細小的改動,都會引起Hash結(jié)果的非常大的變化
因此,這個比較適合用來保存用戶密碼,因為不能反推出用戶密碼,Hash結(jié)果一致就證明原文一致,我們來用Ruby代碼試一下上面的第二點(MD5是一種常用的Hash算法)
2.2.3:003>require'digest/md5.so'=>true2.2.3:004>puts Digest::MD5.hexdigest('I love you')e4f58a805a6e1fd0f6bef58c86f9ceb3=>nil2.2.3:005>puts Digest::MD5.hexdigest('I love you!')690a8cda8894e37a6fff4d1790d53b33=>nil2.2.3:006>puts Digest::MD5.hexdigest('I love you!')b2c63c3ca6019cff3bad64fcfa807361=>nil2.2.3:007>puts Digest::MD5.hexdigest('I love you')e4f58a805a6e1fd0f6bef58c86f9ceb3=>nil2.2.3:008>
那么我們在使用MD5保存密碼時候的驗證流程是什么呢?
用戶注冊時,把用戶密碼是MD5(password)后保存到數(shù)據(jù)庫。
用戶輸入用戶名和密碼
服務(wù)器從數(shù)據(jù)庫查找用戶名
如果有這個用戶,A=MD5(input password),B=Database password
如果A==B,那么說明用戶密碼輸入正確,如果不相等,用戶輸入錯誤。
為什么Hash(MD5)后仍然不夠安全?窮舉
但是,如果你認為就只是這樣密碼就不會被人知道,那么就不對了,這只是比明文更安全,為什么?
因為,大部分人的密碼都非常簡單,當(dāng)拿到MD5的密碼后,攻擊者也可以通過比對的方式,比如你的密碼是4218
2.2.3:008>puts Digest::MD5.hexdigest('4218')d278df4919453195d221030324127a0e
那么攻擊者可以把1到4218個數(shù)字都MD5一下,然后和你密碼的MD5對比一下,就知道你原密碼是什么了。
曾經(jīng)我的密碼箱密碼忘了,我把鎖給撬了,后來我才想起可以用窮舉法,最多就999次不就打開了?那么問題來了,你的密碼箱還安全嗎?
彩虹表
除了窮舉法外,由于之前的密碼泄露,那么攻擊者們,手上都有大量的彩虹表,比如"I love you",生日等等,這個表保存了這些原值以及MD5后的值,那么使用時直接從已有庫里就可以查出來對應(yīng)的密碼。
加鹽Salt
那么,由于簡單的對密碼進行Hash算法不夠安全,那么我們就可以對密碼加Salt,比如密碼是"I love you",雖然彩虹表里有這條數(shù)據(jù),但是如果加上"安紅我愛你",這樣MD5結(jié)果就大不一樣.
jacks-MacBook-Air:~jack$irb2.2.3:001>require'digest/md5.so'=>true2.2.3:002>puts Digest::MD5.hexdigest('I love you')e4f58a805a6e1fd0f6bef58c86f9ceb3=>nil2.2.3:003>puts Digest::MD5.hexdigest('I love you安紅我愛你')b10d890bf46b1a045eb99af5d43c7b13=>nil2.2.3:004>puts Digest::MD5.hexdigest('I dont love you')c82294c9a7b6e4a372ad25ed4d6011c9=>nil2.2.3:005>puts Digest::MD5.hexdigest('I dont love you安紅我愛你')dce67bcdfdf007445dd4a2c2dc3d29c1=>nil2.2.3:006>
如此一來,因為攻擊者很難猜到“安紅我愛你”,那么自然彩虹表里是沒有的,當(dāng)然我建議你在實際項目中不要使用"安紅我愛你",你應(yīng)該使用一個連你自己都猜不到的較長的字符串。
加鹽了,就安全了嗎?
實際上,加鹽并不能100%保證安全,假如有人泄露了你的Salt呢?實際上通過反編譯程序很容易可以拿到這個,由于WEB程序一般放在WEB服務(wù)器上,那么就需要保證服務(wù)器不被攻擊,當(dāng)然這個是運維人員去操心。
為了讓加鹽更安全,一般情況下我們可以使用一個“鹽+鹽”,也就是為每個用戶保存一個"Salt",然后再使用全局的鹽,我們可以對用戶的鹽使用自己的加密算法。那么代碼就如下:
if MD5(userInputPpassword+globalsalt+usersalt)===user.databasePassword)普通用戶如何做?
由于這個是寫給程序員,當(dāng)然是說在前端用戶注冊時密碼應(yīng)該如何設(shè)置,很簡單,我們要求用戶必須輸入強密碼!但是,我知道很多用戶覺得很煩,這樣你就失掉了一個用戶,但我們需要做一個適當(dāng)?shù)恼壑?,比如至少有一個大寫字母,小寫字母和數(shù)字的組合。
最后
我們來看看解決了之前文章下面例子的什么問題。
假如,明明和麗麗相互不認識,明明想給麗麗寫一封情書,讓隔壁老王送去
如何保證隔壁老王不能看到情書的內(nèi)容?(保密性)
如何保證隔壁老王不修改情書的內(nèi)容?(完整性)
如何保證隔壁老王不冒充明明?(身份認證)
如何保證明明不能否認情書是自己寫的?(來源的不可否認)
通過了解hash算法,"明明"就有辦法讓麗麗知道信的內(nèi)容沒有修改,他可以對郵件進行Hash生成郵件的摘要,然后讓"隔壁的李叔叔"把摘要送給麗麗,麗麗拿到郵件的摘要后,把郵件內(nèi)容也Hash一下,然后把結(jié)果和"隔壁的李叔叔"給的摘要對比一下,然后通過比較結(jié)果就知道郵件有沒有被"隔壁的王叔叔"更改過了。