Java实现MD5算法过程,并利用自带MD5函数进行对比校验

文章目录

    • 一、环境说明
    • 二、算法原理概述
    • 三、程序设计
      • 数据结构
      • 重要模块步骤
    • 四、运行结果

一、环境说明

操作系统:window10

编程语言:Java (JDK版本 11.0.1)

使用IDE:Intellij IDEA

二、算法原理概述

Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第1张图片

整个MD5(信息摘要算法5)的基本过程可以概括为以下几个步骤:

  1. 填充:消息为 K K K bits的原始消息数据尾部填充长度为 P P P bits的标识 1000...0   1 ≤ P ≤ 512 1000...0 \, 1\le P \le 512 1000...01P512 (至少要填充一个bit) 。使得填充后的消息位数满足 K + P ≡ 448 ( m o d   512 ) K + P \equiv 448 (mod \, 512) K+P448(mod512) (注:当 K ≡ 448 ( m o d   512 ) K \equiv 448(mod \, 512) K448(mod512))时, P = 512 P = 512 P=512

    填充好的消息尾部需要在附加 K K K值的低64位即 K   m o d   2 64 K \, mod \,2^{64} Kmod264。 最终结果得到 K + P + 64 ≡ 0 ( m o d   512 ) K + P + 64 \equiv 0 (mod \, 512) K+P+640(mod512)填充消息

  2. 分块:把填充之后的消息结果分割为 L L L 512 − b i t 512-bit 512bit 分组: Y 0 . . Y L − 1 Y_0..Y_{L-1} Y0..YL1 。也是 L L L个64字节的分组。

  3. 缓冲区初始化:初始化一个 128 − b i t 128-bit 128bit的MD缓冲区,记为 C V q CV_q CVq,表示成4个 32 − b i t ( 4 个 b y t e ) 32-bit (4个byte) 32bit(4byte) 的寄存器 ( A , B , C , D ) (A,B,C,D) (A,B,C,D) C V 0 = I V ( I V 为 16 进 制 初 值 ) CV_0 = IV (IV为16进制初值) CV0=IV(IV16)

    Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第2张图片

  4. 循环压缩 :对L个消息分组 Y q ( q = 0 , 1 , . . . L − 1 ) Y_q(q = 0, 1,...L-1) Yq(q=0,1,...L1) ,逐个经过4重循环的压缩算法。表示为:

    C V 0 = I V CV_0 = IV CV0=IV

    C V i = H M D 5 ( C V i − 1 , Y i ) CV_i = H_{MD5}(CV_{i-1}, Y_i) CVi=HMD5(CVi1,Yi)

  5. 得出结果:最后一个消息分组经过 H M D 5 H_{MD5} HMD5压缩得到MD5结果为MD值,即 M D = C V L MD = CV_L MD=CVL

整个加密算法的基本流程如上。而整个加密算法的核心步骤在于 H M D 5 H_{MD5} HMD5压缩函数流程如下。

Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第3张图片

  1. 总控流程 H M D 5 H_{MD5} HMD5 C V CV CV输入128位,分配到缓冲区 ( A , B , C , D ) (A,B,C,D) (A,B,C,D),从消息分组输入512位 Y q Y_q Yq ,经过4轮循环,每次循环16次迭代(共64次迭代)之后,得到用于下一轮的输入的 C V CV CV值。如果 Y q = Y L − 1 Yq = Y_{L-1} Yq=YL1,即输出MD5值。

  2. 每轮循环:结合T表元素 T [ ] T[] T[]和消息分组的不同部分 X [ ] X[] X[],每轮固定不同的生成函数 F , G , H , I F,G,H,I FGHI做16次迭代运算,生成下一轮循环的输入。

  3. 四个生成函数 F , G , H , I F,G,H,I F,G,H,I

Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第4张图片

  1. 消息分组的内容:需要靠下标k来进行运算得到参与 迭代的消息部分,代表当前处理消息分组的第 k k k ( k = 0...15 ) 32 (k = 0...15)32 (k=0...15)32位字,即 M q × 16 + k M_{q × 16 + k} Mq×16+k

    在各轮循环中第 i i i 次迭代 ( i = 1..16 ) (i = 1..16) (i=1..16) 使用的 X [ k ] X[k] X[k] 的确定:

    ​ 设 j = i − 1 j = i -1 j=i1
    ◌ 第1轮迭代: k = j k = j k=j.
    顺序使用 $ X[0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15]$
    ◌ 第2轮迭代: k = ( 1 + 5 j ) m o d 16 k = (1 + 5j) mod 16 k=(1+5j)mod16.
    顺序使用 X [ 1 , 6 , 11 , 0 , 5 , 10 , 15 , 4 , 9 , 14 , 3 , 8 , 13 , 2 , 7 , 12 ] X[1, 6,11, 0, 5,10,15, 4, 9,14, 3, 8,13, 2, 7,12] X[1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12]
    ◌ 第3轮迭代: k = ( 5 + 3 j ) m o d 16 k = (5 + 3j) mod 16 k=(5+3j)mod16.
    顺序使用 X [ 5 , 8 , 11 , 14 , 1 , 4 , 7 , 10 , 13 , 0 , 3 , 6 , 9 , 12 , 15 , 2 ] X[5, 8,11,14, 1, 4, 7,10,13, 0, 3, 6, 9,12,15, 2] X[5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2]
    ◌ 第4轮迭代: k = 7 j m o d 16 k = 7j mod 16 k=7jmod16.
    顺序使用 X [ 0 , 7 , 14 , 5 , 12 , 3 , 10 , 1 , 8 , 15 , 6 , 13 , 4 , 11 , 2 , 9 ] X[0, 7,14, 5,12, 3,10, 1, 8,15, 6,13, 4,11, 2, 9] X[0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9]

  2. T 表元素的生成:共有64个元素,用于64次的迭代,每个元素的计算如下。

    T [ i ] = i n t ( 2 32 × ∣ s i n ( i ) ∣ ) T[i] = int(2_{32}×|sin(i)|) T[i]=int(232×sin(i))
    i n t 为 取 整 函 数 , s i n 正 弦 函 数 , 以 i 作 为 弧 度 输 入 。 int为 取整函数,sin正弦函数,以i 作为弧度输入。 intsini

  3. 移位数s的确定:
    s表共有64个元素,用于64次迭代,各次迭代运算采用的左循环移位的s 值:
    s [ 1..16 ] = 7 , 12 , 17 , 22 , 7 , 12 , 17 , 22 , 7 , 12 , 17 , 22 , 7 , 12 , 17 , 22 s[ 1..16] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22 } s[1..16]=7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22
    s [ 17..32 ] = 5 , 9 , 14 , 20 , 5 , 9 , 14 , 20 , 5 , 9 , 14 , 20 , 5 , 9 , 14 , 20 s[17..32] = { 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20 } s[17..32]=5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20
    s [ 33..48 ] = 4 , 11 , 16 , 23 , 4 , 11 , 16 , 23 , 4 , 11 , 16 , 23 , 4 , 11 , 16 , 23 s[33..48] = { 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23 } s[33..48]=4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23
    s [ 49..64 ] = 6 , 10 , 15 , 21 , 6 , 10 , 15 , 21 , 6 , 10 , 15 , 21 , 6 , 10 , 15 , 21 s[49..64] = { 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 } s[49..64]=6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21

  4. 一次迭代运算逻辑: a , b , c , d a,b,c,d a,b,c,d为寄存器 A , B , C , D A,B,C,D A,B,C,D的内容,每轮循环重的一次迭代运算逻辑如下:

  5. 对寄存器A进行迭代: a ← b + ( ( a + g ( b , c , d ) + X [ k ] + T [ i ] ) < < < s ) a \leftarrow b + ((a + g(b,c,d) + X[k] + T[i]) <<< s) ab+((a+g(b,c,d)+X[k]+T[i])<<<s)

  6. 对缓冲区的内容进行向左循环变换,即 ( B , C , D , A ) ← ( A , B , C , D ) (B,C,D,A) \leftarrow (A,B,C,D) (B,C,D,A)(A,B,C,D)
    X [ k ] X[k] X[k]为消息分组的部分内容, g ( b , c , d ) g(b,c,d) g(b,c,d)为生成函数, T [ i ] T[i] T[i]为T表元素, s s s为移位数。
    Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第5张图片

三、程序设计

数据结构

本程序使用的数据数据结构如下:

 	static byte[] M;                  /* 存放消息字节数组 */

    static long[] T = new long[64];   /* 迭代运算的T表, 64个元素,每个元素有32bits,16进制8位 */

    /*在迭代中的消息数组*/
    static long[] X = new long[16];

    /*四个寄存器A,B,C,D,构成128bits的迭代缓冲区*/
    static long A = 0x67452301;
    static long B = 0xEFCDAB89;
    static long C = 0x98BADCFE;
    static long D = 0x10325476;

消息数组M的生成如下:

FileInputStream fis = new FileInputStream(inputString);   // 读入文件流
BufferedInputStream bis = new BufferedInputStream(fis);   // 将文件流读入缓冲区
DataInputStream dis = new DataInputStream(bis);           // 将缓冲区数据写入数据流

M = new byte[(int) (length + paddingLength + 8)];     // 填充消息最终长度  满足于 length + padding + 8 = 0 mod 64  字节

// 将文件内容读入全部字节数组M中并填充
for(int i = 0; i < length + paddingLength; i++){
   if( i < length){
      M[i] = (byte)dis.read();
   }
    else if(i == length){
      M[i] = (byte)128;
   }
   else{
       M[i] = 0;
   }
}

迭代运算中的T表数据生成如下:

/*生成迭代的T表格*/
public static void create_T_Table(){
    for(int i = 0;i < 64;i++){
        T[i] = (long) (Math.floor(Math.abs(Math.sin(i+1)) * (long)Math.pow(2,32)));
    }
}

处理消息分组X[]的生成如下:

/*将 512bit的消息处理为 16个字的X数组*/
for(j=0,k=0;j<16;j++,k+=4){
   X[j] = ((int)M[i * 64 + k] & 0xFF) | ((int)M[i*64+k+1] & 0xFF) << 8 |
         ((int)M[i*64+k+2] & 0xFF) << 16 | ((int)M[i*64+k+3] & 0xFF) << 24;
}

重要模块步骤

循环左移s位模块

/* 
*  @ param  x  被移数
*  @ param  s  左移的位数
* */
public static long rotateLeft(long x, long s){
     return (x << s)| (x >> (32 - s)) & 0xFFFFFFFL;
}
/////////////////////////////////////////////////////////////////////

四个生成函数和四个迭代函数。

    /*四重循环使用的函数*/
    /*
    * @param a b c d 为四个缓冲区的内容
    *        k 为X[k]
    *        s 为移位数目
    *        i为 T[j]
     */
    public static long F_Func(long a,long b,long c,long d,long k,long s, long i){
        return (b + rotate_left(((a + ((b & c) | ((~b) & d)) + k + i) & 0xFFFFFFFFL),s)) & 0xFFFFFFFFL;
    }

    public static long G_Func(long a,long b,long c,long d,long k,long s, long i){
        return (b + rotate_left(((a + ((b & d) | (c & (~d))) + k + i) & 0xFFFFFFFFL),s)) & 0xFFFFFFFFL;
    }

    public static long H_Func(long a,long b,long c,long d,long k,long s, long i){
        return (b + rotate_left(((a + (b ^ c ^ d) + k + i) & 0xFFFFFFFFL) , s)) & 0xFFFFFFFFL;
    }

    public static long I_Func(long a,long b,long c,long d,long k,long s, long i){
        return (b + rotate_left(((a + (c ^ (b | (~d))) + k + i) & 0xFFFFFFFFL), s)) & 0xFFFFFFFFL;
    }
    /*将小端形式转为大端形式*/
    public static long encode(long t){
        return ((t >> 24) & 0xff) | ((t >> 16) & 0xff) << 8 | ((t >> 8) & 0xff) << 16 | (t & 		0xff) << 24;
    }

将数据从小端转为大端的形式

/*将小端形式转为大端形式*/
    public static long encode(long t){
        return ((t >> 24) & 0xff) | ((t >> 16) & 0xff) << 8 | ((t >> 8) & 0xff) << 16 | (t & 		0xff) << 24;
    }

获取填充的位数

	    String inputString = "test1.txt";       // 输入的文件名
        File file = new File(inputString);  // 文件操作对象
        length = file.length();             // 获取文件的字节长度1
        
        // 获取填充的位数
        if(length % 64 < 56){
            paddingLength = (int)(56 - length % 64); // 字节
        }
        else if(length % 64 == 56){
            paddingLength = 64;   // 64 字节
        }
        else if(length % 64 > 56){
            paddingLength = (int) (64 - (length % 64 - 56));
        }

将消息分块

// 该循环的作用是:对全部原始消息进行分块,每块大小为64个字节,共512位
   for(int i = 0; i < (length + paddingLength + 8)/64; i++){
            ...

进入4次循环,共64次迭代

// 进入 4 轮循环,每次循环16次迭代,一共64次迭代
 for(j = 0; j < 64; j ++){
     int div16 = j / 16;  // div16 代表每次循环的迭代次数

每次循环的迭代过程

switch (div16){
    case 0:
           // 第一轮循环,16次迭代
           j_factor = j ;
           k_index = j_factor;
           // 分四个A、B、C、D 缓冲区处理
           if(j % 4 == 0)
           {
              A = F_Func(A,B,C,D,X[k_index],7,T[j]);
           }
            else if(j % 4 == 1)
            {
                D = F_Func(D,A,B,C,X[k_index],12,T[j]);
            }
            else if(j % 4 == 2)
            {
                C = F_Func(C,D,A,B,X[k_index],17,T[j]);
            }
            else if(j % 4 == 3)
            {
                B = F_Func(B,C,D,A,X[k_index],22,T[j]);
            }
	break;

原寄存器内容与4重循环后的寄存器内容相加,得到下一轮压缩的寄存器值。

  A = (A + tmpA) & 0xFFFFFFFFL;
  B = (B + tmpB) & 0xFFFFFFFFL;
  C = (C + tmpC) & 0xFFFFFFFFL;
  D = (D + tmpD) & 0xFFFFFFFFL;

全部消息压缩后,输出结果。

System.out.format("小端形式MD5:%x %x %x %x\n", A,B,C,D);
A = encode(A);   // 转为大端形式
B = encode(B);
C = encode(C);
D = encode(D);
System.out.format("大端形式MD5:%x %x %x %x\n",A,B,C,D);

使用java自带的MD5函数进行比对验证。

/*调用java自带md5函数 输出md5值*/
    public static String getMd5ForFile(String fileName) throws IOException, NoSuchAlgorithmException {
        FileInputStream in = null;
        File file = new File(fileName);
        in = new FileInputStream(file);
        MessageDigest md5 = MessageDigest.getInstance("MD5");

        byte[] cache = new byte[2048];
        int len;
        while ((len = in.read(cache)) != -1) {
            md5.update(cache, 0, len);
        }
        in.close();
        BigInteger bigInt = new BigInteger(1, md5.digest());
        return bigInt.toString(16);
    }

四、运行结果

主函数中的代码如下, 利用java自带的MD5函数进行验证。

  public static void main(String[] args)throws IOException,  NoSuchAlgorithmException{
        String inputString = "";       // 输入的文件名
        System.out.println("输入加密的文件名:");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        inputString = br.readLine();//直接读取字符串
        getMD5ByFile(inputString);  // 自行实现的函数
        System.out.println("java自带的MD5函数结果如下:");
        System.out.println(getMd5ForFile(inputString));  // 使用java自带的MD5函数
    }

文件内容为hello world时,结果如下

Java实现MD5算法过程,并利用自带MD5函数进行对比校验_第6张图片

由此可见,基本实现了MD5算法。实验存在的不足之处是:处理大文件的时候,一次性将文件数据读入内存并不太现实,Java内置的解决办法是通过内存映射的方式来实现内存占用的问题,这为优化算法提供了新的思路。

完整代码github见。

你可能感兴趣的:(加密算法,Java,信息安全,algorithm,Java)