Hashed password with salted value——利用salt对密码进行散列

来源:互联网 发布:星火网络电视 编辑:程序博客网 时间:2024/05/22 08:16

在没有学习web安全密码学部分的时候,我自己全部的密码都是同一个,而且是7位数字组成的。还好我本身没有什么财产好损失,不过都不会再犯这种愚蠢错误了。

     我们为什么需要对密码进行hash化,又为什么要在hash化之前给它加一个随机值?我想从一个例子分析起:我看过一个黑客的文章,讲的是他通过一些技术手段进入了一个刚刚起步的网站,他表示这个网站的安全问题非常严重,严重到可以拿到所有用户的用户名,邮箱和密码。大家可能都有这样的感受,就是不爱记忆太多的密码,所以大部分的网站或者应用都用了同一个密码!这样,黑客不仅可以进入邮箱,而且可以进入QQ、MSN等及时聊天系统,从而获得更多的私人关系和关心网信息,这也就是为什么会有那么多利用QQ进行诈骗亲朋好友的案例。还好写这个文章的黑客没有恶意,只是指出了这个网站的问题,否则后果不堪设想(由此看来大部分黑客的职业操守还是很高的o(∩_∩)o )。

     所以这个故事告诉我们,尽量不要用相同的密码,特别是重要的如银行账户之类的,一定要谨慎。那么大家现在都知道对密码进行hash,比如MD5之类的可以比较有效的防止被破译。但是事实真的是这样吗?

     如果直接对密码进行hash,那么黑客可以对一个已知密码进行散列,然后通过对比散列值得到某用户的密码。换句话说,虽然黑客不能取得某特定用户的密码,但他可以知道使用特定密码的用户有哪些!这时,我们可以通过加salt来从一定程度上解决这个问题。什么是salt,顾名思义salt这个“佐料”也就是在对密码进行hash时,添加的一个随机数。其基本想法是这样的——当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,以确定密码是否正确。因为salt值是系统随机生成的,即使两个用户用了相同的密码,他们的hash值也不一样,黑客找出密码的几率大大减小了(必须是密码和自己生成的散列值都跟用户相同,这个概率很低了)。

 

下面讨论一下用户注册的过程:

1)用户提供密码(以及其他用户信息);2)系统为用户生成Salt值;

3)系统将Salt值和用户密码连接到一起;4)对连接后的值进行散列,得到Hash值;

5)将Hash值和Salt值分别放到数据库中。

当用户登录时,相应的过程是:

1)用户提供用户名和密码;

2)系统通过用户名找到与之对应的Hash值和Salt值;

3)系统将Salt值和用户提供的密码连接到一起;

4)对连接后的值进行散列,得到Hash'(注意有个“撇”);

5)比较Hash和Hash'是否相等,相等则表示密码正确,否则表示密码错误。

 

感觉说的比较清楚了,也是结合了网上一些资料和自己的体会写出来的。下面用网上一篇博客里的代码详细的分析一下对密码执行散列和salt的方法。正好在学习c#也是对自己的一个提升。

原文链接http://www.cnblogs.com/esshs/archive/2005/03/29/127998.html

[csharp] view plaincopy
  1. <span style="font-size:16px;">using System;  
  2. using System.IO;  
  3. using System.Text;  
  4. using System.Security.Cryptography;  
  5.   
  6. namespace BookStore.Common  
  7. {  
  8.     /// <summary>  
  9.     /// Credentials 的摘要说明。  
  10.     /// 原理:  
  11.     /// 对密码执行散列运算  
  12.     /// 若要避免以明文形式存储密码,一种常见的安全做法是对密码执行散列运算。如以下代码所示,使用 System.Security.Cryptography 命名空间(它实现 160 位 SHA-1 标准)对密码进行散列运算。有关更多信息,请参见 SHA1 成员。  
  13.     /// 对散列执行 Salt 运算  
  14.     /// 虽然对密码执行散列运算的一个好的开端,但若要增加免受潜在攻击的安全性,则可以对密码散列执行 Salt 运算。Salt 就是在已执行散列运算的密码中插入的一个随机数字。这一策略有助于阻止潜在的攻击者利用预先计算的字典攻击。字典攻击是攻击者使用密钥的所有可能组合来破解密码的攻击。当您使用 Salt 值使散列运算进一步随机化后,攻击者将需要为每个 Salt 值创建一个字典,这将使攻击变得非常复杂且成本极高。  
  15.     /// Salt 值随散列存储在一起,并且未经过加密。所存储的 Salt 值可以在随后用于密码验证。  
  16.     /// </summary>  
  17.     public class Credentials  
  18.     {  
  19.         private static string key = "!48%0d-F=cj>,s&2";    //密钥(增加密码复杂度,好像比较多余)  
  20.         private const int saltLength = 4;                //定义salt值的长度  
  21.   
  22.         /// <summary>  
  23.         /// 对密码进行Hash 和 Salt  
  24.         /// </summary>  
  25.         /// <param name="Password">用户输入的密码</param>  
  26.         /// <returns></returns>  
  27.         public static byte[] HashAndSalt(string Password)  
  28.         {  
  29.             return CreateDbPassword(HashPassword(Password));  
  30.         }  
  31.   
  32.         /// <summary>  
  33.         /// 对用户输入的密码加上密钥key后进行SHA1散列  
  34.         /// </summary>  
  35.         /// <param name="Password">用户输入的密码</param>  
  36.         /// <returns>返回 160 位 SHA-1 散列后的的byte[](160位对应20个字节)</returns>  
  37.         private static byte[] HashPassword( string Password )  
  38.         {  
  39.             //创建SHA1的对象实例sha1  
  40.             SHA1 sha1 = SHA1.Create();  
  41.             //计算输入数据的哈希值  
  42.             return sha1.ComputeHash( Encoding.Unicode.GetBytes( Password + key ) );  
  43.         }  
  44.           
  45.         /// <summary>  
  46.         /// 比较数据库中的密码和所输入的密码是否相同  
  47.         /// </summary>  
  48.         /// <param name="storedPassword">数据库中的密码</param>  
  49.         /// <param name="Password">用户输入的密码</param>  
  50.         /// <returns>true:相等/false:不等</returns>  
  51.         public static bool ComparePasswords(byte[] storedPassword, string Password)  
  52.         {  
  53.             //首先将用户输入的密码进行Hash散列  
  54.             byte[] hashedPassword = HashPassword(Password);  
  55.   
  56.             if (storedPassword == null || hashedPassword == null || hashedPassword.Length != storedPassword.Length - saltLength)  
  57.             {  
  58.                 return false;  
  59.             }  
  60.   
  61.             //获取数据库中的密码的salt 值,数据库中的密码的后4个字节为salt 值  
  62.             byte[] saltValue = new byte[saltLength];  
  63.             int saltOffset = storedPassword.Length - saltLength;  
  64.             for (int i = 0; i < saltLength; i++){  
  65.                 saltValue[i] = storedPassword[saltOffset + i];  
  66.             }  
  67.               
  68.             //用户输入的密码用户输入的密码加上salt 值,进行salt  
  69.             byte[] saltedPassword = CreateSaltedPassword(saltValue, hashedPassword);  
  70.           
  71.             //比较数据库中的密码和经过salt的用户输入密码是否相等  
  72.             return CompareByteArray(storedPassword, saltedPassword);  
  73.         }  
  74.   
  75.         /// <summary>  
  76.         /// 比较两个ByteArray,看是否相等  
  77.         /// </summary>  
  78.         /// <param name="array1"></param>  
  79.         /// <param name="array2"></param>  
  80.         /// <returns>true:相等/false:不等</returns>  
  81.         private static bool CompareByteArray(byte[] array1, byte[] array2)  
  82.         {  
  83.             if (array1.Length != array2.Length)  
  84.             {  
  85.                 return false;  
  86.             }  
  87.             for (int i = 0; i < array1.Length; i++)  
  88.             {  
  89.                 if (array1[i] != array2[i])  
  90.                 {  
  91.                     return false;  
  92.                 }  
  93.             }  
  94.             return true;  
  95.         }  
  96.   
  97.         /// <summary>  
  98.         /// 对要存储的密码进行salt运算  
  99.         /// </summary>  
  100.         /// <param name="unsaltedPassword">没有进行过salt运算的hash散列密码</param>  
  101.         /// <returns>经过salt的密码(经过salt的密码长度为:20+4=24,存储密码的字段为Binary(24))</returns>  
  102.         private static byte[] CreateDbPassword(byte[] unsaltedPassword)  
  103.         {  
  104.             //获得 salt 值  
  105.             byte[] saltValue = new byte[saltLength];  
  106.             RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();  
  107.             rng.GetBytes(saltValue);  
  108.               
  109.             return CreateSaltedPassword(saltValue, unsaltedPassword);  
  110.         }  
  111.           
  112.         /// <summary>  
  113.         /// 创建一个经过salt的密码  
  114.         /// </summary>  
  115.         /// <param name="saltValue">salt 值</param>  
  116.         /// <param name="unsaltedPassword">没有进行过salt运算的hash散列密码</param>  
  117.         /// <returns>经过salt的密码</returns>  
  118.         private static byte[] CreateSaltedPassword(byte[] saltValue, byte[] unsaltedPassword)  
  119.         {  
  120.             //将salt值数组添加到hash散列数组后拼接成rawSalted数组中  
  121.             byte[] rawSalted  = new byte[unsaltedPassword.Length + saltValue.Length];   
  122.             unsaltedPassword.CopyTo(rawSalted,0);  
  123.             saltValue.CopyTo(rawSalted,unsaltedPassword.Length);  
  124.               
  125.             //将合并后的rawSalted数组再进行SHA1散列的到saltedPassword数组(长度为20字节)  
  126.             SHA1 sha1 = SHA1.Create();  
  127.             byte[] saltedPassword = sha1.ComputeHash(rawSalted);  
  128.   
  129.             //将salt值数组在添加到saltedPassword数组后拼接成dbPassword数组(长度为24字节)  
  130.             byte[] dbPassword  = new byte[saltedPassword.Length + saltValue.Length];  
  131.             saltedPassword.CopyTo(dbPassword,0);  
  132.             saltValue.CopyTo(dbPassword,saltedPassword.Length);  
  133.   
  134.             return dbPassword;  
  135.         }  
  136.   
  137.     }  
  138. }</span>