程序员之网络安全系列(二):如何安全保存用户密码及哈希算法

系列目录: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一下,而后把结果和"隔壁的李叔叔"给的摘要对比一下,而后经过比较结果就知道邮件有没有被"隔壁的王叔叔"更改过了。

相关文章
相关标签/搜索