1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| from struct import pack, unpack from math import floor, sin
""" MD5 Extension Attack ====================
@refs https://github.com/shellfeel/hash-ext-attack """
class MD5:
def __init__(self): self.A, self.B, self.C, self.D = \ (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476) self.r: list[int] = \ [7, 12, 17, 22] * 4 + [5, 9, 14, 20] * 4 + \ [4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4 self.k: list[int] = \ [floor(abs(sin(i + 1)) * pow(2, 32)) for i in range(64)]
def _lrot(self, x: int, n: int) -> int: return (x << n) | (x >> 32 - n)
def update(self, chunk: bytes) -> None: w = list(unpack('<'+'I'*16, chunk)) a, b, c, d = self.A, self.B, self.C, self.D
for i in range(64): if i < 16: f = (b & c) | ((~b) & d) flag = i elif i < 32: f = (b & d) | (c & (~d)) flag = (5 * i + 1) % 16 elif i < 48: f = (b ^ c ^ d) flag = (3 * i + 5) % 16 else: f = c ^ (b | (~d)) flag = (7 * i) % 16
tmp = b + \ self._lrot((a + f + self.k[i] + w[flag]) & 0xffffffff, self.r[i]) a, b, c, d = d, tmp & 0xffffffff, b, c
self.A = (self.A + a) & 0xffffffff self.B = (self.B + b) & 0xffffffff self.C = (self.C + c) & 0xffffffff self.D = (self.D + d) & 0xffffffff
def extend(self, msg: bytes) -> None: assert len(msg) % 64 == 0 for i in range(0, len(msg), 64): self.update(msg[i:i + 64])
def padding(self, msg: bytes) -> bytes: length = pack('<Q', len(msg) * 8)
msg += b'\x80' msg += b'\x00' * ((56 - len(msg)) % 64) msg += length
return msg
def digest(self) -> bytes: return pack('<IIII', self.A, self.B, self.C, self.D)
def verify_md5(test_string: bytes) -> None: from hashlib import md5 as md5_hashlib
def md5_manual(msg: bytes) -> bytes: md5 = MD5() md5.extend(md5.padding(msg)) return md5.digest()
manual_result = md5_manual(test_string).hex() hashlib_result = md5_hashlib(test_string).hexdigest()
assert manual_result == hashlib_result, "Test failed!"
def attack(message_len: int, known_hash: str, append_str: bytes) -> tuple: md5 = MD5()
previous_text = md5.padding(b"*" * message_len) current_text = previous_text + append_str
md5.A, md5.B, md5.C, md5.D = unpack("<IIII", bytes.fromhex(known_hash)) md5.extend(md5.padding(current_text)[len(previous_text):])
return current_text[message_len:], md5.digest().hex()
if __name__ == '__main__':
message_len = int(input("[>] Input known text length: ")) known_hash = input("[>] Input known hash: ").strip() append_text = input("[>] Input append text: ").strip().encode()
print("[*] Attacking...")
extend_str, final_hash = attack(message_len, known_hash, append_text)
from urllib.parse import quote from base64 import b64encode
print("[+] Extend text:", extend_str) print("[+] Extend text (URL encoded):", quote(extend_str)) print("[+] Extend text (Base64):", b64encode(extend_str).decode()) print("[+] Final hash:", final_hash)
|