zoukankan      html  css  js  c++  java
  • 安全随笔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值作为返回值。经过这个过程以后,密码的长度够了,复杂度也够了,想要通过穷举法来得到真正的密码值的成本就大大增加了。

  • 相关阅读:
    hdu 3289 Magic tree (最大生成树 + dfs +树状数组)
    hdu 3294 Girls' research
    hdu 3639 HawkandChicken (强连通 + 反图 + 缩点) && hdu1827 Summer Holiday && hdu 1269 迷宫城堡 && hdu3072 Intelligence System
    hdu 3288 Resource Allocation
    hdu3038 How Many Answers Are Wrong
    单项式
    我的新博客开通了.
    svn服务器中实现自动备份(postcommit钩子,linux系统)
    校验和
    SVNPath 与 SVNParentPath 的区别注意
  • 原文地址:https://www.cnblogs.com/luminji/p/2055021.html
Copyright © 2011-2022 走看看