Python实现MD5弱密码批量检测:从字典攻击原理到安全自检实践

📅 2026/7/20 14:19:08 👁️ 阅读次数 📝 编程学习
Python实现MD5弱密码批量检测:从字典攻击原理到安全自检实践

1. 项目概述:从“撞库”到“精准破解”的思维转变

看到这个标题,很多朋友的第一反应可能是“这不就是教人搞破坏吗?”。先别急着下结论,让我从一个安全从业者的角度来聊聊这件事。在网络安全领域,尤其是渗透测试和系统安全评估中,密码强度测试是一项基础且至关重要的工作。我们常说的“撞库”,是指攻击者利用从其他网站泄露的用户名和密码组合,去批量尝试登录目标网站。这种方法效率低下、动静大,且极易触发安全警报。而标题里提到的“批量破解MD5弱密码”,其核心场景恰恰相反:它通常是防御方(如安全工程师、系统管理员)在获得了一批经过哈希处理(如MD5)的密码密文后,为了评估系统内用户密码的健壮性,而进行的合法、授权的安全测试。

我干了十多年安全,见过太多因为弱密码导致的“血案”。一个简单的123456password,经过MD5哈希后变成一串看似随机的字符,就常常被开发者或管理员误认为“已经加密了,很安全”。这种误解是致命的。MD5作为一种古老的哈希算法,其抗碰撞性早已被攻破,且对于弱密码而言,根本无需破解算法本身,通过“查表”(彩虹表)或“暴力枚举”常见密码组合的方式,几乎可以瞬间还原。这个Python脚本项目,目的就是自动化这个过程,让你能快速、批量地检验一批MD5值背后是否是那些不堪一击的弱密码。

这个脚本适合谁?如果你是运维工程师,需要检查服务器用户表导出的密码哈希是否安全;如果你是开发者,想自测数据库里存储的用户密码哈希强度;或者你是一名正在学习网络安全的学生,想理解密码哈希与破解的实操原理——那么,这个工具和背后的思路就是为你准备的。它的价值不在于“攻击”,而在于“自检”和“教育”,让你直观地看到弱密码在MD5面前有多么脆弱,从而在设计和运维中彻底杜绝它们。

2. 核心原理与方案设计:为什么选择“字典攻击”而非“暴力破解”

要理解这个脚本,首先要搞清楚MD5和密码破解的基本原理。MD5是一种单向哈希函数,它可以将任意长度的输入(你的密码),计算成一个固定长度(128位,通常表示为32位十六进制字符串)的输出。这个过程理论上不可逆,即无法从哈希值反推出原始密码。那么“破解”是如何发生的呢?方法主要有两种:暴力破解和字典攻击。

暴力破解就是尝试所有可能的字符组合,从azzzzzz...,计算每个组合的MD5值并与目标哈希比对。这种方法理论上绝对能成功,但时间成本是天文数字。一个仅由小写字母和数字组成的8位密码,就有 (26+10)^8 种可能,即使每秒能计算100万次哈希,也需要数年时间。显然,对于批量、快速的弱密码检测,这不现实。

字典攻击则是基于一个聪明的观察:绝大多数人设置的密码,并非完全随机的字符串,而是有规律可循的常见词汇、姓名、日期或简单变体。因此,我们可以预先准备一个“弱密码字典”,里面包含了123456passwordadminqwerty生日公司名+123等成千上万条常见的密码。攻击时,只需遍历这个字典,计算每个词的MD5值进行比对即可。由于字典规模(通常几万到几百万条)远小于暴力破解的空间,速度极快,专门用于揪出那些最脆弱的密码。

我们的Python脚本,核心就是实现一个高效的“基于字典的MD5哈希批量比对器”。方案设计围绕以下几个关键点:

  1. 字典管理:需要一个高质量、持续更新的弱密码字典作为输入源。字典的质量直接决定检测效果。
  2. 哈希计算效率:MD5计算本身很快,但批量处理数百万次计算时,代码效率依然关键。我们会使用Python的内置hashlib库,并注意循环优化。
  3. 批量处理能力:脚本需要能读取一个包含多个目标MD5哈希值的文件(每行一个),并针对每个哈希,遍历整个字典进行比对。这里涉及文件I/O和循环逻辑。
  4. 结果输出与可读性:需要清晰地将破解成功的对应关系(MD5值 -> 明文密码)输出,并统计成功率,方便生成报告。
  5. 可扩展性:虽然核心是MD5,但代码结构应易于扩展,以支持SHA-1、SHA-256等其他哈希算法。

注意:法律与道德边界必须再次强调,此脚本及字典仅限用于您拥有合法权限的系统进行安全审计、教学研究或个人学习测试。未经授权对任何系统进行密码破解尝试都是非法行为。请务必在隔离的测试环境(如虚拟机)中运行此类工具。

3. 工具准备与字典获取:工欲善其事,必先利其器

在动手写代码之前,我们需要准备好两样东西:Python环境和弱密码字典。

3.1 Python环境配置脚本使用标准Python 3.6+环境即可,无需复杂第三方库。如果你还没安装Python,可以去官网下载安装包。安装后,打开终端(Windows是CMD或PowerShell,Mac/Linux是Terminal),输入python --version检查是否安装成功。一个常见的坑是,在Windows上可能同时安装了Python 2和Python 3,命令python可能指向旧版本。你可以尝试用python3命令,或者确保安装时勾选了“Add Python to PATH”选项。

对于代码编辑,我强烈推荐使用VSCode。它轻量、免费,且有强大的Python插件支持。安装VSCode后,搜索并安装官方“Python”扩展。这个扩展提供了代码高亮、智能提示、调试等功能,能极大提升开发效率。你不需要配置复杂的虚拟环境(virtualenv)来运行这个简单的脚本,直接用系统Python环境即可。

3.2 弱密码字典的搜集与选择字典是脚本的“弹药库”。网络上有很多开源、共享的弱密码字典。这里我推荐几个常用且质量较高的来源:

  • SecLists:这是一个在GitHub上非常著名的安全测试资源集合,其中的Passwords目录包含了从历年重大数据泄露事件中整理出的常见密码、弱密码字典,如rockyou.txt(源于一次著名的数据泄露,包含1400万条密码)。你可以直接下载整个项目或单独的字典文件。
  • Weakpass:一个专注于提供各种语言、场景弱密码字典的网站,可以根据需要下载不同大小的字典包。
  • 自行生成:对于特定目标(如某公司),可以结合社会工程学信息,使用工具(如crunchCUPP)生成定制字典,包含公司名、产品名、当地常见词汇等变体。

对于本次实战,我建议从一个中等规模的字典开始,比如rockyou.txt的精简版(前100万行),或者SecLists中的common-passwords-win.txt。太大的字典(几个GB)首次运行可能会比较慢。将下载的字典文件(例如weak_passwords.txt)放在你的项目文件夹里。

实操心得:字典的预处理下载的原始字典可能包含空行、重复项或非UTF-8编码字符。在脚本中直接读取可能会出错。一个稳健的做法是,在脚本开头或使用一个预处理脚本,对字典进行清洗:去除重复、过滤无效字符。这能提升后续比对的效率和稳定性。你可以用一段简单的Python预处理代码来完成:

# preprocess_dict.py - 字典预处理脚本 input_file = ‘weak_passwords_raw.txt‘ output_file = ‘clean_passwords.txt‘ seen = set() with open(input_file, ‘r‘, encoding=‘utf-8‘, errors=‘ignore‘) as f_in, \ open(output_file, ‘w‘, encoding=‘utf-8‘) as f_out: for line in f_in: password = line.strip() # 去除首尾空白字符 if password and password not in seen: # 非空且未重复 seen.add(password) f_out.write(password + ‘\n‘) print(f“字典清洗完成。原始行数未知,去重后保留 {len(seen)} 条。“)

4. 脚本核心代码实现与逐行解析

接下来,我们进入核心环节:编写Python脚本。我将把脚本拆解成几个功能模块,并逐行解释其作用和背后的考量。

4.1 脚本框架与参数解析一个健壮的脚本应该支持命令行参数,这样更灵活。我们使用Python内置的argparse库。

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ MD5弱密码批量破解脚本 用途:通过字典攻击方式,批量尝试破解给定的MD5哈希值。 仅用于授权安全测试与教育目的。 """ import argparse import hashlib import sys from pathlib import Path from typing import Optional def parse_arguments(): """解析命令行参数""" parser = argparse.ArgumentParser(description=‘MD5弱密码批量破解脚本‘) parser.add_argument(‘-d‘, ‘--dict‘, required=True, help=‘弱密码字典文件路径 (每行一个密码)‘) parser.add_argument(‘-t‘, ‘--target‘, required=True, help=‘目标MD5哈希文件路径 (每行一个MD5哈希)‘) parser.add_argument(‘-o‘, ‘--output‘, help=‘结果输出文件路径 (可选,不指定则打印到屏幕)‘) parser.add_argument(‘--encoding‘, default=‘utf-8‘, help=‘字典文件编码 (默认: utf-8)‘) return parser.parse_args()
  • argparse让脚本可以通过python md5_cracker.py -d dict.txt -t hashes.txt这样的命令运行。
  • required=True确保必须提供字典和目标哈希文件。
  • 添加了encoding参数是因为字典文件可能来自不同系统,编码各异(如gbk,latin-1),指定编码可以避免读取时乱码或崩溃。

4.2 核心破解函数:单哈希破解这是脚本的引擎,负责对一个MD5哈希值进行字典攻击。

def crack_md5(target_hash: str, dict_path: Path, encoding: str = ‘utf-8‘) -> Optional[str]: """ 尝试破解单个MD5哈希。 参数: target_hash: 目标MD5哈希字符串 (32位十六进制,不区分大小写) dict_path: 弱密码字典文件路径 encoding: 字典文件编码 返回: 如果破解成功,返回明文密码;否则返回None。 """ # 首先,规范化目标哈希:去除空白字符,转为小写 target_hash = target_hash.strip().lower() if len(target_hash) != 32: # MD5哈希长度固定为32位十六进制 print(f“警告: 哈希值 ‘{target_hash}‘ 长度不为32,已跳过。“) return None try: with open(dict_path, ‘r‘, encoding=encoding, errors=‘ignore‘) as f: for line_num, line in enumerate(f, 1): password = line.rstrip(‘\n\r‘) # 只去除行尾换行符,保留可能的前后空格(有时密码就是空格) # 计算当前密码的MD5 # 注意:必须将字符串编码为字节流,hashlib才能处理 hash_obj = hashlib.md5(password.encode(‘utf-8‘)) hash_hex = hash_obj.hexdigest() # 比对哈希值 if hash_hex == target_hash: print(f“[+] 在第 {line_num} 行破解成功: {target_hash} -> {password}“) return password except FileNotFoundError: print(f“错误: 字典文件未找到 - {dict_path}“) sys.exit(1) except UnicodeDecodeError as e: print(f“错误: 使用编码 ‘{encoding}‘ 读取字典文件失败。请尝试指定 --encoding 参数。错误详情: {e}“) sys.exit(1) # 遍历完整个字典未找到匹配项 return None
  • target_hash.strip().lower():用户输入的哈希值可能有空格或大小写混用,统一格式化可以避免比对失败。
  • len(target_hash) != 32:一个简单的有效性校验,过滤掉明显无效的输入。
  • enumerate(f, 1):在遍历字典时同时获取行号,方便在破解成功时定位到字典中的位置,便于后续分析(例如,某个密码在字典很靠前的位置,说明极其常见)。
  • password.encode(‘utf-8‘):这是关键一步。hashlib.md5()需要输入bytes类型,所以必须将字符串密码编码。这里统一使用UTF-8编码,确保一致性。如果字典中的密码包含特殊字符,UTF-8有更好的兼容性。
  • errors=‘ignore‘:在打开字典文件时使用,即使遇到一些无法解码的字符,也会忽略并继续读取,避免因个别乱码导致整个脚本中断。
  • 函数返回破解出的密码,便于主流程记录。

4.3 批量处理与主流程主函数将串联整个流程:读取目标哈希文件,对每个哈希调用破解函数,并管理结果输出。

def main(): args = parse_arguments() dict_file = Path(args.dict) target_file = Path(args.target) # 1. 检查输入文件是否存在 if not dict_file.is_file(): print(f“错误: 字典文件不存在 - {dict_file}“) sys.exit(1) if not target_file.is_file(): print(f“错误: 目标哈希文件不存在 - {target_file}“) sys.exit(1) # 2. 读取目标哈希列表 try: with open(target_file, ‘r‘) as f: target_hashes = [line.strip() for line in f if line.strip()] except Exception as e: print(f“读取目标哈希文件失败: {e}“) sys.exit(1) if not target_hashes: print(“目标哈希文件为空。“) sys.exit(0) print(f“[*] 加载了 {len(target_hashes)} 个目标MD5哈希。“) print(f“[*] 开始使用字典 ‘{dict_file}‘ 进行破解...“) # 3. 准备结果存储 cracked_results = [] total_targets = len(target_hashes) # 4. 逐个哈希进行破解 for idx, target_hash in enumerate(target_hashes, 1): print(f“\r[*] 进度: {idx}/{total_targets}“, end=““, flush=True) password = crack_md5(target_hash, dict_file, args.encoding) if password: cracked_results.append((target_hash, password)) print() # 换行,结束进度行 # 5. 输出结果 cracked_count = len(cracked_results) success_rate = (cracked_count / total_targets * 100) if total_targets > 0 else 0 print(f“\n[+] 破解完成!“) print(f“[*] 总计目标: {total_targets}“) print(f“[*] 成功破解: {cracked_count}“) print(f“[*] 破解成功率: {success_rate:.2f}%“) if cracked_results: if args.output: output_path = Path(args.output) try: with open(output_path, ‘w‘, encoding=‘utf-8‘) as f_out: f_out.write(“MD5_Hash,Plaintext_Password\n“) for h, p in cracked_results: f_out.write(f“{h},{p}\n“) print(f“[+] 结果已保存至: {output_path}“) except Exception as e: print(f“写入输出文件失败: {e},结果将打印在下方:“) print_results(cracked_results) else: print(“\n[+] 破解结果明细:“) print_results(cracked_results) else: print(“[-] 未破解任何哈希。请尝试使用更大或更针对性的字典。“) def print_results(results): """格式化打印破解结果""" for hash_val, pwd in results: print(f“ {hash_val} : {pwd}“) if __name__ == ‘__main__‘: main()
  • Path来自pathlib库,提供了更面向对象、跨平台的文件路径操作方式,比直接拼接字符串更安全。
  • 进度提示print(f“\r[*] 进度: {idx}/{total_targets}“, end=““, flush=True):使用\r回车符实现单行动态更新进度,flush=True确保立即输出,避免缓冲。这对于处理大量哈希时,让用户感知脚本在运行而非卡死,是很重要的体验优化。
  • 结果输出提供了两种方式:命令行标准输出和写入CSV文件。CSV格式(MD5_Hash,Plaintext_Password)可以被Excel、数据库等工具直接导入,方便后续分析报告。
  • 计算并展示破解成功率,这是一个直观的指标,能快速评估当前字典对目标哈希集的有效性。

5. 实战演练:从准备到运行的全过程

现在,让我们用一个完整的例子,把脚本跑起来。

5.1 准备测试数据首先,我们创建两个测试文件。

  1. 目标哈希文件target_hashes.txt:里面包含一些常见弱密码的MD5值。你可以用在线工具或Python交互环境生成。

    e10adc3949ba59abbe56e057f20f883e # 123456 5f4dcc3b5aa765d61d8327deb882cf99 # password 21232f297a57a5a743894a0e4a801fc3 # admin 25d55ad283aa400af464c76d713c07ad # 12345678 7c6a180b36896a0a8c02787eeafb0e4c # password1 (假设字典里有) d41d8cd98f00b204e9800998ecf8427e # 空字符串

    注意:最后一行d41d8cd98f00b204e9800998ecf8427e是空字符串的MD5。这提醒我们,有些系统可能存在空密码漏洞,我们的字典里也应该包含一个空行(或显式的空字符串)来检测这种情况。

  2. 弱密码字典文件my_dict.txt:内容如下。

    123456 password admin 12345678 qwerty letmein welcome (这里可以是一个空行,代表空密码) monkey dragon

    将这两个文件保存在与脚本相同的目录下。

5.2 运行脚本打开终端,导航到脚本所在目录,执行命令:

python md5_cracker.py -d my_dict.txt -t target_hashes.txt -o results.csv

如果一切正常,你将看到类似以下输出:

[*] 加载了 6 个目标MD5哈希。 [*] 开始使用字典 ‘my_dict.txt‘ 进行破解... [+] 在第 1 行破解成功: e10adc3949ba59abbe56e057f20f883e -> 123456 [+] 在第 2 行破解成功: 5f4dcc3b5aa765d61d8327deb882cf99 -> password [+] 在第 3 行破解成功: 21232f297a57a5a743894a0e4a801fc3 -> admin [+] 在第 4 行破解成功: 25d55ad283aa400af464c76d713c07ad -> 12345678 [*] 进度: 6/6 [+] 破解完成! [*] 总计目标: 6 [*] 成功破解: 4 [*] 破解成功率: 66.67% [+] 结果已保存至: results.csv

打开results.csv,你会看到四行成功破解的记录。

5.3 结果分析与优化这次运行破解了4个,成功率66.7%。password1和空密码没有被破解,因为我们的测试字典里没有password1这个词条,也没有包含空行(或者我们没加)。这演示了字典的覆盖度直接影响结果。

为了提高破解率,你可以:

  1. 使用更大的字典:换上之前提到的rockyou.txt
  2. 对字典进行规则变换:很多人喜欢在基础密码上加后缀(如password123)、前缀(如123password)或大小写变换(如Password)。可以使用像Hashcat工具的规则引擎,或者自己写一个简单的Python脚本对基础字典进行变换生成新字典。例如:
    # 简单的规则变换示例 base_words = [‘password‘, ‘admin‘] suffixes = [‘‘, ‘123‘, ‘!‘, ‘2023‘] expanded_dict = [] for word in base_words: for suf in suffixes: expanded_dict.append(word + suf) expanded_dict.append(word.capitalize() + suf) # 将 expanded_dict 写入文件
  3. 针对性补充字典:如果是对特定公司或系统测试,加入公司名、产品名、本地常见词汇等。

6. 性能优化与高级技巧

当字典很大(上GB)或目标哈希非常多时,基础脚本可能会运行较慢。以下是几个关键的优化方向:

6.1 使用内存哈希表(彩虹表思想)最直接的优化是“空间换时间”。与其为每个目标哈希遍历整个字典(时间复杂度 O(N_target * N_dict)),不如先将整个字典的密码及其MD5预先计算好,存入一个Python字典(哈希表)中。这样,破解每个目标哈希就只是一次快速的字典查找操作(O(1))。

def build_md5_lookup_table(dict_path: Path, encoding: str = ‘utf-8‘) -> dict: """预计算字典中所有密码的MD5,构建查找表""" lookup_table = {} print(“[*] 正在预计算字典哈希表,这可能需要一些时间...“) try: with open(dict_path, ‘r‘, encoding=encoding, errors=‘ignore‘) as f: for line in f: password = line.rstrip(‘\n\r‘) if password: # 忽略空行 hash_hex = hashlib.md5(password.encode(‘utf-8‘)).hexdigest() # 一个MD5可能对应多个密码(碰撞或不同密码产生相同哈希极罕见,但以防万一) lookup_table.setdefault(hash_hex, []).append(password) print(f“[+] 哈希表构建完成,共 {len(lookup_table)} 个唯一MD5条目。“) return lookup_table except Exception as e: print(f“构建哈希表失败: {e}“) sys.exit(1) # 在主函数中,先构建查找表 lookup_table = build_md5_lookup_table(dict_file, args.encoding) cracked_results = [] for target_hash in target_hashes: target_hash = target_hash.strip().lower() if target_hash in lookup_table: # 如果找到,可能有多个密码对应同一个哈希(理论碰撞) for pwd in lookup_table[target_hash]: cracked_results.append((target_hash, pwd)) print(f“[+] 破解成功: {target_hash} -> {pwd}“)

优点:当目标哈希数量很多时,速度提升极其显著。缺点:需要足够的内存来存储整个查找表。一个包含1亿条密码的字典,其MD5查找表大约需要 100,000,000 * (32+平均密码长度) Bytes,可能超过10GB内存。因此,这只适用于字典不是特别大的情况,或者你有充足的内存资源。

6.2 多进程并行计算如果内存有限,或者字典巨大无法全部装入查找表,我们可以利用多核CPU进行并行计算。Python的concurrent.futures库提供了简洁的接口。

思路是将目标哈希列表分成多个块,每个进程处理一个块,共享同一个字典文件句柄(注意文件读取的线程安全)或各自读取字典。更高效的方式是让每个进程负责字典的一个子集,但这样逻辑复杂。一个简单的实现是让每个进程独立处理一批目标哈希,并完整遍历字典(进程间任务独立)。

from concurrent.futures import ProcessPoolExecutor, as_completed def crack_hash_batch(args): """单个工作进程的任务函数:破解一批哈希""" hash_list, dict_path, encoding = args local_results = [] dict_path = Path(dict_path) for target_hash in hash_list: pwd = crack_md5(target_hash, dict_path, encoding) # 复用之前的函数 if pwd: local_results.append((target_hash, pwd)) return local_results def main_parallel(): args = parse_arguments() # ... 文件检查等代码 ... # 将目标哈希列表分成若干份,份数等于CPU核心数 import os cpu_count = os.cpu_count() or 4 hash_batches = [list() for _ in range(cpu_count)] for i, h in enumerate(target_hashes): hash_batches[i % cpu_count].append(h) all_results = [] print(f“[+] 使用 {cpu_count} 个进程进行并行破解...“) with ProcessPoolExecutor(max_workers=cpu_count) as executor: # 准备任务参数 task_args = [(batch, args.dict, args.encoding) for batch in hash_batches if batch] # 提交任务 future_to_batch = {executor.submit(crack_hash_batch, arg): i for i, arg in enumerate(task_args)} for future in as_completed(future_to_batch): batch_results = future.result() all_results.extend(batch_results) # ... 后续结果处理和输出 ...

优点:充分利用多核CPU,显著提升计算密集型任务的效率。缺点:进程间通信有开销,如果字典文件很大,每个进程都需要读取一遍,可能造成I/O瓶颈。对于纯计算任务(如哈希计算)效果很好。

6.3 处理加盐(Salt)的MD5在实际系统中,为了增加破解难度,几乎不会直接存储密码的MD5值,而是会存储“密码+盐(Salt)”的MD5值。盐是一个随机字符串,每个用户不同,与密码拼接后再哈希。即使两个用户密码相同,由于盐不同,哈希值也不同,使得彩虹表攻击失效。

我们的脚本目前无法直接破解加盐哈希。但如果你知道盐值,脚本可以很容易地修改以适应。核心修改在计算哈希的那一步:

# 假设盐是固定的,或者可以从目标哈希数据中获取(例如,哈希值格式为 `salt:hash`) salt = “random_salt_string“ hash_obj = hashlib.md5((salt + password).encode(‘utf-8‘)) # 或者 password + salt,取决于系统实现 # 或者从每个目标哈希中解析出盐 # target_hash 格式可能是 “c4f9375f9834b4e7f3a9c6b2d8f1a0e2:random_salt“ # hash_value, salt = target_hash.split(‘:‘)

在真实安全评估中,获取盐值通常需要结合其他漏洞(如源代码泄露、配置文件泄露等)。如果盐值未知且随机,那么针对单个哈希的字典攻击将退化为暴力破解,可行性大大降低。

7. 常见问题、排查技巧与安全建议

7.1 脚本运行常见问题

  • 问题:UnicodeDecodeError错误

    • 现象:运行脚本时提示类似‘gbk‘ codec can‘t decode byte 0x80 in position 1024的错误。
    • 原因:字典文件的编码与脚本默认(或指定)的编码不匹配。rockyou.txt等字典常用latin-1编码。
    • 解决:使用--encoding latin-1参数运行脚本。或者用文本编辑器(如Notepad++)将字典文件转换为UTF-8编码再使用。
  • 问题:脚本运行缓慢,进度几乎不动

    • 原因1:字典文件非常大(几个GB),每次读取都耗时。
    • 排查:先使用一个小字典测试脚本逻辑是否正确。对于大字典,考虑使用上述的“内存哈希表”优化(如果内存足够),或者使用更高效的语言(如C/C++)重写核心循环。
    • 原因2:目标哈希文件中有大量无效或格式错误的行,导致crack_md5函数中频繁打印警告。
    • 排查:检查目标哈希文件,确保每行是32位十六进制字符串。可以在读取后添加清洗步骤:if re.match(r‘^[a-fA-F0-9]{32}$‘, line.strip()):
  • 问题:破解成功率为0

    • 原因1:字典完全不匹配。你的字典是英文常见密码,但目标哈希来自一个中文网站的用户,密码可能是拼音或简单数字。
    • 解决:更换或扩充字典,加入中文常见密码、键盘模式(如1qaz2wsx)等。
    • 原因2:目标哈希不是纯MD5,可能是加了盐的MD5,或者是其他哈希算法(如SHA-1)。
    • 解决:确认哈希值的来源和算法。如果是“盐+哈希”的格式,需要修改脚本。可以尝试用已知的弱密码(如123456)加上可能的盐值,计算哈希并与目标比对来验证算法和盐。

7.2 安全加固建议(从防御者角度)通过这个项目,你应该深刻认识到仅使用MD5(或无盐哈希)存储密码的风险。以下是对开发者和运维人员的加固建议:

  1. 弃用MD5/SHA-1:对于新系统,绝对不要使用MD5或SHA-1存储密码。它们已不再安全。
  2. 使用专业的密码哈希函数:使用bcryptscryptArgon2PBKDF2。这些函数设计时考虑了“慢哈希”和“抗ASIC/GPU”,使得暴力破解的成本极高。例如,在Python中可以使用bcrypt库。
  3. 必须加盐(Salt):即使使用强哈希函数,也必须为每个密码生成唯一的、足够长的随机盐(如16字节),并将盐与哈希值一起存储。
  4. 强制密码策略:要求用户设置足够长度和复杂度的密码,并定期更换。但要注意,过于复杂的策略可能导致用户把密码写在便签上,带来其他风险。
  5. 实施多因素认证(MFA):对于重要系统,密码+短信验证码/OTP令牌/生物识别等多因素认证能极大提升安全性。

7.3 脚本的扩展方向这个基础脚本可以作为一个起点,进行多种扩展:

  • 支持多算法:修改crack_md5函数,接受一个算法参数,支持SHA-1、SHA-256、SHA-512等。
  • 分布式破解:将任务分发到多台机器上运行,可以使用消息队列(如Redis)来协调任务。
  • 集成到安全工具链:将脚本作为一个模块,集成到自动化渗透测试框架中,在获得哈希后自动调用。
  • 生成分析报告:不仅输出密码,还分析破解出的密码的常见模式(如纯数字、常见单词、长度分布),生成更详细的安全评估报告。

最后,我个人的体会是,编写和运行这样的脚本,最大的收获不是“破解”了几个哈希,而是以一种最直观的方式理解了“弱密码”和“弱哈希”组合在一起是多么危险。它像一面镜子,让你审视自己负责的系统是否存在同样的隐患。安全是一个持续的过程,而了解攻击者的工具和思路,是构建有效防御的第一步。希望这个项目能成为你安全学习路上的一块有用的垫脚石。在实际工作中,请永远记住:权限与授权是第一位的