MD5的介绍

        从本质上来说,MD5并不是一种加密算法,其全称是Message Digest Algorithm MD5(中文名:信息摘要算法第五版),为计算机安全领域广泛使用的一种散列函数,主要用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。MD5将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。信息摘要算法除了MD5以外,其中比较有名的还有sha-1、RIPEMD以及Haval等。

MD5的应用

一致性验证

MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNⅨ下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:
 MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461
这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要。为了让读者朋友对MD5的应用有个直观的认识,笔者以一个比方和一个实例来简要描述一下其工作过程:
大家都知道,地球上任何人都有自己独一无二的指纹,这常常成为公安机关鉴别罪犯身份最值得信赖的方法;与之类似,MD5就可以为任何文件(不管其大小、格式、数量)产生一个同样独一无二的“数字指纹”,如果任何人对文件做了任何改动,其MD5值也就是对应的“数字指纹”都会发生变化。
我们常常在某些软件下载站点的某软件信息中看到其MD5值,它的作用就在于我们可以在下载该软件后,对下载回来的文件用专门的软件(如Windows MD5 Check等)做一次MD5校验,以确保我们获得的文件与该站点提供的文件为同一文件。利用MD5算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。

数字证书

MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。

安全访问认证

MD5还广泛用于操作系统的登陆认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方面。如在UNⅨ系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在文件系统中。当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。MD5将任意长度的“字节串”映射为一个128bit的大整数,并且是通过该128bit反推原始字符串是困难的,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。所以,要遇到了md5密码的问题,比较好的办法是:你可以用这个系统中的md5()函数重新设一个密码,如admin,把生成的一串密码的Hash值覆盖原来的Hash值就行了。
正是因为这个原因,现在被黑客使用最多的一种破译密码的方法就是一种被称为"跑字典"的方法。有两种方法得到字典,一种是日常搜集的用做密码的字符串表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。我们假设密码的最大长度为8位字节(8 Bytes),同时密码只能是字母和数字,共26+26+10=62个字符,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这个字典就需要TB级的磁盘阵列,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。这种加密技术被广泛的应用于UNⅨ系统中,这也是为什么UNⅨ系统比一般操作系统更为坚固一个重要原因。

算法原理

对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

关于MD5的解密

2004年8月17日的美国加州圣巴巴拉的国际密码学会议(Crypto’2004)上,来自中国山东大学的王小云教授做了破译MD5、HAVAL-128、 MD4和RIPEMD算法的报告,公布了MD系列算法的破解结果。宣告了固若金汤的世界通行密码标准MD5的堡垒轰然倒塌,引发了密码学界的轩然大波。

需要指出的是,王小云教授研究出的破解方法,本质上并非真正的破解,只是加速了杂凑冲撞。通俗点说,知道一个MD5串,然后使用原文进行MD5散列后再生成同样的MD5串,也就是说找到不同的原文产生相同MD5串的方法,这不是解密而称之为碰撞。MD5只有128位要真能解密的话,就变成一个超级压缩工具了!

网络上流行的MD5解密工具和在线解密网站,通常只是针对用户密码,或比较简单的数字进行破解,其方法是将常用字符串的MD5密码保存到数据库,然后再与待解密的字符串做对比,最终找到匹配的源码。

 

java实现MD5加密及密码验证

前面的论述告诉我们,MD5本身并没有解密的算法,更谈不上使用编程语言编写解密算法了,下面给出java实现的MD5的加密以及用来做用户密码验证的程序,仅供参考。

工具类MyMD5Util.java:

package com.zyg.security.md5;   
  
import java.io.UnsupportedEncodingException;   
import java.security.MessageDigest;   
import java.security.NoSuchAlgorithmException;   
import java.security.SecureRandom;   
import java.util.Arrays;   
  
public class MyMD5Util {   
       
    private static final String HEX_NUMS_STR="0123456789ABCDEF";   
    private static final Integer SALT_LENGTH = 12;   
       
    /**   
     * 将16进制字符串转换成字节数组   
     * @param hex   
     * @return   
     */  
    public static byte[] hexStringToByte(String hex) {   
        int len = (hex.length() / 2);   
        byte[] result = new byte[len];   
        char[] hexChars = hex.toCharArray();   
        for (int i = 0; i < len; i++) {   
            int pos = i * 2;   
            result[i] = (byte) (HEX_NUMS_STR.indexOf(hexChars[pos]) << 4    
                            | HEX_NUMS_STR.indexOf(hexChars[pos + 1]));   
        }   
        return result;   
    }   
  
       
    /**  
     * 将指定byte数组转换成16进制字符串  
     * @param b  
     * @return  
     */  
    public static String byteToHexString(byte[] b) {   
        StringBuffer hexString = new StringBuffer();   
        for (int i = 0; i < b.length; i++) {   
            String hex = Integer.toHexString(b[i] & 0xFF);   
            if (hex.length() == 1) {   
                hex = '0' + hex;   
            }   
            hexString.append(hex.toUpperCase());   
        }   
        return hexString.toString();   
    }   
       
    /**  
     * 验证口令是否合法  
     * @param password  
     * @param passwordInDb  
     * @return  
     * @throws NoSuchAlgorithmException  
     * @throws UnsupportedEncodingException  
     */  
    public static boolean validPassword(String password, String passwordInDb)   
            throws NoSuchAlgorithmException, UnsupportedEncodingException {   
        //将16进制字符串格式口令转换成字节数组   
        byte[] pwdInDb = hexStringToByte(passwordInDb);   
        //声明盐变量   
        byte[] salt = new byte[SALT_LENGTH];   
        //将盐从数据库中保存的口令字节数组中提取出来   
        System.arraycopy(pwdInDb, 0, salt, 0, SALT_LENGTH);   
        //创建消息摘要对象   
        MessageDigest md = MessageDigest.getInstance("MD5");   
        //将盐数据传入消息摘要对象   
        md.update(salt);   
        //将口令的数据传给消息摘要对象   
        md.update(password.getBytes("UTF-8"));   
        //生成输入口令的消息摘要   
        byte[] digest = md.digest();   
        //声明一个保存数据库中口令消息摘要的变量   
        byte[] digestInDb = new byte[pwdInDb.length - SALT_LENGTH];   
        //取得数据库中口令的消息摘要   
        System.arraycopy(pwdInDb, SALT_LENGTH, digestInDb, 0, digestInDb.length);   
        //比较根据输入口令生成的消息摘要和数据库中消息摘要是否相同   
        if (Arrays.equals(digest, digestInDb)) {   
            //口令正确返回口令匹配消息   
            return true;   
        } else {   
            //口令不正确返回口令不匹配消息   
            return false;   
        }   
    }   
  
  
    /**  
     * 获得加密后的16进制形式口令  
     * @param password  
     * @return  
     * @throws NoSuchAlgorithmException  
     * @throws UnsupportedEncodingException  
     */  
    public static String getEncryptedPwd(String password)   
            throws NoSuchAlgorithmException, UnsupportedEncodingException {   
        //声明加密后的口令数组变量   
        byte[] pwd = null;   
        //随机数生成器   
        SecureRandom random = new SecureRandom();   
        //声明盐数组变量   
        byte[] salt = new byte[SALT_LENGTH];   
        //将随机数放入盐变量中   
        random.nextBytes(salt);   
  
        //声明消息摘要对象   
        MessageDigest md = null;   
        //创建消息摘要   
        md = MessageDigest.getInstance("MD5");   
        //将盐数据传入消息摘要对象   
        md.update(salt);   
        //将口令的数据传给消息摘要对象   
        md.update(password.getBytes("UTF-8"));   
        //获得消息摘要的字节数组   
        byte[] digest = md.digest();   
  
        //因为要在口令的字节数组中存放盐,所以加上盐的字节长度   
        pwd = new byte[digest.length + SALT_LENGTH];   
        //将盐的字节拷贝到生成的加密口令字节数组的前12个字节,以便在验证口令时取出盐   
        System.arraycopy(salt, 0, pwd, 0, SALT_LENGTH);   
        //将消息摘要拷贝到加密口令字节数组从第13个字节开始的字节   
        System.arraycopy(digest, 0, pwd, SALT_LENGTH, digest.length);   
        //将字节数组格式加密后的口令转化为16进制字符串格式的口令   
        return byteToHexString(pwd);   
    }   
}

用于检验MD5加密及密码验证的测试类Client.java:

package com.zyg.security.md5;   
  
import java.io.UnsupportedEncodingException;   
import java.security.NoSuchAlgorithmException;   
import java.util.HashMap;   
import java.util.Map;   
  
public class Client {   
    private static Map users = new HashMap();   
       
    public static void main(String[] args){   
        String userName = "zyg";   
        String password = "123";   
        registerUser(userName,password);   
           
        userName = "changong";   
        password = "456";   
        registerUser(userName,password);   
           
        String loginUserId = "zyg";   
        String pwd = "1232";   
        try {   
            if(loginValid(loginUserId,pwd)){   
                System.out.println("欢迎登陆!!!");   
            }else{   
                System.out.println("口令错误,请重新输入!!!");   
            }   
        } catch (NoSuchAlgorithmException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        } catch (UnsupportedEncodingException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        }    
    }   
       
    /**  
     * 注册用户  
     *   
     * @param userName  
     * @param password  
     */  
    public static void registerUser(String userName,String password){   
        String encryptedPwd = null;   
        try {   
            encryptedPwd = MyMD5Util.getEncryptedPwd(password);   
               
            users.put(userName, encryptedPwd);   
               
        } catch (NoSuchAlgorithmException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        } catch (UnsupportedEncodingException e) {   
            // TODO Auto-generated catch block   
            e.printStackTrace();   
        }   
    }   
       
    /**  
     * 验证登陆  
     *   
     * @param userName  
     * @param password  
     * @return  
     * @throws UnsupportedEncodingException   
     * @throws NoSuchAlgorithmException   
     */  
    public static boolean loginValid(String userName,String password)    
                throws NoSuchAlgorithmException, UnsupportedEncodingException{   
        String pwdInDb = (String)users.get(userName);   
        if(null!=pwdInDb){ // 该用户存在   
                return MyMD5Util.validPassword(password, pwdInDb);   
        }else{   
            System.out.println("不存在该用户!!!");   
            return false;   
        }   
    }   
}