MD5加密图片(python具体实现)密码学作业

        第一次写博客,有点小激动~

        这次主要是写一下MD5算法的具体实现过程,我在网上也查了许多资料,借鉴了很多大佬的代码,同时大部分是参考《现代密码学(第5版)》这本教材,最后缝缝补补写了一段代码,写出来分享一下。我主要在网上参考的是MD5密码实验——Python实现(完整解析版)_python md5解密-CSDN博客这篇博客,帮助很大,感谢博主!

一、实验环境

本实验是在python3.9环境下运行的,在spyder上完成运行。

二、实验内容

MD5算法的具体实现。

三、总体思路

copy的图:

        总体来说,首先对于原消息,要将其填充,最终填充后的长度可以被512bit整除。填充后,将每512bit作为一块(之后说到一块,就指每一次的512bit),将每一块以及一个128bit的初始序列作为输入,进行MD5分组处理的运算。在每一块的分组运算中,有4轮(之后说到轮,就是每一块的4轮),64步(之后说到步,就是指一块中的64步),也就是一轮有16步。每一块运算完成后,会有一个128bit的输出,这个输出会作为下一块的运算的一个输入,再加上下一块的512bit,作为下一块MD5运算的输入量,之后不断循环,直到所有bit都运算过。最后,将最后一次运算的128bit的输出作为结果,即MD5加密的值。

四、实验步骤

1.设置基本参数

在MD5运算中有许多参数需要设置,包括初始的4个32bit的缓冲区A、B、C、D,以及他们的复制AA、BB、CC、DD;表T;表S(压缩函数每步左循环移位的位数);表m(每轮对使用字的顺序的处理);F、G、H、I基本逻辑函数;F_、G_、H_、I_函数(每一轮中每一步函数的实现);CLS函数(左循环移位函数)。

1.1 A、B、C、D缓冲区以及他们的复制AA、BB、CC、DD。

        设置缓冲区的目的是存储中间结果以及最终的MD5值,每个寄存器都以小端方式存储数据。初始值为A = 0x01234567,B = 0x89ABCDEF,C = 0xFEDCBA98,D = 0x76543210,按照小端方式存储后,结果为A = 0X67452301,B = 0XEFCDAB89,C = 0X98BADCFE,D = 0X10325476。同时设置他们的复制,方便在每一轮循环最后,将改变后的ABCD与最初输入的值进行相加。

A = 0X67452301
B = 0XEFCDAB89
C = 0X98BADCFE
D = 0X10325476
AA = 0X67452301
BB = 0XEFCDAB89
CC = 0X98BADCFE
DD = 0X10325476
1.2 表T

        书上提供了常数表T。T[i]在每一轮中的每一步中,需要T[i]的参与。一块一共64步,恰好遍历64个T[i]。

T = [ 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 ,]
1.3 表S

        书上提供了表S。表S中的S[i]是CLS函数中需要用到的,表示每步左循环移位的位数。与T[i]类似,恰好64步,遍历64个S[i]。

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 ,]
1.4 表m

        表m实际上是对代码的一步简化。因为在函数的一块处理中,输入的512bit要分成16个32bit的字,在64步中,每16步要循环利用一遍这16个字。第一遍为0-15,恰好也利用的是F_函数。在第一遍中,16步的每一步都需要加X[k],而这里的X[k]即为M[k],即从M[0]到M[15]的字。而在第二遍中,即G_函数中,需要用到字依然是M[0]-M[15],但是用字的顺序发生了改变,书上提供了算法,置换算法如下:

而表m即将这每16步的字的顺序算好,存到了m中,为之后调用提供便利。

m = [ 0 ,  1 ,  2 ,  3 , 4 ,  5 ,  6 ,  7 ,  8 ,  9 , 10 , 11 , 12 , 13 , 14 , 15 ,
      1 ,  6 , 11 ,  0 , 5 , 10 , 15 ,  4 ,  9 , 14 ,  3 ,  8 , 13 ,  2 ,  7 , 12 ,
      5 ,  8 , 11 , 14 , 1 ,  4 ,  7 , 10 , 13 ,  0 ,  3 ,  6 ,  9 , 12 , 15 ,  2 ,
      0 ,  7 , 14 ,  5 , 12,  3 , 10 ,  1 ,  8 , 15 ,  6 , 13 ,  4 , 11 ,  2 ,  9 ]
1.5 F、G、H、I基本逻辑函数

        这四个逻辑函数是在每一步中都需要使用到其中一种,每个函数都是简单的逻辑运算函数,书上给出了公式。

def F(a, b, c):
        return (a & b) | ((~a) & c)
def G(a, b, c):
        return (a & c) | (b & (~c))
def H(a, b, c):
        return a ^ b ^ c
def I(a, b, c):
        return b ^ (a | (~c))
1.6 F_、G_、H_、I_函数

        这四个函数表示的是每一步中整体的运算函数。书上给出了完整的过程。首先是B、C、D进行g函数(即FGHI)运算,之后与A相加,与X[k]相加,与T[i]相加。接下来进行CLS左循环移位,再与B在进行相加,这样就得到了B的结果。最后将原来B、C、D的结果分别传给C、D、A,这一步就计算完成了。贴了一个函数,剩下几个基本一致。

        这里需要注意的是,由于我申请的A、B、C、D都是全局变量,在python中需要使用global声明才能对全局变量进行修改。另外,在将x与其他数相加的时候,要使用int转化,且必须有后面的2,只有这样才是按照2进制来解释x,否则会自动默认按照十进制解释x,导致结果错误。

def F_(x, t, s):
    global A, B, C, D
    g = F(B, C, D)
    tem = g + A + int(x,2) + t
    te = CLS(tem, s)
    a1 = (te + B) % pow(2, 32)
    a2, a3, a4 = B, C, D
    A, B, C, D = a4, a1, a2, a3

1.7 CLS函数

CLS函数是32位存数左循环s位函数,s的取值在表S中可以找到。表示m向左移动n位。

def CLS(m, n):
    m &= 0xFFFFFFFF
    return (( m << n ) | (m >> (32 - n))) & 0xFFFFFFFF

        m &= 0xFFFFFFFF这一步保证m是一个32位无符号整数。之后一个向左移位,一个向右移位是因为循环,向左到头就会到右移位。

        到这里参数基本就已经引用完成了,接下来就正式进入MD5加密!

2.打开文件

        由于是图片,所以用open打开文件,之后用read读取文件。需要注意的是,read读取文件读到的是'bytes'类型,需要转化成二进制,也就是逐比特来运算。

with open('jsf.jpg', 'rb') as f:
    Data = f.read()
data = ''.join(format(byte, '08b') for byte in Data)  

        在转化过程中,‘08b’分别表示了使用零填充、总宽度为 8 个bit和二进制格式进行转换。

3.消息填充

        消息填充分两步,第一步是填充1和0,第二步是填充消息长度。

3.1 填充1和0

        填充这一步书上讲的还是比较清楚的,假设原长度为L1,填充后长度为L2,则满足1\leqslant L2-L1\leqslant 512L2\equiv 448(mod\quad512)。填充的第一位为1,之后都为0。

3.2填充消息长度

        在填充消息长度这一步中,由于上述长度与512bit差了64bit,我们要填充的消息长度就是64bit,正好填完,填充的内容是:在填充1和0之前的原消息的长度,这里用的是bit数。最后将填充的内容与原来的消息连接在一起。注意填充方式要使用小端填充,具体小端填充大家可以看这篇文章:python之struct详解_python struct-CSDN博客

length_padding = struct.pack('<Q', length)  
length_padding_bits = ''.join(format(byte, '08b') for byte in length_padding)  
data += length_padding_bits

        现在填充完成,数据应该正好是512bit的倍数。

4.分组处理

        我们上面已经提到,MD5在处理过程中,是以512bit为一块进行处理,下面首先针对一块的处理进行分析。

4.1 对一块消息的处理
4.1.1 总体处理

        针对每一块消息,对照下图。我们的输入有两个,CVq和Yq。CVq为128bit的串,其实就是定义的A、B、C、D四个32bit的缓冲区,连在一起就成了128bit的CVq。Yq为512bit的块,将其分成16个32bit的字。这里特别强调一下,我认为“字”是MD5这个算法的关键!

4.1.2 求“字”

在这一块中,我们需要持续用到这16个字,而且这16个字是独一无二属于这512bit的,在下一个块中,会用到另外16个字。那么首先就先将这个16个字给搞出来。

下面是求一块中16个字的方法:

for i in range(0, len(data), 512):
    M = []
    cur_group = data[i : i + 512]
    
    for j in range(0, 512, 32):
        num = ""
        # 获取每一段的标准十六进制形式
        for k in range(0, len(cur_group[j:j+32]), 4):
            temp = cur_group[j:j+32][k:k+4]
            temp = hex(int(temp, 2))
            num += temp[2]
        # 对十六进制进行小端排序
        num_tmp = ""
        for k in range(8, 0, -2):
            temp = num[k-2:k]
            num_tmp += temp
        mm = ""
        for k in range(len(num_tmp)):
            num = int(num_tmp[k], 16)
            bin_num = bin(num)[2:].zfill(4)
            mm += bin_num
        M.append(mm)

        这段是copyMD5密码实验——Python实现(完整解析版)_python md5解密-CSDN博客博主的,很感谢~偷个懒(#^.^#)

        首先是创建列表M,用来存储这一块的16个字。我们一次看32个bit,首先找到当前这32bit,4位一组求其十六进制数,用hex转化后将前两位“0x”舍掉,从temp[2]开始加到num中。之后对16进制数进行小端排序,倒序来看,每次一个字节(8bit),也就是2位16进制。最后将排序完成的数转化成二进制读出来,添加到M中。

        这里学到一个小知识,int(x, k)表示将x从k进制转化为十进制,如果不写k,就默认是10。这里是将16进制的数解释为10进制整数,之后再转化为2进制即可。

4.1.3 64步迭代

        由于在上面的F_、G_、H_、I_函数已经实现(详见1.6),这里可以直接使用。那我们就直接对函数进行使用,64步共分为4轮,第一轮全部使用的是F_函数,第二轮全部使用的是G_函数,第三轮全部使用的是H_函数,第四轮全部使用的是I_函数。一轮共有16步,总共64步。

        这里需要注意的点是,我们在引用参数的时候,T和S较为简单,就是第几步就是几,但是对于M,由于一共只有16个字(上面刚求过),一次只够一轮(16个)使用。所以书上为我们提供了置换算法,第一次是从M[0]到M[15]顺序使用,之后每一次都使用的顺序不一样,而我们已经将其存到表m中了,所以直接调用表m即可(详见1.4)。

        在函数64步迭代完成后,要将变换后的A、B、C、D与原来的A、B、C、D相加,这里我们用到之前设置的A、B、C、D的复制AA、BB、CC、DD即可。注意这里是模2的32次方相加。之后要将AA、BB、CC、DD重新设置为A、B、C、D,这是为了方便下一轮进行运算。

    for ii in range(0, 16):
        F_(M[m[ii]], T[ii], S[ii])
    for ii in range(0, 16):
        G_(M[m[ii + 16]], T[16 + ii], S[16 + ii])
    for ii in range(0, 16):            
        H_(M[m[ii + 32]], T[32 + ii], S[32 + ii])
    for ii in range(0, 16): 
        I_(M[m[ii + 48]], T[48 + ii], S[48 + ii])
    A = (A + AA) % pow(2, 32)
    B = (B + BB) % pow(2, 32)
    C = (C + CC) % pow(2, 32)
    D = (D + DD) % pow(2, 32)
    AA, BB, CC, DD = A, B, C, D

通过上述对每一块消息的处理,不难发现,消息处理的实质就是对A、B、C、D四个缓冲区里的数据进行修改,调换,最后相加后,置换成新的A、B、C、D和AA、BB、CC、DD。A、B、C、D这四个32bit的缓冲区实质就是128bit的CVq。在中间处理过程中,没必要将其合并,只需要保持A、B、C、D即可。

4.2 对所有消息的处理

        其实就是加上一个循环即可,很简单。512bit一块进行运算,不断更新A、B、C、D。

5.输出MD5值

        当进行完最后一轮A、B、C、D的更新运算后,在循环外,将A、B、C、D分别以小端方式连接在一起,之后转化成16进制形式的字符串,即为最终的MD5码。

A_bytes = struct.pack('<I', A)
B_bytes = struct.pack('<I', B)
C_bytes = struct.pack('<I', C)
D_bytes = struct.pack('<I', D)

# 级联这些字节串
md5_digest = A_bytes + B_bytes + C_bytes + D_bytes

# 将级联后的字节串转换为16进制格式的字符串
md5_hex = ''.join(f'{byte:02x}' for byte in md5_digest)

print("MD5加密值:",md5_hex)

五、实验代码

        下面是我写的完整代码,其中路径地址可以修改。如果想加密字符串,将Data数据改成b"string"形式即可。

import struct
A = 0X67452301
B = 0XEFCDAB89
C = 0X98BADCFE
D = 0X10325476
AA = 0X67452301
BB = 0XEFCDAB89
CC = 0X98BADCFE
DD = 0X10325476
T = [ 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 ,]
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 ,]
m = [ 0 ,  1 ,  2 ,  3 , 4 ,  5 ,  6 ,  7 ,  8 ,  9 , 10 , 11 , 12 , 13 , 14 , 15 ,
      1 ,  6 , 11 ,  0 , 5 , 10 , 15 ,  4 ,  9 , 14 ,  3 ,  8 , 13 ,  2 ,  7 , 12 ,
      5 ,  8 , 11 , 14 , 1 ,  4 ,  7 , 10 , 13 ,  0 ,  3 ,  6 ,  9 , 12 , 15 ,  2 ,
      0 ,  7 , 14 ,  5 , 12,  3 , 10 ,  1 ,  8 , 15 ,  6 , 13 ,  4 , 11 ,  2 ,  9 ]

def F(a, b, c):
        return (a & b) | ((~a) & c)
def G(a, b, c):
        return (a & c) | (b & (~c))
def H(a, b, c):
        return a ^ b ^ c
def I(a, b, c):
        return b ^ (a | (~c))

def CLS(m, n):
    m &= 0xFFFFFFFF
    return (( m << n ) | (m >> (32 - n))) & 0xFFFFFFFF

def F_(x, t, s):
    global A, B, C, D
    g = F(B, C, D)
    tem = g + A + int(x,2) + t
    te = CLS(tem, s)
    a1 = (te + B) % pow(2, 32)
    a2, a3, a4 = B, C, D
    A, B, C, D = a4, a1, a2, a3

def G_(x, t, s):
    global A, B, C, D
    g = G(B, C, D)
    tem = g + A + int(x,2) + t
    te = CLS(tem, s)
    a1 = (te + B) % pow(2, 32)
    a2, a3, a4 = B, C, D
    A, B, C, D = a4, a1, a2, a3

def H_(x, t, s):
    global A, B, C, D
    g = H(B, C, D)
    tem = g + A + int(x,2) + t
    te = CLS(tem, s)
    a1 = (te + B) % pow(2, 32)
    a2, a3, a4 = B, C, D
    A, B, C, D = a4, a1, a2, a3

def I_(x, t, s):
    global A, B, C, D
    g = I(B, C, D)
    tem = g + A + int(x,2) + t
    te = CLS(tem, s)
    a1 = (te + B) % pow(2, 32)
    a2, a3, a4 = B, C, D
    A, B, C, D = a4, a1, a2, a3

# 打开文件
with open('jsf.jpg', 'rb') as f:
    Data = f.read()
#Data = b"hello world"
data = ''.join(format(byte, '08b') for byte in Data)  
# 填充数据
length = len(data)
if length % 512 == 448:
    data += '1'
    data += '0' * 511
else:
    t = (length + 64) % 512
    te = 512 - t
    if te != 1:
        data += '1'
        data += '0' * (te - 1)
    else:
        data += '0'
length_padding = struct.pack('<Q', length)  
length_padding_bits = ''.join(format(byte, '08b') for byte in length_padding)  
data += length_padding_bits
#循环加密
#求出M

for i in range(0, len(data), 512):
    M = []
    cur_group = data[i : i + 512]
    
    for j in range(0, 512, 32):
        num = ""
        # 获取每一段的标准十六进制形式
        for k in range(0, len(cur_group[j:j+32]), 4):
            temp = cur_group[j:j+32][k:k+4]
            temp = hex(int(temp, 2))
            num += temp[2]
        # 对十六进制进行小端排序
        num_tmp = ""
        for k in range(8, 0, -2):
            temp = num[k-2:k]
            num_tmp += temp
        mm = ""
        for k in range(len(num_tmp)):
            num = int(num_tmp[k], 16)
            bin_num = bin(num)[2:].zfill(4)
            mm += bin_num
        M.append(mm)
    for ii in range(0, 16):
        F_(M[m[ii]], T[ii], S[ii])
    for ii in range(0, 16):
        G_(M[m[ii + 16]], T[16 + ii], S[16 + ii])
    for ii in range(0, 16):            
        H_(M[m[ii + 32]], T[32 + ii], S[32 + ii])
    for ii in range(0, 16): 
        I_(M[m[ii + 48]], T[48 + ii], S[48 + ii])
    A = (A + AA) % pow(2, 32)
    B = (B + BB) % pow(2, 32)
    C = (C + CC) % pow(2, 32)
    D = (D + DD) % pow(2, 32)
    AA, BB, CC, DD = A, B, C, D
    
A_bytes = struct.pack('<I', A)
B_bytes = struct.pack('<I', B)
C_bytes = struct.pack('<I', C)
D_bytes = struct.pack('<I', D)

# 级联这些字节串
md5_digest = A_bytes + B_bytes + C_bytes + D_bytes

# 将级联后的字节串转换为16进制格式的字符串
md5_hex = ''.join(f'{byte:02x}' for byte in md5_digest)

print("MD5加密值:",md5_hex)

六、实验总结

        这个实验我是参考了网上很多代码,本身这个算法也比较简单,只要理解透彻了其实不算很难。当然说着不难还是有点难的,我感觉令我收获最大的就是修改代码的过程。因为我借鉴的那篇博客(上文提到)他只能运行通过小于512bit的内容,我就一点一点对着修改。不是对着代码改,是对着输出的结果改。就是参照它的输出结果和我的输出结果,中间步骤的运行结果,比如填充的结果、一步加密的结果、一轮加密中间量的结果等等,一点一点对着修改,最后成功写出来。

        第一次写博客,之后还会多多写,确实写一遍之后收获蛮大!

        哦哦,最后还有一点,就是我是怎么验证的。在linux环境下,输入

md5sum jsf.jpg

        即可得到某文件的MD5加密结果,这个结果与代码运行结果是一致的。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值