<jupyter_start><jupyter_text>MD5 Python Implementation<jupyter_code>class MD5:
    
    def __init__(self, message):
        """
        the message is the string format of a hex
        """
        self.message = message
    
    def padding(self):
        """
        return the padded hex with a string format
        """
        pad_len_1 = 112 - (len(self.message) % 112) - 1
        m_len = hex(len(self.message)*4)[2:]
        if len(m_len) > 2:
            if len(m_len) % 2 != 0: ML = '0' + m_len
            m_len = ML[-2:]
            for i in range(int(len(ML)/2)-1):
                m_len += ML[-2*(i+1)-2:-2*(i+1)]
        pad_len_2 = 16 - (len(m_len) % 16)
        self.m_pad = self.message + '8' + '0' * pad_len_1
        self.m_pad = self.m_pad + '0' * int(len(self.m_pad)/112 - 1) * 16 + m_len + '0' * pad_len_2
        return self.m_pad
    
    def partitioning(self):
        """
        return 
        1. the block messages in a list
        2. the number of N
        """
        self.m_list = []
        self.num_block = int(len(self.m_pad)/8)
        for i in range(self.num_block):
            temp = self.m_pad[i*8 : (i+1)*8]
            temp = temp[6:8] + temp[4:6] + temp[2:4] + temp[:2]
            self.m_list.append(temp)
        return self.m_list, int(self.num_block/16)
    
    def int32Control(self, x: int):
        """
        overflow control
        """
        if x > 2147483647: return -2147483648 + (x - 0x7fffffff) - 1
        elif x < -2147483648: return 2147483647 - (-2147483648 - x) + 1
        else: return x
        
    def AC(self, t: int):
        """ 
        return the decimal Addition Constant ACt for each step
        """
        assert(t < 64)
        from math import sin
        return self.int32Control(int(2**32*abs(sin(1+t))))
    
    def Ft(self, X, Y, Z, t: int):
        """
        return the output from the non-linear function
        """
        assert(t < 64)
        if t >= 0 and t < 16: return self.int32Control((X & Y) ^ ((~X) & Z))
        elif t >= 16 and t < 32: return self.int32Control((Z & X) ^ ((~Z) & Y))
        elif t >= 32 and t < 48: return self.int32Control(X ^ Y ^ Z) 
        else: return self.int32Control((Y ^ (X | (~Z))))
        
    def RC(self, t: int):
        """
        return the Rotation Constant RCt, RCt+1, RCt+2, RCt+3
        """
        assert(t < 64)
        if t >=0 and t < 16:
            if t % 4 == 0: return 7
            elif t % 4 == 1: return 12
            elif t % 4 == 2: return 17
            else: return 22
        elif t >= 16 and t < 32:
            if t % 4 == 0: return 5
            elif t % 4 == 1: return 9
            elif t % 4 == 2: return 14
            else: return 20
        elif t >=32 and t < 48:
            if t % 4 == 0: return 4
            elif t % 4 == 1: return 11
            elif t % 4 == 2: return 16
            else: return 23
        else:
            if t % 4 == 0: return 6
            elif t % 4 == 1: return 10
            elif t % 4 == 2: return 15
            else: return 21
    
    def rot(self, input_int, rc):
        """
        return cyclic rotation
        """
        input_int = self.int32Control(input_int)
        if input_int >= 0:
            input_bin = bin(input_int)[2:]
        else:
            input_bin = bin(0xffffffff + input_int + 1)[2:]
        if (len(input_bin)<32): input_bin = '0' * (32-len(input_bin)) + input_bin
        return self.int32Control(int('0b0' + input_bin[rc%len(input_bin):]+input_bin[:rc%len(input_bin)], 2))
    
    def Wt(self, N = 1, t = 0):
        """
        return the word for each step
        """
        if t>=0 and t < 16: return self.int32Control(int(self.m_list[16*(N-1) + t], 16))
        if t>=16 and t < 32: return self.int32Control(int(self.m_list[16*(N-1) + (1+5*t)%16], 16))
        if t>=32 and t < 48: return self.int32Control(int(self.m_list[16*(N-1) + (5+3*t)%16], 16))
        if t>=48 and t < 64: return self.int32Control(int(self.m_list[16*(N-1) + (7*t)%16], 16))
    
    def MD5Hash(self, Q0=0x67452301, Q1=0xefcdab89, Q2=0x98badcfe, Q3=0x10325476, N=1):
        """
        return the hash value of the message
        """
        a = self.int32Control(Q0)
        b = self.int32Control(Q1)
        c = self.int32Control(Q2)
        d = self.int32Control(Q3)
        
        for t in range(64): # the number of step
            addition = self.int32Control(self.AC(t) + self.Wt(N, t) + a + self.Ft(b,c,d,t))
            rotation = self.rot(addition, self.RC(t))
            (a, b, c, d) = (d, 
                            self.int32Control(b + rotation),
                            b,
                            c
                           )
        
        out = [self.int32Control(a+Q0), self.int32Control(b+Q1), self.int32Control(c+Q2), self.int32Control(d+Q3)]
        Hash = ""
        for i in range(4):
            if out[i] < 0: temp = hex(0xffffffff + out[i] + 1)[2:]
            else: temp = hex(out[i])[2:]
            if len(temp) < 8: temp = '0' * (8-len(temp)) + temp
            temp = temp[6:8] + temp[4:6] + temp[2:4] + temp[:2]
            Hash += temp
            
        return Hash, Q0+a, Q1+b, Q2+c, Q3+d

def encode_md5(message: str):
    out = ''
    for i in range(len(message)):
        out += hex(ord(message[i]))[2:]
    return out

def hash_md5(message: str):
    """
    Instance of implementation
    """
    obj = MD5(message)
    new_message = obj.padding()
    block_message, N = obj.partitioning()
    if N == 1: 
        hash_message, _, _, _, _ = obj.MD5Hash()
    else:
        _, A, B, C, D = obj.MD5Hash()
        for n in range(2, N+1):
            if n == N: 
                hash_message, _, _, _, _ = obj.MD5Hash(A, B, C, D, n)
            else: _, A, B, C, D = obj.MD5Hash(A, B, C, D, n)
    return hash_message, new_message
# Example
message = "TU Delft"
encode_message = encode_md5(message)
hash_message, _ = hash_md5(encode_message)
print(hash_message)<jupyter_output>467764e7f10c77695db73ab3d414c65d<jupyter_text>MD5 by HashLib<jupyter_code>import hashlib                    
md5_object = hashlib.md5()       
md5_object.update(b"TU Delft")    
md5_result = md5_object.hexdigest() 
print(md5_result)<jupyter_output>467764e7f10c77695db73ab3d414c65d
{'fraction_non_alphanumeric': 0.1417814802947773, 'fraction_numerical': 0.0680871515539891, 'mean_word_length': 1.746590409150902, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 3, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '465537', 'license': 'MIT', 'n_tokens_mistral': 2485, 'n_tokens_neox': 2229, 'n_words': 570, 'path': 'MD5Encryption.ipynb', 'repo_name': 'Atherzzz/MD5_Collision', 'size': '6242'}