散列法提供了一种单向加密的方式。这种方式非常适用于在数据库中存储密码。因为我们无须(也不希望)提供解密的信息。在登录验证时,只需简单地将用户的输入进行散列,并和数据库中存储的散列值进行比较即可。
由于散列码的长度不会随着源数据的长度而变化,因此可用于文件比较和数据流错误检查(和校验码非常类似)。源数据中任意一个位的变化都能够使散列码发生巨大的变化。
HashAlgorithm子类(例如SHA256或者MD5)的ComputeHash方法可以用于生成散列码:
获取文件的MD5
byte[] hash;
using (Stream fa = File.OpenRead("text_file1.txt"))
{
hash = MD5.Create().ComputeHash(fa);
}
对字符串进行SHA256加密
byte[] data = System.Text.Encoding.UTF8.GetBytes("this is a password");
byte[] hash256 = SHA256.Create().ComputeHash(data);
Encoding类的GetBytes方法将会把字符串编码为字节数组;而Get-String方法则会执行反向操作。但是Encoding类无法将一个加密或者散列之后的字节数组转换为字符串,因为这种数据往往会包含违反编码规则的数据。如果需要将字节数组转换为合法的(且XML友好的)字符串,或执行相反操作,请分别使用Convert.ToBase64String和Convert.From-Base64String。
MD5和SHA256是.NET Framework中从HashAlgorithm派生的子类。以下以升序按照安全等级列出了常用的散列算法:
MD5(16)->SHA1(20)->SHA256(32)->SHA384(48)->SHA512(64)
算法的长度越短运算的速度越快。MD5的运算速度比SHA512快20多倍,因此非常适用于计算文件的校验和。MD5算法可以每秒钟加密数百兆字节的数据,并将其存储在一个Guid中(Guid恰好也是16字节长,并且作为一个值类型比字节数组更易于处理。例如,我们可以直接用相等运算符比较两个Guid的值。)但同时,越短的散列算法发生碰撞(两个不同的文件会生成相同的散列码)的概率越大。
如果要存储密码或其他高安全等级的敏感数据,请至少使用SHA256。MD5和SHA1在这种情形下是不安全的。MD5和SHA1仅适用于防止意外的破坏,而无法防御故意的篡改行为。
SHA384执行速度并不比SHA512快,因此如果需要获取比SHA256更高的安全性,可以使用SHA512。较长的SHA算法可以用于密码的散列。但是它们需要强制采用高密码强度的策略来防范字典攻击的威胁(字典攻击指攻击者对字典中的每一个词进行散列,创建一个密码查找表的攻击策略)。还可以通过重复散列的方式来加长密码散列值的长度,从而提供更强的保护。例如,如果重复进行100次散列,则使用字典攻击需要花费1个月来破解的密码现在需要8年才能够破解。Rfc2898DeriveBytes和PasswordDeriveBytes类恰好提供了上述功能。
另一种防止字典攻击的技术是在加密过程中引入salt“盐”,即由随机数生成器生成的一长串字节,并在散列之前将其并入密码中。这样做可以通过两种途径来对抗攻击者:
1、这需要更长的计算时间,
2、攻击者无法访问“盐”字节的值。
此外,Framework还提供了160位的RIPEMD散列算法。这种算法的安全性比SHA1稍好,但受制于.NET对该算法的低效实现,它的执行速度比SHA512还慢。