哈希(Hash)与加密(Encrypt)的基本原理、区别及工程应用


// 写在前面的话和背景php

由于作项目涉及到用户在浏览器或者用户windows的桌面系统中输入完用户名和密码,发http的rest请求到nodejs server端,server端验证用户名和密码是有效后(能够请求db或者ldap),继续后续的业务逻辑操做(好比操做db或者读取server上的文件),将操做结果返回给请求端。html

上面的一系列操做,好比 java

  1. 当用户输入密码(是否要加密,好比salt等), node

  2. 密码传输(https?若是是在windows上桌面请求怎么保证传输的安全性?), python

  3. nodejs server是否要解密(好比加 salt的密码?),在作 ladp验证?仍是 ladp支持加 salt的验证?git

11. 下面的文章不能回答上面的问题,文章中c#举例,但至少是对 密码加密有个基础或者概念性的了解程序员

http://www.cnblogs.com/leoo2sk/archive/2010/10/01/hash-and-encrypt.html
github


2010-10-01 00:09 by T2噬菌体, 44948 阅读, 46 评论, 收藏编辑算法

0、摘要

      今天看到吉日嘎拉一篇关于管理软件中信息加密和安全的文章,感受很是有实际意义。文中做者从实践经验出发,讨论了信息管理软件中如何经过哈希和加密进行数据保护。可是从文章评论中也能够看出不少朋友对这个方面一些基本概念比较模糊,这样就容易“照葫芦画瓢”,不能根据自身具体状况灵活选择和使用各类哈希和加密方式。本文不对哈希和加密作过于深刻的讨论,而是对哈希和加密的基本概念和原理进行阐述、比较,并结合具体实践说明如何选择哈希和加密算法、如何提升安全性等问题,使朋友们作到“知其然,知其因此然”,这样就能经过分析具体状况,灵活运用哈希和加密保护数据。数据库

一、哈希(Hash)与加密(Encrypt)的区别

      在本文开始,我须要首先从直观层面阐述哈希(Hash)加密(Encrypt)的区别,由于我见过不少朋友对这两个概念不是很清晰,容易混淆二者。而正确区别二者是正确选择和使用哈希与加密的基础。

      归纳来讲,哈希(Hash)是将目标文本转换成具备相同长度的、不可逆的杂凑字符串(或叫作消息摘要),而加密(Encrypt)是将目标文本转换成具备不一样长度的、可逆的密文。

      具体来讲,二者有以下重要区别:

      一、哈希算法每每被设计成生成具备相同长度的文本,而加密算法生成的文本长度与明文自己的长度有关。

      例如,设咱们有两段文本:“Microsoft”和“Google”。二者使用某种哈希算法获得的结果分别为:“140864078AECA1C7C35B4BEB33C53C34”和“8B36E9207C24C76E6719268E49201D94”,而使用某种加密算法的到的结果分别为“Njdsptpgu”和“Hpphmf”。能够看到,哈希的结果具备相同的长度,而加密的结果则长度不一样。实际上,若是使用相同的哈希算法,不论你的输入有多么长,获得的结果长度是一个常数,而加密算法每每与明文的长度成正比。

      二、哈希算法是不可逆的,而加密算法是可逆的。

      这里的不可逆有两层含义,一是“给定一个哈希结果R,没有方法将E转换成原目标文本S”,二是“给定哈希结果R,即便知道一段文本S的哈希结果为R,也不能断言当初的目标文本就是S”。其实稍微想一想就知道,哈希是不可能可逆的,由于若是可逆,那么哈希就是世界上最强悍的压缩方式了——能将任意大小的文件压缩成固定大小。

      加密则不一样,给定加密后的密文R,存在一种方法能够将R肯定的转换为加密前的明文S。

      这里先从直观层面简单介绍二者的区别,等下文从数学角度对二者作严谨描述后,读者朋友就知道为何会有这两个区别了。

二、哈希(Hash)与加密(Encrypt)的数学基础

      从数学角度讲,哈希和加密都是一个映射。下面正式定义二者:

      一个哈希算法是一个多对一映射,给定目标文本S,H能够将其惟一映射为R,而且对于全部S,R具备相同的长度。因为是多对一映射,因此H不存在逆映射

使得R转换为惟一的S。

      一个加密算法是一个一一映射,其中第二个参数叫作加密密钥,E能够将给定的明文S结合加密密钥Ke惟一映射为密文R,而且存在另外一个一一映射,能够结合Kd将密文R惟一映射为对应明文S,其中Kd叫作解密密钥。

      下图是哈希和加密过程的图示:

      有了以上定义,就很清楚为何会存在上文提到的两个区别了。因为哈希算法的定义域是一个无限集合,而值域是一个有限集合,将无限集合映射到有限集合,根据“鸽笼原理(Pigeonhole principle)”,每一个哈希结果都存在无数个可能的目标文本,所以哈希不是一一映射,是不可逆的。

      而加密算法是一一映射,所以理论上来讲是可逆的。

      可是,符合上面两个定义的映射仅仅能够被叫作哈希算法和加密算法,但未必是好的哈希和加密,好的哈希和加密每每须要一些附加条件,下面介绍这些内容。

      一个设计良好的哈希算法应该很难从哈希结果找到哈希目标文本的碰撞(Collision)。那么什么是碰撞呢?对于一个哈希算法H,若是,则S1和S2互为碰撞。关于为何好的哈希须要难以寻找碰撞,在下面讲应用的时候会详解。另外,好的哈希算法应该对于输入的改变极其敏感,即便输入有很小的改动,如一亿个字符变了一个字符,那么结果应该大相径庭。这就是为何哈希能够用来检测软件的完整性。

      一个设计良好的加密算法应该是一个“单向陷门函数(Trapdoor one-way function)”,单向陷门函数的特色是通常状况下即便知道函数自己也很难将函数的值转换回函数的自变量,具体到加密也就是说很难从密文获得明文,虽然从理论上这是可行的,而“陷门”是一个特殊的元素,一旦知道了陷门,则这种逆转换则很是容易进行,具体到加密算法,陷门就是密钥。

      顺便提一句,在加密中,应该保密的仅仅是明文和密钥。也就是说咱们一般假设攻击者对加密算法和密文了如指掌,所以加密的安全性应该仅仅依赖于密钥而不是依赖于假设攻击者不知道加密算法。

三、哈希(Hash)与加密(Encrypt)在软件开发中的应用

      哈希与加密在现代工程领域应用很是普遍,在计算机领域也发挥了很大做用,这里咱们仅仅讨论在日常的软件开发中最多见的应用——数据保护。

      所谓数据保护,是指在数据库被非法访问的状况下,保护敏感数据不被非法访问者直接获取。这是很是有现实意义的,试想一个公司的安保系统数据库服务器被入侵,入侵者得到了全部数据库数据的查看权限,若是管理员的口令(Password)被明文保存在数据库中,则入侵者能够进入安保系统,将整个公司的安保设施关闭,或者删除安保系统中全部的信息,这是很是严重的后果。可是,若是口令通过良好的哈希或加密,使得入侵者没法得到口令明文,那么最多的损失只是被入侵者看到了数据库中的数据,而入侵者没法使用管理员身份进入安保系统做恶。

3.一、哈希(Hash)与加密(Encrypt)的选择

      要实现上述的数据保护,能够选择使用哈希或加密两种方式。那么在何时该选择哈希、何时该选择加密呢?

      基本原则是:若是被保护数据仅仅用做比较验证,在之后不须要还原成明文形式,则使用哈希;若是被保护数据在之后须要被还原成明文,则须要使用加密。

      例如,你正在作一个系统,你打算当用户忘记本身的登陆口令时,重置此用户口令为一个随机口令,然后将此随机口令发给用户,让用户下次使用此口令登陆,则适合使用哈希。实际上不少网站都是这么作的,想一想你之前登陆过的不少网站,是否是当你忘记口令的时候,网站并非将你忘记的口令发送给你,而是发送给你一个新的、随机的口令,而后让你用这个新口令登陆。这是由于你在注册时输入的口令被哈希后存储在数据库里,而哈希算法不可逆,因此即便是网站管理员也不可能经过哈希结果复原你的口令,而只能重置口令。

      相反,若是你作的系统要求在用户忘记口令的时候必须将原口令发送给用户,而不是重置其口令,则必须选择加密而不是哈希。

3.二、使用简单的一次哈希(Hash)方法进行数据保护

      首先咱们讨论使用一次哈希进行数据保护的方法,其原理以下图所示:

      对上图我想已无需多言,不少朋友应该使用过相似的哈希方法进行数据保护。当前最经常使用的哈希算法是MD5SHA1,下面给出在.NET平台上用C#语言实现MD5和SHA1哈希的代码,因为.NET对于这两个哈希算法已经进行很很好的封装,所以咱们没必要本身实现其算法细节,直接调用相应的库函数便可(实际上MD5和SHA1算法都十分复杂,有兴趣的能够参考维基百科)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using  System;
using  System.Web.Security;
 
namespace  HashAndEncrypt
{
     /// <summary>
     /// 哈希(Hash)工具类
     /// </summary>
     public  sealed  class  HashHelper
     {
         /// <summary>
         /// 使用MD5算法进行哈希
         /// </summary>
         /// <param name="source">源字串</param>
         /// <returns>杂凑字串</returns>
         public  static  string  MD5Hash( string  source)
         {
             return  FormsAuthentication.HashPasswordForStoringInConfigFile(source, "MD5" );
         }
 
         /// <summary>
         /// 使用SHA1算法进行哈希
         /// </summary>
         /// <param name="source">源字串</param>
         /// <returns>杂凑字串</returns>
         public  static  string  SHA1Hash( string  source)
         {
             return  FormsAuthentication.HashPasswordForStoringInConfigFile(source, "SHA1" );
         }
     }
}

3.三、对简单哈希(Hash)的攻击

      下面咱们讨论上述的数据保护方法是否安全。

      对于哈希的攻击,主要有寻找碰撞法穷举法

      先来讲说寻找碰撞法。从哈希自己的定义和上面的数据保护原理图能够看出,若是想非法登陆系统,不必定非要获得注册时的输入口令,只要能获得一个注册口令的碰撞便可。所以,若是能从杂凑串中分析出一个口令的碰撞,则大功告成。

      不过个人意见是,对这种攻击大可没必要担忧,由于目前对于MD5和SHA1并不存在有效地寻找碰撞方法。虽然我国杰出的数学家王小云教授曾经在国际密码学会议上发布了对于MD5和SHA1的碰撞寻找改进算法,但这种方法和不少人口中所说的“破解”相去甚远,其理论目前仅具备数学上的意义,她将破解MD5的预期步骤数从2^80降到了2^69,虽然从数学上下降了好几个数量级,但2^69对于实际应用来讲仍然是一个天文数字,就比如之前须要一亿年,如今须要一万年同样。

      不过这并不意味着使用MD5或SHA1后就万事大吉了,由于还有一种对于哈希的攻击方法——穷举法。通俗来讲,就是在一个范围内,如从000000到999999,将其中全部值一个一个用相同的哈希算法哈希,而后将结果和杂凑串比较,若是相同,则这个值就必定是源字串或源字串的一个碰撞,因而就能够用这个值非法登陆了。

      例如,下文是对MD5的穷举攻击的代码(设攻击范围为000000到999999):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using  System;
using  System.Web.Security;
 
namespace  HashAndEncrypt
{
     /// <summary>
     /// MD5攻击工具类
     /// </summary>
     public  sealed  class  MD5AttackHelper
     {
         /// <summary>
         /// 对MD5进行穷举攻击
         /// </summary>
         /// <param name="hashString">杂凑串</param>
         /// <returns>杂凑串的源串或源串碰撞(攻击失败则返回null)</returns>
         public  static  string  AttackMD5( string  hashString)
         {
             for  ( int  i = 0; i <= 999999; i++)
             {
                 string  testString = i.ToString();
                 while  (testString.Length < 6)
                     testString = "0"  + testString;
 
                 if  (FormsAuthentication.HashPasswordForStoringInConfigFile(testString, "MD5" ) == hashString)
                     return  testString;
             }
 
             return  null ;
         }
     }
}

      这种看似笨拙的方法,在现实中爆发的能量倒是惊人的,目前几乎全部的MD5破解机或MD5在线破解都是用这种穷举法,但就是这种“笨”方法,却成功破解出不少哈希串。纠其原因,就是至关一部分口令是很是简单的,如“123456”或“000000”这种口令还有不少人在用,能够看出,穷举法是否能成功很大程度上取决于口令的复杂性。由于穷举法扫描的区间每每是单字符集、规则的区间,或者由字典数据进行组合,所以,若是使用复杂的口令,例如“ASDF#$%uiop.8930”这种变态级口令,穷举法就很难奏效了。

3.四、对一次哈希(Hash)的改进——多重混合哈希(Hash)

      上面说过,若是口令过于简单,则使用穷举法能够颇有效地破解出一次哈希后的杂凑串。若是不想这样,只有让用户使用复杂口令,可是,不少时候咱们并不能强迫用户,所以,咱们须要想一种办法,即便用户使用诸如“000000”这种简单密码,也令穷举法难奏效。其中一种办法就是使用多重哈希,所谓多重哈希就是使用不一样的哈希函数配合自定义的Key对口令进行屡次哈希,若是Key很复杂,那么穷举法将变得异常艰难。

      例如,若是使用下面的混合公式进行哈希:

      若是将Key设为一个极为复杂的字符串,那么在攻击者不知道Key的状况下,几乎没法经过穷举法破解。由于即便S很简单,可是Key的MD5值几乎是没法在合理时间内穷举完的。下面是这种多重混合哈希的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using  System;
using  System.Web.Security;
 
namespace  HashAndEncrypt
{
     /// <summary>
     /// 多重混合哈希工具类
     /// </summary>
     public  sealed  class  HashHelper
     {
         private  static  readonly  String hashKey = "qwer#&^Buaa06" ;
         /// <summary>
         /// 对敏感数据进行多重混合哈希
         /// </summary>
         /// <param name="source">待处理明文</param>
         /// <returns>Hasn后的数据</returns>
         public  static  String Hash(String source)
         {
             String hashCode = FormsAuthentication.HashPasswordForStoringInConfigFile(source, "MD5" ) +
                               FormsAuthentication.HashPasswordForStoringInConfigFile(hashKey, "MD5" );
             return  FormsAuthentication.HashPasswordForStoringInConfigFile(hashCode, "SHA1" );
         }
     }
}

3.五、使用加密(Encrypt)方法进行数据保护

      加密方法若是用于口令保护的话,与上述哈希方法的流程基本一致,只是在须要时,能够使用解密方法获得明文。关于加密自己是一个很是庞大的系统,而对于加密算法的攻击更是能够写好几本书了,因此这里从略。下面只给出使用C#进行DES加密和解密的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using  System;
using  System.Security.Cryptography;
using  System.Text;
using  System.Web.Security;
 
namespace  HashAndEncrypt
{
     /// <summary>
     /// 工具类,封装了加解密相关操做
     /// </summary>
     public  sealed  class  EncryptHelper
     {
         private  static  readonly  Byte[] DesKey = {5, 7, 8, 9, 0, 2, 1, 6};
         private  static  readonly  Byte[] DesVi = { 6, 9, 8, 5, 1, 6, 2, 8 };
         /// <summary>
         /// 使用DES算法加密数据
         /// </summary>
         /// <param name="data">待加密数据</param>
         /// <returns>密文</returns>
         public  static  String Encrypt(String data)
         {
             DESCryptoServiceProvider des = new  DESCryptoServiceProvider();
             Encoding utf = new  UTF8Encoding();
             ICryptoTransform encryptor = des.CreateEncryptor(DesKey, DesVi);
 
             byte [] bData = utf.GetBytes(data);
             byte [] bEnc = encryptor.TransformFinalBlock(bData, 0, bData.Length);
             return  Convert.ToBase64String(bEnc);
         }
 
         /// <summary>
         /// 使用DES算法解密数据
         /// </summary>
         /// <param name="data">待解密数据</param>
         /// <returns>明文</returns>
         public  static  String Decrypt(String data)
         {
             DESCryptoServiceProvider des = new  DESCryptoServiceProvider();
             Encoding utf = new  UTF8Encoding();
             ICryptoTransform decryptor = des.CreateDecryptor(DesKey, DesVi);
 
             byte [] bEnc = Convert.FromBase64String(data);
             byte [] bDec = decryptor.TransformFinalBlock(bEnc, 0, bEnc.Length);
             return  utf.GetString(bDec);
         }
     }
}

四、总结

      密码学自己是一个很是深奥的数学分支,对于普通开发者,不须要了解过于深刻的密码学知识。本文仅仅讲述哈希与加密的基础内容,并对二者作了比较,帮助读者明晰概念,另外,对一些实际应用状况进行了简单的讨论。但愿本文对你们有所帮助。看了下时间,零点刚过,祝你们十一快乐!玩得开心!

__________________________________________________________________________________________________________

__________________________________________________________________________________________________________

12: 【转载】 加盐密码哈希:如何正确使用

http://blog.jobbole.com/61872/

若是你是Web开发者,你极可能须要开发一个用户帐户系统。这个系统最重要的方面,就是怎样保护用户的密码。存放账号的数据库常常成为入侵的目标,因此你必须作点什么来保护密码,以防网站被攻破时发生危险。最好的办法就是对密码进行加盐哈希,这篇文章将介绍它是如何作到这点。

在对密码进行哈希加密的问题上,人们有许多争论和误解,这大概是因为网络上普遍的误传吧。密码哈希是一件很是简单的事情,可是依然有不少人理解错误了。本文阐述的并非进行密码哈希惟一正确的方法,可是会告诉你为何这样是正确的。

郑重警告:若是你在试图编写本身的密码哈希代码,赶忙停下来!那太容易搞砸了。即便你受过密码学的高等教育,也应该遵从这个警告。这是对全部人说的:不要本身写加密函数!安全存储密码的难题如今已经被解决了,请使用phpass或者本文给出的一些源代码。

若是由于某些缘由你忽视了上面那个红色警告,请翻回去好好读一遍,我是认真的。这篇文章的目的不是教你研究出本身的安全算法,而是讲解为何密码应该被这样储存。

下面一些连接能够用来快速跳转到本文的各章节。

  1. 为何密码须要进行哈希?
  2. 如何破解哈希加密
  3. 加盐
  4. 无效的哈希方法
  5. 恰当使用哈希加密
  6. 常见问题

这里也给出了一些基于BSD许可的哈希函数源代码:

为何密码须要进行哈希?


hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash("waltz") = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542

哈希算法是一个单向函数。它能够将任何大小的数据转化为定长的“指纹”,而且没法被反向计算。另外,即便数据源只改动了一丁点,哈希的结果也会彻底不一样(参考上面的例子)。这样的特性使得它很是适合用于保存密码,由于咱们须要加密后的密码没法被解密,同时也能保证正确校验每一个用户的密码。

在基于哈希加密的帐户系统中,一般用户注册和认证的流程是这样的:

  1. 用户注册一个账号
  2. 密码通过哈希加密储存在数据库中。只要密码被写入磁盘,任什么时候候都不容许是明文
  3. 当用户登陆的时候,从数据库取出已经加密的密码,和通过哈希的用户输入进行对比
  4. 若是哈希值相同,用户得到登入受权,不然,会被告知输入了无效的登陆信息
  5. 每当有用户尝试登陆,以上两步都会重复

在第4步中,永远不要告诉用户究竟是用户名错了,仍是密码错了。只须要给出一个大概的提示,好比“无效的用户名或密码”。这能够防止攻击者在不知道密码的状况下,枚举出有效的用户名。

须要提到的是,用于保护密码的哈希函数和你在数据结构中学到的哈希函数是不一样的。好比用于实现哈希表这之类数据结构的哈希函数,它们的目标是快速查找,而不是高安全性。只有加密哈希函数才能用于保护密码,例如SHA256,SHA512,RipeMD和WHIRLPOOL。

也许你很容易就认为只须要简单地执行一遍加密哈希函数,密码就能安全,那么你大错特错了。有太多的办法能够快速地把密码从简单哈希值中恢复出来,但也有不少比较容易实现的技术能使攻击者的效率大大下降。黑客的进步也在激励着这些技术的进步,好比这样一个网站:你能够提交一系列待破解的哈希值,而且在不到1秒的时间内获得告终果。显然,简单哈希加密并不能知足咱们对安全性的需求。

那么下一节会讲到几种经常使用的破解简单哈希加密的办法。

如何破解哈希加密


字典攻击和暴力攻击

Dictionary Attack
Trying apple : failed
Trying blueberry : failed
Trying justinbeiber : failed
...
Trying letmein : failed

Trying s3cr3t : success!
Brute Force Attack
Trying aaaa : failed
Trying aaab : failed
Trying aaac : failed
...
Trying acdb : failed

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!

查表法对于破解一系列算法相同的哈希值有着无与伦比的效率。主要的思想就是预计算密码字典中的每一个密码,而后把哈希值和对应的密码储存到一个用于快速查询的数据结构中。一个良好的查表实现能够每秒进行数百次哈希查询,即便表中储存了几十亿个哈希值。

若是你想更好地体验查表法的速度,尝试使用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值的彩虹表已经出现了。

下面咱们会讲到一种让查表法和彩虹表都失去做用的技术,叫作加盐

加盐


hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hello" + "QxLUF1bgIAdeQX") = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash("hello" + "bv5PehSMfV11Cd") = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash("hello" + "YYLmfY6IehjZMQ") = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007

查表法和彩虹表只有在全部密码都以相同方式进行哈希加密时才有效。若是两个用户密码相同,那么他们密码的哈希值也是相同的。咱们能够经过“随机化”哈希来阻止这类攻击,因而当相同的密码被哈希两次以后,获得的值就不相同了。

好比能够在密码中混入一段“随机”的字符串再进行哈希加密,这个被字符串被称做盐值。如同上面例子所展现的,这使得同一个密码每次都被加密为彻底不一样的字符串。为了校验密码是否正确,咱们须要储存盐值。一般和密码哈希值一块儿存放在帐户数据库中,或者直接存为哈希字符串的一部分。

盐值并不须要保密??,因为随机化了哈希值,查表法、反向查表法和彩虹表都再也不有效。攻击者没法确知盐值,因而就不能预先计算出一个查询表或者彩虹表。这样每一个用户的密码都混入不一样的盐值后再进行哈希,所以反向查表法也变得难以实施。

我理解这里不须要保密的意思是,并非能够把 盐值 让攻击者知道,

下面讲讲咱们在实现加盐哈希的过程当中一般会犯哪些错误

错误一:短盐值和盐值重复


最多见的错误就是在屡次哈希加密中使用相同的盐值或者过短的盐值。

盐值重复

每次哈希加密都使用相同的盐值是很容易犯的一个错误,这个盐值要么被硬编码到程序里,要么只在第一次使用时随机得到。这样加盐的方式是作无用功,由于两个相同的密码依然会获得相同的哈希值。攻击者仍然能够使用反向查表法对每一个值进行字典攻击,只须要把盐值应用到每一个猜想的密码上再进行哈希便可。若是盐值被硬编码到某个流行的软件里,能够专门为这个软件制做查询表和彩虹表,那么破解它生成的哈希值就变得很简单了。

用户建立帐户或每次修改密码时,都应该从新生成新的盐值进行加密

短盐值

若是盐值过短,攻击者能够构造一个查询表包含全部可能的盐值。以只有3个ASCII字符的盐值为例,一共有95x95x95=857,375种可能。这看起来不少,可是若是对于每一个盐值查询表只包含1MB最多见的密码,那么总共只须要837GB的储存空间。一个不到100美圆的1000GB硬盘就能解决问题。
一样地,用户名也不该该被用做盐值。尽管在一个网站中用户名是惟一的,可是它们是可预测的,而且常常重复用于其余服务中。攻击者能够针对常见用户名构建查询表,而后对用户名盐值哈希发起进攻。

为了使攻击者没法构造包含全部可能盐值的查询表,盐值必须足够长。一个好的作法是使用和哈希函数输出的字符串等长的盐值,好比SHA256算法的输出是256bits(32 bytes),那么盐值也至少应该是32个随机字节。

错误二:两次哈希和组合哈希函数


(译注:此节标题原文中的Wacky Hash Functions直译是古怪的哈希函数,大概是因为做者不承认这种组合多种哈希函数的作法,为了便于理解,本文仍是翻译为组合哈希函数)

这节讲述了另外一种对密码哈希的误解:使用组合哈希函数。人们常常情不自禁地认为将不一样的哈希函数组合起来,结果会更加安全。实际上这样作几乎没有好处,仅仅形成了函数之间互相影响的问题,甚至有时候会变得更加不安全。永远不要尝试发明本身的加密方法,只需只用已经被设计好的标准算法。有的人会说使用多种哈希函数会使计算更慢,从而破解也更慢,可是还有其余的办法能更好地减缓破解速度,后面会提到的。

这里有些低端的组合哈希函数,我在网上某些论坛看到它们被推荐使用:

  • 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)))

不要使用其中任何一种。

注意:这节内容是有争议的。我已经收到的大量的邮件,为组合哈希函数而辩护。他们的理由是若是攻击者不知道系统使用的哪一种哈希函数,那么也就很难预先为这种组合构造出彩虹表,因而破解起来会花费更多的时间。

诚然,攻击者在不知道加密算法的时候是没法发动攻击的,可是不要忘了Kerckhoffs’s principle,攻击者一般很容易就能拿到源码(尤为是那些免费或开源的软件)。经过系统中取出的一些密码-哈希值对应关系,很容易反向推导出加密算法。破解组合哈希函数确实须要更多时间,但也只是受了一点能够确知的因素影响。更好的办法是使用一个很难被并行计算出结果的迭代算法,而后增长适当的盐值防止彩虹表攻击。

固然你实在想用“标准的”组合哈希函数,好比HMAC,也是能够的。但若是只是为了使破解起来更慢,那么先读读下面讲到的密钥扩展。

创造新的哈希函数可能带来安全问题,构造哈希函数的组合又可能带来函数间互相影响的问题,它们带来的一丁点好处和这些比起来真是微不足道。显然最好的作法是使用标准的、通过完整测试的算法。

哈希碰撞


哈希函数将任意大小的数据转化为定长的字符串,所以其中必定有些输入通过哈希计算以后获得了相同的结果。加密哈希函数的设计就是为了使这样的碰撞尽量难以被发现。随着时间流逝,密码学家发现攻击者愈来愈容易找到碰撞了,最近的例子就是MD5算法的碰撞已经肯定被发现了。

碰撞攻击的出现代表极可能有一个和用户密码不一样的字符串却和它有着相同的哈希值。然而,即便在MD5这样脆弱的哈希函数中找到碰撞也须要耗费大量的计算,所以这样的碰撞“意外地”在实际中出现的可能性是很低的。因而站在实用性的角度上能够这么说,加盐MD5和加盐SHA256的安全性是同样的。不过可能的话,使用自己更安全的哈希函数老是好的,好比SHA25六、SHA5十二、RipeMD或者WHIRLPOOL

正确的作法:恰当使用哈希加密


本节会准确讲述应该如何对密码进行哈希加密。其中第一部分介绍最基本的要素,也是在哈希加密中必定要作到的;后面讲解怎样在这个基础上进行扩展,使得加密更难被破解。

基本要素:加盐哈希

忠告:你不只仅要用眼睛看文章,更要本身动手去实现后面讲到的“让密码更难破解:慢哈希函数”。

在前文中咱们已经看到,利用查表法和彩虹表,普通哈希加密是多么容易被恶意攻击者破解,也知道了能够经过随机加盐的办法也解决这个问题。那么到底应该使用怎样的盐值呢,又如何把它混入密码?

盐值应该使用基于加密的伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)来生成。CSPRNG和普通的随机数生成器有很大不一样,如C语言中的rand()函数。物如其名,CSPRNG专门被设计成用于加密,它能提供高度随机和没法预测的随机数。咱们显然不但愿本身的盐值被猜想到,因此必定要使用CSPRNG。下面的表格列出了当前主流编程语言中的CSPRNG方法:

Platform CSPRNG
PHP mcrypt_create_ivopenssl_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

对于每一个用户的每一个密码,盐值都应该是独一无二的。每当有新用户注册或者修改密码,都应该使用新的盐值进行加密。而且这个盐值也应该足够长,使得有足够多的盐值以供加密。一个好的标准的是:盐值至少和哈希函数的输出同样长;盐值应该被储存和密码哈希一块儿储存在帐户数据表中。

存储密码的步骤

  1. 使用CSPRNG生成一个长度足够的盐值
  2. 将盐值混入密码,并使用标准的加密哈希函数进行加密,如SHA256
  3. 把哈希值和盐值一块儿存入数据库中对应此用户的那条记录

校验密码的步骤

  1. 从数据库取出用户的密码哈希值和对应盐值
  2. 将盐值混入用户输入的密码,而且使用一样的哈希函数进行加密
  3. 比较上一步的结果和数据库储存的哈希值是否相同,若是相同那么密码正确,反之密码错误

文章最后有几个加盐密码哈希的代码实现,分别使用了PHP、C#、Java和Ruby。

在Web程序中,永远在服务器端进行哈希加密

若是你正在开发一个Web程序,你可能会疑惑到底在哪进行加密。是使用JavaScript在用户的浏览器上操做呢,仍是将密码“裸体”传送到服务器再进行加密?

即便浏览器端用JavaScript加密了,你仍然须要在服务端再次进行加密。试想有个网站在浏览器将密码通过哈希后传送到服务器,那么在认证用户的时候,网站收到哈希值和数据库中的值进行比对就能够了。这看起来比只在服务器端加密安全得多,由于至始至终没有将用户的密码明文传输,但实际上不是这样。

问题在于,从客户端来看,通过哈希的密码逻辑上成为用户真正的密码。为了经过服务器认证,用户只须要发送密码的哈希值便可。若是有坏小子获取了这个哈希值,他甚至能够在不知道用户密码的状况经过认证。更进一步,若是他用某种手段入侵了网站的数据库,那么不须要去猜解任何人的密码,就能够随意使用每一个人的账号登陆。

这并非说你不该该在浏览器端进行加密,可是若是你这么作了,必定要在服务端再次加密。在浏览器中进行哈希加密是个好想法,不过实现的时候注意下面几点:

• 客户端密码哈希并不能代替HTTPS(SSL/TLS)。若是浏览器和服务器之间的链接是不安全的,那么中间人攻击能够修改JavaScript代码,删除加密函数,从而获取用户密码。

• 有些浏览器不支持JavaScript,也有的用户禁用了浏览器的JavaScript功能。为了最好的兼容性,你的程序应该检测JavaScript是否可用,若是答案为否,须要在服务端模拟客户端的加密。

• 客户端哈希一样须要加盐,很显然的办法就是向服务器请求用户的盐值,可是不要这么作。由于这给了坏蛋一个机会,可以在不知道密码的状况下检测用户名是否有效。既然你已经在服务端对密码进行了加盐哈希,那么在客户端把用户名(或邮箱)加上网站特有的字符串(如域名)做为盐值是可行的。

让密码更难破解:慢哈希函数

加盐使攻击者没法采用特定的查询表和彩虹表快速破解大量哈希值,可是却不能阻止他们使用字典攻击或暴力攻击。高端的显卡(GPU)和定制的硬件能够每秒进行数十亿次哈希计算,所以这类攻击依然能够很高效。为了下降攻击者的效率,咱们能够使用一种叫作密钥扩展的技术。

这种技术的思想就是把哈希函数变得很慢,因而即便有着超高性能的GPU或定制硬件,字典攻击和暴力攻击也会慢得让攻击者没法接受。最终的目标是把哈希函数的速度降到足以让攻击者望而却步,但形成的延迟又不至于引发用户的注意。

密钥扩展的实现是依靠一种CPU密集型哈希函数。不要尝试本身发明简单的迭代哈希加密,若是迭代不够多,是能够被高效的硬件快速并行计算出来的,就和普通哈希同样。应该使用标准的算法,好比PBKDF2或者bcrypt这里能够找到PBKDF2在PHP上的一种实现。

这类算法使用一个安全因子或迭代次数做为参数,这个值决定了哈希函数会有多慢。对于桌面软件或者手机软件,获取参数最好的办法就是执行一个简短的性能基准测试,找到使哈希函数大约耗费0.5秒的值。这样,你的程序就能够尽量保证安全,而又不影响到用户体验。

若是你在一个Web程序中使用密钥扩展,记得你须要额外的资源处理大量认证请求,而且密钥扩展也使得网站更容易遭受拒绝服务攻击(DoS)。但我依然推荐使用密钥扩展,不过把迭代次数设定得低一点,你应该基于认证请求最高峰时的剩余硬件资源来计算迭代次数。要求用户每次登陆时输入验证码能够消除拒绝服务的威胁。另外,必定要把你的系统设计为迭代次数可随时调整的。

若是你担忧计算量带来的负载,但又想在Web程序中使用密钥扩展,能够考虑在浏览器中用JavaScript完成。Stanford JavaScript Crypto Library里包含了PBKDF2的实现。迭代次数应该被设置到足够低,以适应速度较慢的客户端,好比移动设备。同时当客户端不支持JavaScript的时候,服务端应该接手计算。客户端的密钥扩展并不能免除服务端进行哈希加密的职责,你必须对客户端传来的哈希值再次进行哈希加密,就像对付一个普通密码同样。

没法破解的哈希加密:密钥哈希和密码哈希设备

只要攻击者能够检测对一个密码的猜想是否正确,那么他们就能够进行字典攻击或暴力攻击。所以下一步就是向哈希计算中增长一个密钥,只有知道这个密钥的人才能校验密码。有两种办法能够实现:将哈希值加密,好比使用AES算法;将密钥包含到哈希字符串中,好比使用密钥哈希算法HMAC

听起来很简单,作起来就不同了。这个密钥须要在任何状况下都不被攻击者获取,即便系统由于漏洞被攻破了。若是攻击者获取了进入系统的最高权限,那么不论密钥被储存在哪,他们均可以窃取到。所以密钥须要储存在外部系统中,好比另外一个用于密码校验的物理服务器,或者一个关联到服务器的特制硬件,如YubiHSM

我强烈推荐大型服务(10万用户以上)使用这类办法,由于我认为面对如此多的用户是有必要的。

若是你难以负担多个服务器或专用的硬件,仍然有办法在一个普通Web服务器上利用密钥哈希技术。大部分针对数据库的入侵都是因为SQL注入攻击,所以不要给攻击者进入本地文件系统的权限(禁止数据库服务访问本地文件系统,若是它有这个功能的话)。这样一来,当你随机生成一个密钥存到经过Web程序没法访问的文件中,而后混入加盐哈希,获得的哈希值就再也不那么脆弱了,即使这时数据库遭受了注入攻击。不要把将密钥硬编码到代码里,应该在安装时随机生成。这固然不如独立的硬件系统安全,由于若是Web程序存在SQL注入点,那么可能还存在其余一些问题,好比本地文件包含漏洞(Local File Inclusion),攻击者能够利用它读取本地密钥文件。不管如何,这个措施比没有好。

请注意密钥哈希不表明无需进行加盐。高明的攻击者早晚会找到办法窃取密钥,所以依然对密码哈希进行加盐和密钥扩展很重要。

其余安全措施


哈希加密能够在系统发生入侵时保护密码,但这并不能使整个程序更加安全。首先还有不少事情须要作,来保证密码哈希(和其余用户数据)不被窃取。

即便经验丰富的开发者也须要额外学习安全知识,才能写出安全的程序。这里有个关于Web程序漏洞的资源:The Open Web Application Security Project (OWASP),还有一个很好的介绍:OWASP Top Ten Vulnerability List。除非你了解列表中全部的漏洞,才能尝试编写一个处理敏感数据的Web程序。雇主也有责任保证他全部的开发人员都有资质编写安全的程序。

对你的程序进行第三方“渗透测试”是一个不错的选择。最好的程序员也可能犯错,所以有一个安全专家审查你的代码寻找潜在的漏洞是有意义的。找寻值得信赖的机构(或招聘人员)来对你的代码进行审查。安全审查应该从编码的初期就着手进行,一直贯穿整个开发过程。

监控你的网站来发现入侵行为也是很重要的,我推荐至少雇佣一我的全职负责监测和处理安全隐患。若是有个漏洞没被发现,攻击者可能经过网站利用恶意软件感染访问者,所以检测漏洞而且及时应对是十分重要的

常见问题


我应该使用什么哈希算法?

应该使用:

  • 本文末尾的PHP source code, Java source code, C# source code or the Ruby source code
  • OpenWall的Portable PHP password hashing framework
  • 任何先进的、被良好测试过的哈希加密算法,好比SHA256,SHA512,RipeMD,WHIRLPOOL,SHA3等等
  • 设计良好的密钥扩展算法,如PBKDF2bcryptscrypt
  • 安全的crypt()版本($2y$,$5$,$6$)

不要使用:

  • 过期的函数,好比MD5或SHA1
  • 不安全的crypt()版本($1$,$2$,$2x$,$3$)
  • 任何你本身设计的加密算法。只应该使用那些在公开领域中的,而且被密码学家完整测试过的技术

尽管尚未一种针对MD5或SHA1很是效率的攻击手段,可是它们太古老也被普遍地认为不足以胜任存储密码的工做(某种程度上甚至是错误的),所以我也不推荐使用它们。可是有个例外,PBKDF2中频繁地使用了SHA1做为它底层的哈希函数。

当用户忘记密码的时候,怎样进行重置?

我我的的观点是,当前全部普遍使用的密码重置机制都是不安全的。若是你对安全性有极高的要求,好比一个加密服务,那么不要容许用户重置密码。
大多数网站向那些忘记密码的用户发送电子邮件来进行身份认证。首先,须要随机生成一个一次性的令牌,它直接关联到用户的帐户。而后将这个令牌混入一个重置密码的连接中,发送到用户的电子邮箱。最后当用户点击这个包含有效令牌的连接时,提示他们能够设置新的密码。要确保这个令牌只对一个帐户有效,以防攻击者从邮箱获取到令牌后,用来重置其余用户的密码。

令牌必须在15分钟内使用,而且一旦被使用就当即失效。当用户从新请求令牌时,或用户登陆成功时(说明他还记得密码),使原令牌失效也是一个好作法。若是一个令牌始终不过时,那么它一直能够用于入侵用户的账号。电子邮件(SMTP)是一个纯文本协议,而且网络上有不少恶意路由在截取邮件信息。在用户修改密码后,那些包含重置密码连接的邮件在很长一段时间内依然缺少保护。所以应该尽早使令牌过时,下降把用户信息暴露给攻击者的可能。

攻击者是能够篡改令牌的,因此不要把帐户信息和失效时间存储在里面。这些信息应该以不可猜解的二进制形式存在,而且只用来识别数据库中某条用户的记录。

永远不要经过电子邮件向用户发送新密码,同时也记得在用户重置密码的时候随机生成一个新的盐值用于加密,不要重复使用以前密码的那个盐值。

当帐户数据库被泄漏或入侵时,应该怎么作?

你首先须要作的,是查看系统被暴露到什么程度了,而后修复这个攻击者利用的漏洞。若是你没有应对入侵的经验,我强烈推荐雇一个第三方安全机构来作这件事。

将一个漏洞精心掩盖期待没有人能注意到,是否听起来很省事而又诱人呢?可是这样只会让你显得更糟糕,由于你在用户不知情的状况下,将他们的密码和我的信息暴露在危险之中。即便用户还没法理解到底发生了什么,你也应该尽快履行告知的义务。好比在首页放置一个连接,指向对此问题更详细的说明,可能的话还能够经过电子邮件告知用户目前的状况。

向你的用户说明你是如何保护他们的密码的——最好是使用了加盐哈希——即使如此恶意黑客也能使用字典攻击和暴力攻击。设想用户可能在不少服务中使用相同的密码,攻击者会用找到的密码去尝试登陆其余网站。提示你的用户应该修改全部类似的密码,不论它们被使用在哪一个服务上,而且强制用户下次登陆你的网站时修改密码。大部分用户会尝试将密码“修改”为和以前相同的以便记忆,你应该使用老密码的哈希值来确保用户没法这么作。

即便有加盐哈希的保护,攻击者也极可能快速破解其中一些脆弱的密码。为了减小攻击者使用的它们机会,你应该对这些密码的账号发送认证电子邮件,直到用户修改了密码。能够参考上一个问题,其中有一些实现电子邮件认证的要点。

另外也要告诉你的用户,网站到底储存了哪些我的信息。若是你的数据库中有用户的信用卡号,你应该指导用户检查本身近期的帐单,而且注销掉这张信用卡。

我应该使用什么样的密码规则?是否应该强制用户使用复杂的密码?

若是你的服务对安全性没有严格的要求,那么不要对用户进行限制。我推荐在用户输入密码的时候,页面上显示出密码强度,由用户本身决定须要多安全的密码。若是你的服务对安全有特殊的需求,那就应该强制用户输入长度至少为12个字符的密码,而且其中至少包括两个字母、两个数字和两个符号。

不要过于频繁地强制你的用户修改密码,最多6个月1次,由于那样作会使用户疲于选择一个强度足够好的密码。更好的作法是指导用户在他们感受密码可能泄漏的时候去主动修改,而且提示用户不要把密码告诉任何人。若是这是在商业环境中,鼓励你的员工利用工做时间熟记并使用他们的密码。

若是攻击者入侵了个人数据库,他们难道不能把其中的密码哈希替换为本身的值,而后登陆系统么?

固然能够,可是若是他已经入侵了你的数据库,那么极可能已经有权限访问你服务器上任何东西了,所以彻底不必登陆帐户去获取他想要的。对密码进行哈希加密的手段,(对网站而言)不是保护网站免受入侵,而是在入侵已经发生时保护数据库中的密码。

经过为数据库链接设置两种权限,能够防止密码哈希在遭遇注入攻击时被篡改。一种权限用于建立用户:它对用户表可读可写;另外一种用于用户登陆,它只能读用户表而不能写。

为何我非得用像HMAC那种特殊的算法?为何不能简单地把密钥混入密码?

像MD五、SHA1和SHA2这类哈希函数是基于Merkle–Damgård构造的,所以在长度扩展攻击面前很是脆弱。就是说若是已经知道一个哈希值H(X),对于任意的字符串Y,攻击者能够计算出H(pad(X) + Y)的值,而不须要知道X是多少,其中pad(X)是哈希函数的填充函数(padding function,好比MD5将数据每512bit分为一组,最后不足的将填充字节)。

在攻击者不知道密钥(key)的状况下,他仍然能够根据哈希值H(key + message)计算出H(pad(key + message) + extension)。若是这个哈希值用于身份认证,而且依靠其中的密钥来防止攻击者篡改消息,这个办法已经行不通了。由于攻击者无需知道密钥,也能构造出包含message + extension的一个有效的哈希值。

目前还不清楚攻击者可否用这个办法更快破解密码,可是因为这种攻击的出现,在密钥哈希中使用上述哈希函数已经被认为是差劲的实践了。也许某天高明的密码学家会发现一个利用长度扩展攻击的新思路,从而更快地破解密码,因此仍是使用HMAC吧。

盐值应该加到密码前面仍是后面?

都行,可是在一个程序中应该保持一致,以避免出现互操做方面的问题。目前看来加到密码以前是比较经常使用的作法。

为何本文中的代码在比较哈希值的时候,都是通过固定的时间才返回结果?

让比较过程耗费固定的时间能够保证攻击者没法对一个在线系统使用计时攻击,以此获取密码的哈希值,而后进行本地破解工做。

比较两个字节序列(字符串)的标准作法是,从第一字节开始,每一个字节逐一顺序比较。只要发现某字节不相同了,就能够当即返回“假”的结果。若是遍历整个字符串也没有找到不一样的字节,那么两个字符串就是相同的,而且返回“真”。这意味着比较字符串的耗时决定于两个字符串到底有多大的不一样。

举个例子,使用标准的方法比较“xyzabc”和“abcxyz”,因为第一个字符就不一样,不须要检查后面的内容就能够立刻返回结果。相反,若是比较“aaaaaaaaaaB”和“aaaaaaaaaaZ”,比较算法就须要遍历最后一位前全部的“a”,而后才能知道它们是不相同的。

假设攻击者妄图入侵一个在线系统,而且此系统限制了每秒只能尝试一次用户认证。还假设他已经知道了密码哈希全部的参数(盐值、哈希函数的类型等等),除了密码的哈希值和密码自己(显然啊,不然还破解个什么)。若是攻击者能精确测量在线系统耗时多久去比较他猜想的密码和真实密码,那么他就能使用计时攻击获取密码的哈希值,而后进行离线破解,从而绕过系统对认证频率的限制。

首先攻击者准备256个字符串,它们的哈希值的第一字节包含了全部可能的状况。而后用它们去系统中尝试登陆,并记录系统返回结果所消耗的时间,耗时最长的那个就是第一字节猜对的那个。接下来用一样的方式猜想第二字节、第三字节等等。直到攻击者获取了最够长的哈希值片断,最后只需在本身的机器上破解便可,彻底不受在线系统的限制。

乍看之下在网络上进行计时攻击是不可能作到的,然而有人已经实现了,并运用到实际中了。所以本文提供的代码才使用固定的时间去比较字符串,不论它们有多类似。

“慢比较”的代码是如何工做的?

上一个问题解释了为何“慢比较”是有必要的,如今来说解一下代码具体是怎么实现的。

代码中使用了异或运算符“^”(XOR)来比较两个整数是否相等,而不是“==”。当且仅当两位相等时,异或的结果才是0。由于0 XOR 0 = 0, 1 XOR 1 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1。应用到整数中每一位就是说,当且仅当字节两个整数各位都相等,结果才是0。

代码中的第一行,比较a.length和b.length,相同的话diff是0,不然diff非0。而后使用异或比较数组中各字节,而且将结果和diff求或。若是有任何一个字节不相同,diff就会变成非0的值。由于或运算没有“置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

弄这么麻烦干吗?

用户在你的网站上输入密码,说明他们相信你会保障密码的安全。若是你的数据库被黑了,又没有对用户密码加以保护,恶意黑客就能够使用这些密码去入侵用户在其余网站或服务的帐户(大部分人会在各处使用相同的密码)。这不只仅关乎你网站的安全,更关系到用户的。你须要对用户的安全负责。

PHP PBKDF2 密码哈希代码


下面是PBKDF2在PHP中一种安全的实现,你也能够在这个页面找到测试用例和基准测试的代码。

下载PasswordHash.php

若是你须要兼容的PHP和C#代码,点击这里

Java PBKDF2 密码哈希代码


下面是PBKDF2在Java中一种安全的实现。

下载PasswordHash.java

ASP.NET(C#) PBKDF2 密码哈希代码


下面是PBKDF2在ASP.NET(C#)中一种安全的实现。

下载PasswordHash.cs

若是你须要兼容的PHP和C#代码,点击这里

Ruby(on Rails) PBKDF2 密码哈希代码


下面是PBKDF2在Ruby(on Rails)中一种安全的实现。

下载PasswordHash.rb

相关文章
相关标签/搜索