【转】编写高质量代码改善C#程序的157个建议——建议114:MD5不再安全
建议114:MD5不再安全
MD5不再安全不是就算法本身而言的。如果从可逆性的角度出发,MD5值不存在被破解的可能性。
MD5被广泛应用于密码验证和消息完整性验证。假设新注册一个用户,当注册用户的密码第一次被存储到数据库时,往往会将其转换为MD5值存储:
static string GetMd5Hash(string input) { using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) { return BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(input))).Replace("-", ""); } } static void Main(string[] args) { string source = "liming's key"; string hash = GetMd5Hash(source); Console.WriteLine("保存密码原文:{0}的MD5值:{1}到数据库。",source,hash); Console.Read(); }
输出为:
保存密码原文:liming's key的MD5值:B222558FD330454B08878C61FD595121到数据库。
如果MD5值存储在数据库中,当用户登录时,只需要验证MD5就可以检查用户输入的密码是否正确。如下:
static void Main(string[] args) { Console.WriteLine("请输入密码,按回车键结束……"); string source = Console.ReadLine(); if (VerifyMd5Hash(source, "B222558FD330454B08878C61FD595121")) { Console.WriteLine("密码正确,准许登录系统。"); } else { Console.WriteLine("密码有误,拒绝登录。"); } } static bool VerifyMd5Hash(string input, string hash) { string hashOfInput = GetMd5Hash(input); StringComparer comparer = StringComparer.OrdinalIgnoreCase; return comparer.Compare(hashOfInput, hash) == 0 ? true : false; }
输出为:
请输入密码,按回车键结束……
liming's key
密码正确,准许登录系统。
处于隐私保护的目的,所以不直接存储密码。即便是一个银行系统,我们也不想让银行的后台管理人员看到我们的密码。而通过MD5值来校验,就可以确保无人可以查看或破解我们的密码,也达到了密码验证的目的。虽然有人可能会质疑,MD5的算法不是多对一的吗?也就是说,可能存在一个另外的密码,求出来的MD5值和我这个密码是一样的啊。但是,在实际应用场合中,这个概率会很小,小到可以忽略不计。
既然到目前为止所说的都是MD5的优点,那么,为什么说MD5是不安全的呢?因为,这个世界上还有一种方法叫做穷举法。由于用户安全意识先对薄弱,所以他们设置的密码很有可能是简单的数字集合。这种情况下如果破解密码。穷举法会派上很大的用处。以密码“8888”为例,测试下我们用穷举法破解所需时间:
static void Main(string[] args) { Console.WriteLine("开始穷举法破解用户密码……"); string key = string.Empty; Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 9999; i++) { if (VerifyMd5Hash(i.ToString(), "CF79AE6ADDBA60AD018347359BD144D2")) { key = i.ToString(); break; } } watch.Stop(); Console.WriteLine("密码已破解,为:{0},耗时{1}毫秒。", key, watch.ElapsedMilliseconds); }
输出为:
开始穷举法破解用户密码……
密码已破解,为:8888,耗时124毫秒。
可见,如果我们的密码过于简单,计算机甚至都不需要1秒的时间就能完成暴力破解。当然,这种算法不是针对MD5的可逆破解,而是非常愚蠢的穷举。现在,已经有很多免费的商业的MD5字典库,存储了相当数量字符串的MD5值,我们只要提交一个MD5值进去,立刻就可以得到他的原文,只要这个原文不是非常复杂。所以,从这方面来说,MD5不再安全。
因此,我们需要找一个办法来改进MD5求值了。目前,最通用的算法是多次使用MD5值发。我们修改一下GetMd5Hash方法,代码如下:
static string GetMd5Hash(string input) { string hashKey = "Aa1@#$,.Klj+{>.45oP"; using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) { string hashCode = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(input))).Replace("-", "") + BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(hashKey))).Replace("-", ""); return BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(hashCode))).Replace("-", ""); } }
在改进后的方法中,我们首先设计了一个足够复杂的密码hashKey,然后将它的MD5值和用户输入密码的MD5值相加,再求一次MD5值作为返回值。经过这个过程以后,密码的长度就够了,复杂度也够了,要想通过穷举法来得到真正的密码成本也就大大增加了。
转自:《编写高质量代码改善C#程序的157个建议》陆敏技