MD5算法原理及实现

散列函数

散列函数,也称作哈希函数,消息摘要函数,单向函数或者杂凑函数。散列函数主要用于验证数据的完整性。通过散列函数,可以创建消息的“数字指纹”,消息接收方可以通过校验消息的哈希值来验证消息的完整性,防止消息被篡改。散列函数具有以下特性:

  1. 散列函数的运算过程是不可逆的,这个称为散列函数的单向性。
  2. 对于一个已知的消息及其散列值,要找到另外一个消息使其获得相同的散列值是不可能的,这个特性称为散列函数的弱碰撞性。这个特性可以用来防止消息伪造。
  3. 任意两个不同消息的散列值一定不同。
  4. 对原始消息长度没有限制。

任何消息经过散列函数处理后,都会产生一个唯一的散列值,这个散列值可以用来验证消息的完整性。计算消息散列值的过程被称为“消息摘要”,计算消息散列值的算法被称为消息摘要算法。常使用的消息摘要算法有:MD—消息摘要算法,SHA—安全散列算法,MAC—消息认证码算法。本文主要来了解MD算法。

MD5算法

MD5算法是典型的消息摘要算法,它是由MD4,MD3和MD2算法演变而来。无论是哪一种MD算法,其原理都是接受一个任意长度的消息并产生一个128位的消息摘要。如果把得到的消息摘要转换成十六进制字符串,则会得到一个32字节长度的字符串,我们平常见到的大部分MD数字指纹就是一个长度为32的十六进制字符串。

MD5算法原理

假设原始消息长度是b(以bit为单位),注意这里b可以是任意长度,并不一定要是8的整数倍。计算该消息MD5值的过程如下:

1.填充信息

在计算消息的MD5值之前,首先对原始信息进行填充,这里的信息填充分为两步。
第一步,对原始信息进行填充,填充之后,要求信息的长度对512取余等于448。填充的规则如下:假设原始信息长度为b bit,那么在信息的b+1 bit位填充1,剩余的位填充0,直到信息长度对512取余为448。这里有一点需要注意,如果原始信息长度对512取余正好等于448,这种情况仍然要进行填充,很明显,在这时我们要填充的信息长度是512位,直到信息长度对512取余再次等于448。所以,填充的位数最少为1,最大为512。
第二步,填充信息长度,我们需要把原始信息长度转换成以bit为单位,然后在第一步操作的结果后面填充64bit的数据表示原始信息长度。第一步对原始信息进行填充之后,信息长度对512取余结果为448,这里再填充64bit的长度信息,整个信息恰好可以被512整除。其实从后续过程可以看到,计算MD5时,是将信息分为若干个分组进行处理的,每个信息分组的长度是512bit。

2.信息处理

在进行MD5值计算之前,我们先来做一些定义。

  • 信息分组定义
    原始信息经过填充之后,最终得到的信息长度(bit)是512的整数倍,我们先对信息进行分组,每512bit为一个分组,然后再将每个信息分组(512bit)再细分为16个小的分组,每个小分组的长度为32bit。规定如下
    Mp 代表经过填充之后的信息
    LM 表示Mp的长度(以bit为单位)
    N 表示分组个数,N = LM/512
    M[i] 表示将原始信息进行分组后的第i个信息分组,其中i=1...N
    X[i] 表示将M[i]进行分组后的第i个小分组,其中i=1...16
  • 标准幻数定义
    现定义四个标准幻数如下,
    A = 01 23 45 67
    B = 89 ab cd ef
    C = fe dc ba 98
    D = 76 54 32 10
    在计算机中存储时,采用小端存储方式,以A为例,A在Java中初始化的代码为为A=0x67452301
  • 常量表T
    T是一个常量表,T[i] = 4294967296 * abs(sin(i))的运算结果取整,其中i=1...64
  • 辅助方法
    我们定义四个辅助方法。
    F(x,y,z) = (x & y) | ((~x) & z)
    G(x,y,z) = (x & z) | (y & (~z))
    H(x,y,z) = x ^ y ^ z
    I(x,y,z) = y ^ (x | (~z))
    其中,x,y,z长度为32bit

下面就是最核心的信息处理过程,计算MD5的过程实际上就是轮流处理每个信息分组的过程。

    A=0x67452301
    B=0xefcdab89
    C=0x98badcfe
    D=0x10325476
    for( j=1;j<=N;j++){  //依次处理每个分组,其中N代表分组个数
      AA = A 
      BB = B 
      CC = C
      DD = D
      //开始处理分组,每个信息分组要经过4轮处理
      /*第一轮
      假设 [abcd k s i] 表示执行的运算是 a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺序从左到右。*/
    [ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3 22 4]
    [ABCD 4 7 5] [DABC 5 12 6] [CDAB 6 17 7] [BCDA 7 22 8]
    [ABCD 8 7 9] [DABC 9 12 10] [CDAB 10 17 11] [BCDA 11 22 12]
    [ABCD 12 7 13] [DABC 13 12 14] [CDAB 14 17 15] [BCDA 15 22 16]

    /*第二轮
    假设 [abcd k s i] 表示执行的运算是 a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s),其中 <<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺 序从左到右。*/
    [ABCD 1 5 17] [DABC 6 9 18] [CDAB 11 14 19] [BCDA 0 20 20]
    [ABCD 5 5 21] [DABC 10 9 22] [CDAB 15 14 23] [BCDA 4 20 24]
    [ABCD 9 5 25] [DABC 14 9 26] [CDAB 3 14 27] [BCDA 8 20 28]
    [ABCD 13 5 29] [DABC 2 9 30] [CDAB 7 14 31] [BCDA 12 20 32]

    /*第三轮
    假设 [abcd k s i] 表示执行的运算是 a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺 序从左到右。*/
    [ABCD 5 4 33] [DABC 8 11 34] [CDAB 11 16 35] [BCDA 14 23 36]
    [ABCD 1 4 37] [DABC 4 11 38] [CDAB 7 16 39] [BCDA 10 23 40]
    [ABCD 13 4 41] [DABC 0 11 42] [CDAB 3 16 43] [BCDA 6 23 44]
    [ABCD 9 4 45] [DABC 12 11 46] [CDAB 15 16 47] [BCDA 2 23 48]

    /*第四轮
    假设 [abcd k s i] 表示执行的运算是 a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s),其中<<<表示循环移位。第一轮运算就是对分组执行以下所示的16次运算,运算的顺序从左到右。*/
    [ABCD 0 6 49] [DABC 7 10 50] [CDAB 14 15 51] [BCDA 5 21 52]
    [ABCD 12 6 53] [DABC 3 10 54] [CDAB 10 15 55] [BCDA 1 21 56]
    [ABCD 8 6 57] [DABC 15 10 58] [CDAB 6 15 59] [BCDA 13 21 60]
    [ABCD 4 6 61] [DABC 11 10 62] [CDAB 2 15 63] [BCDA 9 21 64]
    
    //将当前消息分组的运算结果和上一次的结果进行累加
    A = A + AA
    B = B + BB
    C = C + CC
    D = D + DD
}
//最终我们按照低字节在前的顺序依次将A,B,C,D拼接起来,就是计算得到的MD5值,因此,MD5值的长度是固定的,为128bit。
算法实现

MD5算法实现如下所示。

import java.security.MessageDigest;
import java.util.Arrays;
public class MD5Test{
 
    //标准的幻数
    private static final int A=0x67452301;
    private static final int B=0xefcdab89;
    private static final int C=0x98badcfe;
    private static final int D=0x10325476;

    private static final int S11 = 7;
    private static final int S12 = 12;
    private static final int S13 = 17;
    private static final int S14 = 22;
 
    private static final int S21 = 5;
    private static final int S22 = 9;
    private static final int S23 = 14;
    private static final int S24 = 20;
 
    private static final int S31 = 4;
    private static final int S32 = 11;
    private static final int S33 = 16;
    private static final int S34 = 23;
 
    private static final int S41 = 6;
    private static final int S42 = 10;
    private static final int S43 = 15;
    private static final int S44 = 21;
    
    private static final int GROUP_LEN = 64;
 
    public byte[] digest(byte[] input){
        byte [] paddingData = getPaddingData(input); //对原始数据进行补位       
        return process(paddingData);//处理分组,核心算法
    }
 
    private int[] getGroupData(byte[] data,int index){
        int [] groupData=new int[16];
        for(int i=0;i<16;i++){
            groupData[i] = (data[4*i+index]&0xFF)| //这里注意,在byte转int时一定要进行&0xff操作
                           (data[4*i+1+index]&0xFF)<<8|
                           (data[4*i+2+index]&0xFF)<<16|
                           (data[4*i+3+index]&0xFF)<<24;
        }
        return groupData;
    }
    
    private int[] getConstTable() {
        int[] T = new int[64];
        for(int i=1;i<65;i++) {
            T[i-1] = (int)((long)(Math.abs(Math.sin(i))*4294967296l));
        }
        return T;
    }
   
 
    private byte[] getPaddingData(byte[] input) {
        int length = input.length;
        long bitLength = length << 3;
        int rest = length % 64;
        int paddingLength = 0;
        if(rest < 56) {
            paddingLength = 64-rest;
        }else {
            paddingLength = 128 - rest;
        }
        byte[] paddingData = new byte[length+paddingLength];
        for(int i=0;i>> 8;
        }
        return paddingData;
    }
  
    private byte[] process(byte[] data) {
        int [] result = {A,B,C,D};
        int length = data.length; 
        int groupCount = length/64; //计算分组数量,每组512位(64字节)
        int[] T = getConstTable();
        for(int groupIndex=0;groupIndex>8;
            }
        }
        return resultByte;
    }
 
   
    private static int F(int x, int y, int z) {
        return (x & y) | ((~x) & z);
    }
 
    private static int G(int x, int y, int z) {
        return (x & z) | (y & (~z));
    }
 
    private static int H(int x, int y, int z) {
        return x ^ y ^ z;
    }
 
    private static int I(int x, int y, int z) {
        return y ^ (x | (~z));
    }
 
    private static int FF(int a, int b, int c, int d, int x, int s, int t) {
        a += (F(b, c, d)&0xFFFFFFFF) + x + t;
        a = ((a&0xFFFFFFFF)<< s) | ((a&0xFFFFFFFF) >>> (32 - s)); //循环位移
        a += b;
        return (a&0xFFFFFFFF);
    }
 
    private static int GG(int a, int b, int c, int d, int x, int s, int t) {
        a += (G(b, c, d)&0xFFFFFFFF) + x + t;
        a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
        a += b;
        return (a&0xFFFFFFFF);
    }
 
    private static int HH(int a, int b, int c, int d, int x, int s, int t) {
        a += (H(b, c, d)&0xFFFFFFFF) + x + t;
        a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
        a += b;
        return (a&0xFFFFFFFF);
    }
 
    private static int II(int a, int b, int c, int d, int x, int s,int t) {
        a += (I(b, c, d)&0xFFFFFFFF) + x + t;
        a = ((a&0xFFFFFFFF) << s) | ((a&0xFFFFFFFF) >>> (32 - s));
        a += b;
        return (a&0xFFFFFFFF);
    }
   
    public static void main(String []args){
        MD5Test myMd5=new MD5Test();
        String testData = "hello,world";
        System.out.println("--------My MD5--------");
        byte[] myResult = myMd5.digest(testData.getBytes());
        System.out.println(Arrays.toString(myResult));
        
        System.out.println("--------Java MD5--------");  
        try {
            MessageDigest javaMd5 = MessageDigest.getInstance("MD5");
            byte[] javaResult = javaMd5.digest(testData.getBytes());
            System.out.println(Arrays.toString(javaResult)); 
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }
}

这里也和Java提供的标准MD5算法进行了对比,通过测试可以看到该MD5计算的结果和Java标准MD5算法的计算结果是一样的。

你可能感兴趣的:(MD5算法原理及实现)