程序員之網絡安全系列(二):如何安全保存用戶密碼及哈希算法

系列目錄:html

前言

在不少網站的早期,甚至是如今仍然有一些網站,當你點擊忘記密碼功能時,你的郵箱會收到一封郵件,而後裏面赫然寫着你的密碼,不少普通用戶還會以爲慶幸,總算是找回來了,卻不知,這是多麼可怕地一件事,說明了網站是「幾乎是」明文存儲你的密碼,一旦數據用戶數據泄露或者被拖庫,那麼用戶密碼將赤裸裸的暴露了,想一想以前幾回互聯網密碼泄露事件。前端

那麼如何解決呢?程序員

加密

爲了避免讓密碼明文存儲,咱們須要對密碼進行加密,這樣即便數據庫用戶密碼暴露,也是加密後的。可是如何讓加密後的數據難以解密呢?咱們如今比較流行的作法就是把密碼進行Hash存儲。算法

Hash

哈希算法將任意長度的二進制值映射爲較短的固定長度的二進制值,這個小的二進制值稱爲哈希值。哈希值是一段數據惟一且極其緊湊的數值表示形式. 典型的哈希算法包括 MD二、MD四、MD5 和 SHA-1數據庫

Hash算法是給消息生成摘要,那麼什麼是摘要呢?安全

舉個例子:服務器

好比你給你女友寫了一封郵件,確保沒被人改過,你能夠生成這樣一份摘要 「第50個字是我,第100個字是愛, 第998個字是你」,那麼你女友收到這個摘要,檢查一下你的郵件就能夠了。網絡

Hash算法有兩個很是主要的特徵:運維

  • 不能經過摘要來反推出原文
  • 原文的很是細小的改動,都會引發Hash結果的很是大的變化

所以,這個比較適合用來保存用戶密碼,由於不能反推出用戶密碼,Hash結果一致就證實原文一致,咱們來用Ruby代碼試一下上面的第二點 (MD5是一種經常使用的Hash算法)post

2.2.3 :003 > require 'digest/md5.so'
=> true
2.2.3 :004 > puts Digest::MD5.hexdigest('I love you')
e4f58a805a6e1fd0f6bef58c86f9ceb3
=> nil
2.2.3 :005 > puts Digest::MD5.hexdigest('I love you!')
690a8cda8894e37a6fff4d1790d53b33
=> nil
2.2.3 :006 > puts Digest::MD5.hexdigest('I love you !')
b2c63c3ca6019cff3bad64fcfa807361
=> nil
2.2.3 :007 > puts Digest::MD5.hexdigest('I love you')
e4f58a805a6e1fd0f6bef58c86f9ceb3
=> nil
2.2.3 :008 >

那麼咱們在使用MD5保存密碼時候的驗證流程是什麼呢?

  • 用戶註冊時,把用戶密碼是MD5(password)後保存到數據庫。
  • 用戶輸入用戶名和密碼
  • 服務器從數據庫查找用戶名
  • 若是有這個用戶,A=MD5(input password), B=Database password
  • 若是A==B, 那麼說明用戶密碼輸入正確,若是不相等,用戶輸入錯誤。

爲何Hash(MD5)後仍然不夠安全?

窮舉

可是,若是你認爲就只是這樣密碼就不會被人知道,那麼就不對了,這只是比明文更安全,爲何?

由於,大部分人的密碼都很是簡單,當拿到MD5的密碼後,攻擊者也能夠經過比對的方式,好比你的密碼是4218

2.2.3 :008 > puts Digest::MD5.hexdigest('4218')
d278df4919453195d221030324127a0e

那麼攻擊者能夠把1到4218個數字都MD5一下,而後和你密碼的MD5對比一下,就知道你原密碼是什麼了。

曾經個人密碼箱密碼忘了,我把鎖給撬了,後來我纔想起能夠用窮舉法,最多就999次不就打開了?那麼問題來了,你的密碼箱還安全嗎?

彩虹表

除了窮舉法外,因爲以前的密碼泄露,那麼攻擊者們,手上都有大量的彩虹表,好比"I love you",生日等等,這個表保存了這些原值以及MD5後的值,那麼使用時直接從已有庫裏就能夠查出來對應的密碼。

加鹽 Salt

那麼,因爲簡單的對密碼進行Hash算法不夠安全,那麼咱們就能夠對密碼加Salt,好比密碼是"I love you", 雖然彩虹表裏有這條數據,可是若是加上"安紅我愛你",這樣MD5結果就大不同.

jacks-MacBook-Air:~ jack$ irb
2.2.3 :001 > require 'digest/md5.so'
=> true
2.2.3 :002 > puts Digest::MD5.hexdigest('I love you')
e4f58a805a6e1fd0f6bef58c86f9ceb3
=> nil
2.2.3 :003 > puts Digest::MD5.hexdigest('I love you安紅我愛你')
b10d890bf46b1a045eb99af5d43c7b13
=> nil
2.2.3 :004 > puts Digest::MD5.hexdigest('I dont love you')
c82294c9a7b6e4a372ad25ed4d6011c9
=> nil
2.2.3 :005 > puts Digest::MD5.hexdigest('I dont love you安紅我愛你')
dce67bcdfdf007445dd4a2c2dc3d29c1
=> nil
2.2.3 :006 >

如此一來,由於攻擊者很難猜到「安紅我愛你」,那麼天然彩虹表裏是沒有的,固然我建議你在實際項目中不要使用"安紅我愛你",你應該使用一個連你本身都猜不到的較長的字符串。

加鹽了,就安全了嗎?

實際上,加鹽並不能100%保證安全,假若有人泄露了你的Salt呢?實際上經過反編譯程序很容易能夠拿到這個,因爲WEB程序通常放在WEB服務器上,那麼就須要保證服務器不被攻擊,固然這個是運維人員去操心。

爲了讓加鹽更安全,通常狀況下咱們可使用一個「鹽+鹽」,也就是爲每一個用戶保存一個"Salt", 而後再使用全局的鹽,咱們能夠對用戶的鹽使用本身的加密算法。那麼代碼就以下:

if MD5(userInputPpassword+globalsalt+usersalt)===user.databasePassword) 
{
    login success
}

普通用戶如何作?

因爲這個是寫給程序員,固然是說在前端用戶註冊時密碼應該如何設置,很簡單,咱們要求用戶必須輸入強密碼!可是,我知道不少用戶以爲很煩,這樣你就失掉了一個用戶,但咱們須要作一個適當的折中,好比至少有一個大寫字母,小寫字母和數字的組合。

最後

咱們來看看解決了以前文章下面例子的什麼問題。

假如,明明和麗麗相互不認識,明明想給麗麗寫一封情書,讓隔壁老王送去

  1. 如何保證隔壁老王不能看到情書內容?(保密性)
  2. 如何保證隔壁老王不修改情書的內容?(完整性)
  3. 如何保證隔壁老王不冒充明明?(身份認證)
  4. 如何保證實明不可否認情書是本身寫的?(來源的不能否認)

經過了解hash算法,"明明" 就有辦法讓麗麗知道信的內容沒有修改,他能夠對郵件進行Hash生成郵件的摘要,而後讓"隔壁的李叔叔"把摘要送給麗麗,麗麗拿到郵件的摘要後,把郵件內容也Hash一下,而後把結果和"隔壁的李叔叔"給的摘要對比一下,而後經過比較結果就知道郵件有沒有被"隔壁的王叔叔"更改過了。

相關文章
相關標籤/搜索