MD5加密算法解释——Java简单应用(java.security.MessageDigest)

MD5解释

        MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由罗纳德·李维斯特设计,于1992年公开,用以替换MD4算法。MD5在MD4的基础上增加了"安全带"(safety-belts)的概念。虽然MD5比MD4复杂度大一些,但却更为安全。

        知识补给:

        1、MD4算法需要填补信息以确保信息的比特位长度减去448后能被512整除(信息比特位长度 mod 512 = 448)。然后,一个以64位二进制表示的信息的最初长度被添加进来。信息被处理成512位damgard/merkle迭代结构的区块,而且每个区块要通过三个不同步骤的处理。

        2、密码散列函数(Cryptographic hash function),又译为加密散列函数,是散列函数的一种。它被认为是一种单向函数,也就是说极其难以由散列函数输出的结果,回推输入的数据是什么。这样的单向函数被称为“现代密码学的驮马”。

        3、散列函数(或散列算法,又称哈希函数,英语:Hash Function)是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表

        4、字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,也表示一些计算机编程语言中的数据类型和语言字符 。一个字节存储8位无符号数,储存的数值范围为0~255(2^8 - 1)。

Java实现MD5加密

        本例中使用了java.security.MessageDigest这个类包来实现加密,实现方法如下:        

import java.security.MessageDigest;

public class MD5 {
    public static String Md5(String plainText) {
        try {
            //获取MD5消息摘要对象
            //JDK 6 以上支持以下6种消息摘要算法,不区分大小写
            // md5,sha(sha-1),md2,sha-256,sha-384,sha-512
            MessageDigest md = MessageDigest.getInstance("MD5");

            //通过指定的字节更新摘要
            md.update(plainText.getBytes("UTF-8"));

            //获取散列值的字节数组(完成哈希计算)
            byte b[] = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");

                //将整数转换成16进制无符号数
                buf.append(Integer.toHexString(i));
            }

            //将无符号整数转成字符串
            return buf.toString();
        } catch (Exception e) {
            //e.printStackTrace();
            return "";
        }
    }

}

        在此编造一个手机号码进行加密,得到的结果如图所示:

备注:   

        本人通过网上MD5在线破解对str1与str2的32位摘要进行破解,发现对于str1,可以很轻易的碰撞出结果,但是对于str2,结果就很难碰撞出。这也得出了,对于手机号的加密,可以通过在手机号前面增加随机字符来增强安全性。该方法在企业中也很常见。

        在百度百科中,也指出了MD5的弱点:

        1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

         知识补给:MD5破解是通过摘要获取原文吗?

        答案是否定的,这里所谓的破解,并非把摘要还原成原文。因为固定128位的摘要是有穷的,而原文数量是无穷的,每一个摘要都可以由若干个原文通过Hash得到。对于MD5的破解,实际上都属于【碰撞】。比如原文A通过MD5可以生成摘要M,我们并不需要把M还原成A,只需要找到原文B,生成同样的摘要M即可。即A和B通过MD5加密后的结果殊途同归。

        例如:我们用18034587499作为登录密码,后台服务记录的密码为4a05e2a9cbcaa8aea34a0dff645adc55。此时,通过MD5碰撞出一个明文:6666(举例)。我们在登录时就不需要使用18034587499作为登录密码,直接使用6666登录即可骗过应用系统的验签,到达破解密码的效果。

MD5加密原理

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

        1、首先需要对信息进行填充,使其字节长度对512求余数的结果等于448。因此,信息的字节长度(Bits Length)将被扩展至N*512+448,即N*64+56个字节(Bytes),N为一个正整数。  

        2、填充的方法,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后再在这个结果后面附加一个以64位二进制表示的填充前的信息长度。经过这两步的处理,现在的信息字节长度=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍数。这样做的原因是为满足后面处理中对信息长度的要求。

        3、四轮循环运算,MD5中有四个32位被称作链接变量(Chaining Variable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。当设置好这四个链接变量后,就开始进入算法的四轮循环运算,循环的次数是信息中512位信息分组的数目。

主循环逻辑:
      1、每次操作对a,b,c和d中的其中三个作一次非线性函数运算:F=(b&c)|((~b)&d)
      2、将所得结果加上 第四个变量、一个常数和文本的一个子分组:a+F+K[i]+M[g]
      3、再将所得结果向右环移一个不定的数:shift(a+F+K[i]+M[g],s[i])
      4、并加上a,b,c或d中之一:b = b+shift(a+F+K[i]+M[g],s[i])
      5、最后用该结果取代a,b,c或d中之一:Btemp=b+Btemp

        下面将加密原理的代码给出,供学有余力的读者学习。

public class MD5_test {
    /*
     * 四个链接变量
     * 为固定值,经过研究所得
     */
    private final int A=0x01234567;
    private final int B=0x89abcdef;
    private final int C=0xfedcba98;
    private final int D=0x76543210;
    /*
     *ABCD的临时变量
     */
    private int Atemp,Btemp,Ctemp,Dtemp;

    /*
     *常量ti
     *公式:floor(abs(sin(i+1))×(2pow32)
     */
    private final int K[]={
            0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
            0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,
            0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,
            0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,
            0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
            0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,
            0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,
            0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,
            0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,
            0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,
            0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,
            0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,
            0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391};
    /*
     *向左位移数,计算方法未知
     */
    private final int s[]={7,12,17,22,7,12,17,22,7,12,17,22,7,
            12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
            4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,
            15,21,6,10,15,21,6,10,15,21,6,10,15,21};


    /*
     *初始化函数
     */
    private void init(){
        Atemp=A;
        Btemp=B;
        Ctemp=C;
        Dtemp=D;
    }
    /*
     *右环移动一定位数
     */
    private    int    shift(int a,int s){
        return(a<<s)|(a>>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位
    }
    /*
     *主循环
     * 1、每次操作对a,b,c和d中的其中三个作一次非线性函数运算:F=(b&c)|((~b)&d)
     * 2、将所得结果加上 第四个变量、一个常数和文本的一个子分组:a+F+K[i]+M[g]
     * 3、再将所得结果向右环移一个不定的数:shift(a+F+K[i]+M[g],s[i])
     * 4、并加上a,b,c或d中之一:b = b+shift(a+F+K[i]+M[g],s[i])
     * 5、最后用该结果取代a,b,c或d中之一:Btemp=b+Btemp
     */
    private void MainLoop(int M[]){
        int F,g;
        int a=Atemp;
        int b=Btemp;
        int c=Ctemp;
        int d=Dtemp;
        for(int i = 0; i < 64; i ++){
            if(i<16){
                F=(b&c)|((~b)&d);
                g=i;
            }else if(i<32){
                F=(d&b)|((~d)&c);
                g=(5*i+1)%16;
            }else if(i<48){
                F=b^c^d;
                g=(3*i+5)%16;
            }else{
                F=c^(b|(~d));
                g=(7*i)%16;
            }
            int tmp=d;
            d=c;
            c=b;
            b=b+shift(a+F+K[i]+M[g],s[i]);
            a=tmp;
        }
        Atemp=a+Atemp;
        Btemp=b+Btemp;
        Ctemp=c+Ctemp;
        Dtemp=d+Dtemp;

    }
    /*
     *填充函数
     *处理后应满足bits≡448(mod512),字节就是bytes≡56(mode64)
     *填充方式为先加一个0,其它位补零
     *最后加上64位的原来长度
     */
    private int[] add(String str){
        int num=((str.length()+8)/64)+1;//以512位,64个字节为一组
        int strByte[]=new int[num*16];//64/4=16,所以有16个整数
        for(int i=0;i<num*16;i++){//全部初始化0
            strByte[i]=0;
        }
        int    i;
        for(i=0;i<str.length();i++){
            strByte[i>>2]|=str.charAt(i)<<((i%4)*8);//一个整数存储四个字节,小端序
        }
        strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1
        /*
         *添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位
         */
        strByte[num*16-2]=str.length()*8;
        return strByte;
    }
    /*
     *调用函数
     */
    public String getMD5(String source){
        init();
        int strByte[]=add(source);
        for(int i=0;i<strByte.length/16;i++){
            int num[]=new int[16];
            for(int j=0;j<16;j++){
                num[j]=strByte[i*16+j];
            }
            MainLoop(num);
        }
        return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);

    }
    /*
     *整数变成16进制字符串
     */
    private String changeHex(int a){
        String str="";
        for(int i=0;i<4;i++){
            str+=String.format("%2s", Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace(' ', '0');

        }
        return str;
    }
    /*
     *单例
     */
    private static MD5_test instance;
    public static MD5_test getInstance(){
        if(instance==null){
            instance=new MD5_test();
        }
        return instance;
    }

    private MD5_test(){};

    public static void main(String[] args){
        String str=MD5_test.getInstance().getMD5("");
        System.out.println(str);
    }
}

有问题可以一起探讨,希望对你有帮助;

我的一小步,填坑一大步!!!

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值