看了标题,你马上会明白我这篇文章要讲什么——密码的Hash化,虽然这并不是什么新技术,也不是高端技术,但它确实很有意义。当然如果仅仅因为它有意义还不足以让我写这篇文章。有些人会觉得不就是密码Hash嘛,没有什么必要,还会影响网站的处理速度,如果你是这些人中的一员,请听我说两个真实的故事。

故事1
某黑客通过一些技术手段进入了一个刚刚起步的网站(至于用什么方法大家就不要多问了,再说与这篇文章关系不大),这个网站的安全问题很严重,严重到可以拿到所有用户的用户名、邮箱、网站密码。大家知道,很多人不喜欢记太多密码,他们在任何网站使用的永远是同一个密码。好了,问题出来了!这里面有大量的gmail、msn账户,也就是说知道邮箱,知道密码,我们不但能进入这个人的电子邮箱,还能进入googletalk或msn这样的及时聊天系统,从而获得更多的私人信息和关系网信息,甚至能冒用这个人的身份进行诈骗,还好这个黑客并没有恶意,并没有采取进一步的行动,否则后果不堪设想。

这个故事告诉我们在不同的网站、邮箱要使用不同的密码,当然要选择自己好记些的,但千万不要为了图方便而都用一样的密码,这是很危险的!特别是涉及金融、银行方面的网站更要使用高强度密码(有数字、大小写字母、下划线等组成),这样可以防止一些人用字典进行密码破解。从另一个角度看,这个故事还告诉我们,作为网站的开发者你必须时刻记住一点,在任何时候都不应该把机密数据用明文保存或传递,数据从进入系统那一刻开始网站就有义务保证数据的安全性,所以必须作相应的安全处理。对于密码,我们应该对密码做Hash,大家应该知道一个字符串无论何时做Hash,出来的结果是一样的,虽然从理论上讲会出现不同的字符串产生相同的Hash序列的情况,但是这种概率很低,基本上是中彩票的概率,这也是为什么要用Hash逆推出原文来几乎是不可能的,我们可以认为这种方法是安全的。

故事2
早上我在公交车上听到几句电话对话
甲:你密码忘记了?
....
甲:这个没关系,我帮你去问问IT部,他们知道密码是多少?
....

可能乍一看,这段对话很正常,没有什么特别的,但是我们可以分析一下其中的含义。密码忘了我们第一个想到的是什么——要回密码,但这能够实现吗?对于一个公司内部而言,这种密码获取还相对简单,毕竟大家可以方便地证明自己的身份,所以从业务上来讲似乎拿回密码一点问题都没有,但实际上,技术上不允许我们这么做。我们刚才说到过Hash是不可逆的,如果一个网管告诉你他可以帮你拿回原来的密码,这说明什么?说明密码很有可能是用明文存储的,这在很多情况下是很危险的,管理员可以利用自己的职权,获得一些人的密码,然后进入那些人的私人邮箱(非公司邮箱)盗取信息。即使有些密码是加密存储的,如果它是可逆的那也是不安全的,只要有加密后的字符串,且知道加密算法和密钥,也能轻易获得原密码,这同样是很危险的,当然相对而言这种方法要难许多、不是每个人都能做到的,但在第一个故事中我们看到如果某个黑客进入了这样的系统,仍然有办法获得原密码,毕竟黑客的技术水准要高出许多。从这一点我们应该意识到加密的密文必须不可逆,否则会带来进一步的信息泄漏。

好了,通过上面两个故事,我想大家应该对密码Hash的必要性比较认同了,希望以后在写code或者做架构设计的时候一定要把这个它考虑进去。从技术角度讲,我们只能为用户重置密码,但不能告诉用户原密码是什么,这一点live.com就做得很好,如果哪个网站可以拿回原密码,说明他不够安全,建议马上改密码!用一个和你的邮箱不一样的密码,特别是注册这个网站时使用的那个邮箱。

下面我顺便讲讲.NET下如何实现密码Hash化。有人说,Hash可以用System.Security.Cryptography命名空间下面的加密类来实现,对,说得没错,我们的确可以这么做,但这似乎还是有些麻烦~~(我比较懒,呵呵)其实呢,.NET提供了一个专门用于做Password Hashing的方法,这在.NET 1.1中就有了,定义如下:

public static string HashPasswordForStoringInConfigFile ( 
   
string password, 
   
string passwordFormat
)


第一个参数password就是密码,而第二个参数则是要使用的Hash算法,这个值只能是FormsAuthPasswordFormat枚举的成员(位于System.Web.Configuration下),不过不清楚为什么不直接传枚举类型。FormsAuthPasswordFormat有三个枚举成员,它们是Clear、MD5、SHA1,其中的Clear就是不加密,直接用明文的意思。

具体的例子MSDN上有,我就不抄了,大家可以参考:http://msdn2.microsoft.com/en-us/library/system.web.security.formsauthentication.hashpasswordforstoringinconfigfile.aspx

加了一个基于Salt的密码Hash实现,代码如下:
    public class PasswordHelper 
   

       
public static string CreateSalt(int size) 
       

           
//Generate a cryptographic random number. 
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); 
           
byte[] buff = new byte[size]; 
            rng.GetBytes(buff); 
 
           
// Return a Base64 string representation of the random number. 
            return Convert.ToBase64String(buff); 
        }
 
 
       
public static string CreatePasswordHash(string pwd, string salt) 
       

           
string saltAndPwd = String.Concat(pwd, salt); 
           
string hashedPwd = 
             FormsAuthentication.HashPasswordForStoringInConfigFile( 
             saltAndPwd,
"sha1"); 
 
           
return hashedPwd; 
        }
 
 
       
public static bool PasswordMatch(string current,string salt,string savedPasswordHash) 
       

           
string currentPasswordHash=CreatePasswordHash(current, salt); 
           
return (currentPasswordHash == savedPasswordHash); 
        }
 
    }