安全随笔1:谨慎一次MD5值的可被穷举性

MD5不再安全不是从算法本身而言。如果从可逆性角度出发, MD5值不存在被破解的可能性。MD5的算法公式如下:

R=H(S)

该公式指出:对于给定的一个源内容S,H可以将其映射为R。这里要注意几个特点。首先,S到R的映射是一种多对一的映射;其次,R作为目标内容,是一个无规律的定长的字符串;最后,映射H是一种压缩映射,即R的空间远远小于S。

MD5的算法特性使其无法存在一个逆过程,即:将R还原成为S,下面的公式不成立:

R=H-1(S)

正是基于以上的特点,MD5被广泛用于密码验证和消息体完整性验证。相信大家对于密码验证使用MD5算法都不陌生。假设新注册了一个用户,当注册用户的密码第一次被存储到数据库的时候,我们往往将其转换为MD5值存储:

 
  
static void Main( string [] args)
{
string source = " luminji's key " ;
string hash = GetMd5Hash(source);
Console.WriteLine(
" 保存密码原文:{0}的MD5值:{1}到数据库。 " , source, hash);
}

static string GetMd5Hash( string input)
{
using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
{
return BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(input))).Replace( " - " , "" );
}
}

以上代码输出:

 
  
保存密码原文:luminji ' s key的MD5值:D3A8E4D76A0AEF23B65D9F6D6BCB358F到数据库。

有了MD5值存储在数据库中,在用户进行登录的时候,只要校验MD5就可以确保用户输入的是否是正确的密码了。如下:

 
  
static void Main( string [] args)
{
Console.WriteLine(
" 请输入密码,按回车键结束…… " );
string source = Console.ReadLine();
if (VerifyMd5Hash(source, " D3A8E4D76A0AEF23B65D9F6D6BCB358F " ))
{
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 ;
}

本段代码的输出为:

 
  
请输入密码,按回车键结束……
luminji
' s key
密码正确,准许登录系统。

或许大家会问:为什么不直接存储密码,而使用MD5值呢?如果非要回答这个问题,我想更大程度上还是要从保护我们自己的隐私着想。即便是一个银行系统,我们也不想让银行的后台管理人员看到我们的密码。而通过MD5值,就可以确保这一点。通过验证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);
}

在上面的代码中,我们假设用户代码是“0”到“9999”的字符串,并在此范围内进行匹配。结果输出为:

 
  
开始穷举法破解用户密码……
密码已破解,为:
8888 ,耗时271毫秒。

可见,如果我们的密码输入的过份简单,计算机甚至都不需要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值作为返回值。经过这个过程以后,密码的长度够了,复杂度也够了,想要通过穷举法来得到真正的密码值的成本就大大增加了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值