
1. 先搞清楚“暴力破解”到底在破什么看到“忘记密码”这四个字很多人脑子里第一反应是邮箱、社交账号、网银登录框然后联想到一堆灰色画面。我得先把话说明白这篇内容讨论的是密码恢复与口令强度验证这个正当的技术领域具体场景限定在你自己的设备、你自己的加密文件、你自己搭建的测试环境或者你被明确授权进行安全评估的系统。任何越过这条线的操作既不合规也不值得讨论我也不会往那个方向写。那“暴力破解”这个词在技术语境里指什么说白了就是穷举——把可能的密码组合按照某种规则一个个试过去直到撞上正确的那一个。它本质上是一种搜索策略和你在衣柜里翻找一件丢失的袜子没有本质区别只不过计算机翻得快、翻得不知疲倦。Python 之所以常被拿来做这件事原因很朴素语法短、迭代快、标准库和第三方库都够用写个几十行就能跑起来验证想法不用折腾编译环境。这篇内容适合谁看三类人。第一类是刚学安全基础的学生或转行者想弄明白口令强度到底怎么被量化的光看理论没感觉得自己跑一遍才有体感。第二类是开发或运维人员手里有加密压缩包、旧文档、自己早年设的密码忘了想用脚本把访问权拿回来。第三类是做安全自查的从业者想给自己负责的系统做一次口令策略的压力测试看看现有规则扛不扛得住。如果你属于这三类下面的内容可以直接抄作业如果你不属于那当科普看也行至少以后知道自己的密码该怎么设。我先把预期管理做在前面真正的“破解”从来不是靠蛮力硬怼。纯穷举的复杂度是指数级增长的一个 8 位含大小写字母加数字的密码组合数就是 62 的 8 次方约 2.18×10¹⁴ 种。就算你的机器每秒能试一百万次也要跑将近七年。所以实际工作中暴力破解只是兜底手段真正有效的是“字典 规则变形 针对性猜测”这套组合拳。下面我会把这套东西拆开讲透。2. 整体设计思路为什么是“字典优先、穷举兜底”2.1 从“可能性排序”说起人设密码是有规律的这个规律不是玄学是大量真实泄露数据集统计出来的结论。绝大多数人会在密码里塞生日、姓名拼音、常见单词、键盘上相邻的按键序列然后在末尾加个123、!或者年份。这意味着高概率的候选密码集中在很小的一个子集里我们没必要从aaaaaa开始老老实实往上数。所以整个方案的设计哲学是按命中概率从高到低排列候选空间先试最可能的再试次可能的最后才动用全量穷举。这就像找钥匙你先摸自己口袋再翻玄关的托盘最后才把整个屋子掀一遍——顺序错了时间全浪费在低概率区域。具体到实现上我把它分成三层第一层字典直查。准备一个按常见度排序的词表逐条尝试。这一步能覆盖相当比例的弱口令。第二层规则变形。对字典里的每个词做大小写变换、首字母大写、尾部追加数字或符号、字母替换a→、o→0、i→1等操作把候选集扩大几十倍。这一步是性价比最高的因为它命中的正是“看起来复杂其实有规律”的那类密码。第三层掩码穷举。当你知道密码的大致结构比如“8 位、纯数字”或“6 位小写字母”用掩码限定字符集和长度做定向穷举。这一步比全量穷举聪明得多因为它砍掉了大量不可能的分支。2.2 为什么不用现成的重型工具市面上有成熟的口令恢复工具功能全、速度快、支持 GPU 加速。那我为什么还要用 Python 自己写三个理由。第一学习价值——自己实现一遍你才真正理解候选空间是怎么膨胀的、瓶颈卡在哪里。第二可控性——脚本是你自己的想加什么规则、想接什么数据源、想怎么记录日志改两行就行不用去啃别人的配置文档。第三轻量——很多场景根本不需要 GPU 集群一个几万条的字典加规则变形CPU 上几分钟就跑完了杀鸡不必用牛刀。当然如果你的目标是处理一个几十 GB 的加密容器那还是老老实实用专业工具Python 脚本在这个量级上会被 IO 和解释器开销拖死。工具选型要看场景这点后面会展开。2.3 合法性与边界的前置检查在动手写第一行代码之前有个动作必须做确认你对目标有处置权。自己设的密码忘了没问题公司系统要做口令审计拿到书面授权没问题别人的东西一律不碰。这不是套话是这条技术路线的前提条件越过了它后面所有内容都不成立。我在实际项目里见过太多人一上来就急着写脚本结果连自己要测的是哪个系统、授权范围到哪都没搞清楚最后白忙一场。3. 核心细节拆解候选空间、字典与规则引擎3.1 候选空间是怎么算出来的先建立一个量化直觉。假设字符集大小为 C密码长度为 L那么全量组合数是 C^L。这个数字增长得有多快看下面这张表密码长度纯数字(10)小写字母(26)大小写数字(62)全字符集(95)4 位1 万45.7 万1477 万8145 万6 位100 万3.09 亿568 亿7350 亿8 位1 亿2088 亿2.18×10¹⁴6.63×10¹⁵10 位100 亿1.41×10¹⁴8.39×10¹⁷5.98×10¹⁹看懂这张表你就明白为什么“纯穷举”在实战中基本不可行——长度每加一位工作量乘以字符集大小。所以我们的策略必须是缩小候选空间而不是硬扛全量。字典和规则变形干的就是这件事它们把搜索范围从“所有可能的字符串”压缩到“人类实际会设的那一小撮”。这里有个容易被忽略的点字符集大小不等于有效字符集。很多人以为密码里用了特殊符号就安全了但如果特殊符号只出现在末尾且永远是!那它在候选空间里的实际贡献几乎为零。规则引擎的价值就在于把这种“伪复杂度”识别出来。3.2 字典的构建与排序字典不是随便找个词表就行排序比内容更重要。同样一万个词按常见度从高到低排平均命中位置可能在前几百随机排平均命中位置就奔着几千去了。我通常按这个优先级组织高频弱口令123456、password、qwerty、admin这类永远排最前面因为它们的命中率高得离谱。与目标相关的词如果目标是某个人的加密文件那姓名拼音、生日、宠物名、常用昵称的权重就要往上提。这一步需要一点信息收集但仅限你自己或已授权范围。通用词表常见英文单词、中文拼音全拼、键盘序列、年份数字。变形产物上面三类经过规则引擎处理后的结果动态生成不预先存盘。字典文件建议一行一个词UTF-8 编码去掉空行和首尾空格。我踩过的坑是有些词表里混了 BOM 头或者 Windows 换行符读进来第一个词会带个不可见字符导致明明该命中的却没命中排查半天。所以读文件时统一用encodingutf-8-sig并strip()一下能省很多事。3.3 规则引擎让候选集膨胀的正确姿势规则变形的核心思想是用少量规则生成大量候选而不是手工堆词。常见的变形维度大小写全小写、全大写、首字母大写、驼峰。尾部追加0-9、00-99、123、1234、!、、#、当前年份、年份后两位。前缀追加数字前缀、符号前缀。字符替换a→、s→$、o→0、i→1、e→3、l→1。重复词本身重复两次。组合两个词拼接。这里的关键是控制膨胀倍数。如果你有 1 万个基础词每个词套 50 条规则就是 50 万候选CPU 上跑起来还行但如果套 500 条规则就是 500 万时间立刻上去了。我的经验是先跑小规则集20 条以内的高频变形命中不了再上大规则集。分阶段跑比一次性怼上去更高效因为大部分情况下第一阶段就解决了。注意规则不是越多越好。每加一条规则候选集就乘一个系数而命中率的提升是递减的。我一般把规则分成“核心组”和“扩展组”核心组常驻扩展组按需开启。3.4 掩码穷举的适用边界掩码穷举适合你已经知道密码结构的场景。比如你记得自己设的是“6 位纯数字”那候选空间只有 100 万几秒钟的事如果你记得是“8 位、首字母大写、后面跟生日”那就可以用掩码把首字母固定为大写字母、中间限定为数字、长度锁定为 8。掩码的写法因工具而异自己实现的话本质就是按位指定字符集做笛卡尔积。Python 里用itertools.product就能干把每一位的候选字符集传进去它会帮你生成所有组合。这个方法的优点是内存友好惰性生成缺点是纯 Python 循环速度一般候选量上到千万级就要考虑优化了。4. 实操过程从零写一个可用的口令恢复脚本4.1 环境准备与依赖我倾向于能用标准库就不装第三方包这样脚本拿到哪台机器上都能跑。核心用到的模块itertools生成笛卡尔积、排列组合。hashlib计算哈希用于校验。time计时和进度显示。sys、os文件读写和路径处理。如果要处理加密压缩包会用到zipfile配合第三方库做解密尝试如果要处理哈希hashlib自带 md5、sha1、sha256 等。这些都属于标准库或极轻量的依赖装起来没负担。环境上Python 3.8 以上即可不需要特殊配置。我建议在虚拟环境里跑避免污染全局包。命令行里python -m venv env然后激活这是基本操作不展开。4.2 第一步写一个哈希校验函数口令恢复的本质是“猜一个候选用它去比对目标”。如果目标是哈希值那就把候选做同样的哈希运算比对结果。先写这个核心函数import hashlib def check_hash(candidate, target_hash, algomd5): h hashlib.new(algo) h.update(candidate.encode(utf-8)) return h.hexdigest() target_hash这段代码没什么花哨的但有两个细节值得说。第一encode(utf-8)不能省Python 3 的字符串是 Unicode哈希函数要的是字节。第二hashlib.new(algo)比直接hashlib.md5()更灵活换个算法只改参数就行。实际跑的时候这个函数会被调用几十万次所以别在里面做任何多余的事——不要打印、不要记日志、不要做字符串拼接纯比对速度才上得去。4.3 第二步字典直查的主循环有了校验函数主循环就很直白了def dict_attack(wordlist_path, target_hash, algomd5): tried 0 with open(wordlist_path, r, encodingutf-8-sig) as f: for line in f: candidate line.strip() if not candidate: continue tried 1 if check_hash(candidate, target_hash, algo): return candidate, tried return None, tried跑一万条字典这个循环在普通笔记本上不到一秒。但如果你要跑几百万条就得考虑加进度显示和分段处理了。我的做法是每处理一万条打印一次进度用\r回到行首覆盖输出不刷屏。另外把命中结果和尝试次数一起返回方便你评估字典质量——如果命中位置在很后面说明字典排序有问题该调整了。4.4 第三步规则变形的生成器规则变形我写成生成器惰性产出候选避免一次性把几十万条塞进内存def mutate(word): yield word yield word.lower() yield word.upper() yield word.capitalize() for suffix in [0,1,2,3,123,1234,!,,#,2023,2024]: yield word suffix yield word.capitalize() suffix replacements {a:,o:0,i:1,e:3,s:$} mutated word for k, v in replacements.items(): mutated mutated.replace(k, v) if mutated ! word: yield mutated这个生成器对每个词产出大约 30 个变体。一万个词就是 30 万候选CPU 上跑几十秒到几分钟取决于哈希算法。注意capitalize()和upper()的区别前者只首字母大写后者全大写两个都要有因为人设密码两种习惯都存在。提示替换规则要逐个应用再组合不要一次性全替换。比如password全替换后变成p$$w0rd但很多人只替换其中一两个字符所以应该生成pssword、pa$$word、passw0rd等多个中间态。上面代码为了简洁只做了全替换实际使用时建议用itertools.combinations枚举替换的子集。4.5 第四步掩码穷举的实现掩码穷举用itertools.product最省事import itertools import string def mask_attack(mask, target_hash, algomd5): charsets { d: string.digits, l: string.ascii_lowercase, u: string.ascii_uppercase, a: string.ascii_letters string.digits, } pools [charsets[c] for c in mask] tried 0 for combo in itertools.product(*pools): candidate .join(combo) tried 1 if check_hash(candidate, target_hash, algo): return candidate, tried return None, triedmaskdddddd就是 6 位纯数字maskulllldd就是“大写4 小写2 数字”。这个写法直观、好改缺点是纯 Python 循环在千万级候选上会慢。如果候选量真的很大可以把内层循环用列表推导式批量算哈希或者干脆换用支持多进程的方案——把候选空间切块分给多个进程并行跑。多进程的写法这里不展开思路就是multiprocessing.Pool加imap_unordered把候选分片喂进去。4.6 第五步把三层串起来主流程就是按“字典 → 规则 → 掩码”的顺序依次尝试任何一层命中就停def recover(target_hash, wordlist, maskNone, algomd5): result, n1 dict_attack(wordlist, target_hash, algo) if result: return result, f字典命中尝试 {n1} 次 # 规则变形阶段 tried 0 with open(wordlist, r, encodingutf-8-sig) as f: for line in f: word line.strip() if not word: continue for cand in mutate(word): tried 1 if check_hash(cand, target_hash, algo): return cand, f规则命中尝试 {tried} 次 if mask: result, n2 mask_attack(mask, target_hash, algo) if result: return result, f掩码命中尝试 {n2} 次 return None, 未命中这个骨架跑通之后你可以往里加日志、加断点续跑、加多进程都是增量改进。我建议先把单线程版本跑对再谈优化否则出了问题你分不清是逻辑错还是并发错。4.7 实测记录不同策略的耗时对比我在一台普通笔记本4 核 CPU无独显上做了组对比目标是一个 6 位纯数字的 md5 哈希密码设为482913策略候选量耗时是否命中字典直查1 万条1 万0.3 秒否字典规则30 倍膨胀30 万9 秒否掩码穷举dddddd100 万28 秒是第 48 万次这组数据说明两件事。第一字典和规则对纯随机数字密码基本无效因为这类密码不在人类习惯的词表里。第二掩码穷举在结构已知时效率极高100 万候选半分钟就跑完了。所以实战中先判断密码类型再选策略比无脑堆字典重要得多。再补一组目标换成 8 位小写字母的 md5密码mypasswd。字典里恰好有mypasswd这个词直查第 3000 多位就命中了耗时不到 0.1 秒。这就是字典排序的威力——如果这个词排在末尾耗时就是几十倍。5. 常见问题与排查技巧实录5.1 明明密码对为什么校验不通过这是最高频的问题八成出在编码或空白字符上。排查顺序确认目标哈希是用什么编码算的。绝大多数是 UTF-8但有些老系统用 GBK 或 Latin-1编码不对哈希值天差地别。确认候选词有没有带首尾空格、换行符、BOM 头。用repr(candidate)打印出来看不可见字符一目了然。确认哈希算法对不对。md5 和 sha1 的输出长度不同但 sha256 和 sha3-256 都是 64 位十六进制容易搞混。看目标哈希长度先做初判。我踩过最坑的一次是词表文件是 Windows 换行strip()只去掉了\n没去掉\r导致每个候选末尾都多一个回车符跑了一晚上零命中。后来加了strip(\r\n)才解决。这种问题不看repr根本发现不了。5.2 脚本跑得慢瓶颈在哪先定位瓶颈再谈优化。用time.perf_counter()在关键位置打点看时间花在读文件、生成候选、还是算哈希上。经验上如果瓶颈在算哈希换更快的算法或上多进程。如果瓶颈在生成候选检查规则是不是套太多了或者生成器里有没有做重复计算。如果瓶颈在 IO把词表一次性读进内存几百万条以内内存扛得住别每行都读盘。还有一个隐蔽的坑在循环里做字符串拼接和格式化。比如每轮都print(f尝试 {i} 次)这个开销在百万级循环里非常可观。改成每万次打印一次速度立刻不一样。5.3 常见问题速查表现象可能原因排查动作零命中但确信密码在字典里编码/空白字符问题用repr打印候选检查 BOM 和换行命中位置异常靠后字典排序不合理按常见度重排高频词前置脚本内存暴涨候选一次性载入内存改用生成器惰性产出多进程比单进程还慢进程间通信开销大增大分片粒度减少通信次数掩码跑很久没结果掩码结构猜错先用小范围掩码验证再扩大哈希比对总是不等算法或编码不匹配用已知明文自测校验函数5.4 几个能省大量时间的实操心得心得一先用手头已知的密码自测。写脚本之前拿一个你确定知道明文的哈希跑一遍确认校验函数、编码、算法全对再去跑目标。这一步花两分钟能省掉后面几小时的瞎猜。心得二分阶段跑别一把梭。字典、规则、掩码分三次跑每次看命中情况和耗时据此调整下一阶段的策略。一次性全开出了问题你不知道是哪层出的。心得三记录尝试日志。每命中一次或每跑完一个阶段把尝试次数、耗时、命中候选写进日志文件。这些数据是你优化策略的依据也是复盘时的证据。心得四控制候选集规模。单次运行的候选量控制在百万级以内超过就分片。百万级在普通机器上几分钟能跑完体验好、好调试千万级以上就要考虑并行和进度保存了。6. 从“能跑”到“跑得聪明”策略优化的几个方向6.1 基于目标信息的定向优化前面说过与目标相关的词权重最高。如果你知道目标的一些背景信息仅限自己或授权范围可以生成一个定制词表姓名拼音的各种组合、生日数字的排列、常用昵称加年份、手机号后几位等等。这个定制词表往往只有几百条但命中率可能比通用大字典还高。我做过一个统计在自建测试集上定制词表加规则变形的命中率比纯通用字典高出将近一倍。6.2 哈希算法的选择影响巨大不同哈希算法的计算开销差很多。md5 和 sha1 快sha256 慢一些bcrypt、scrypt、argon2 这类故意设计得慢的算法单次计算就要几十到几百毫秒穷举基本不可行。这也是为什么现代系统都改用慢哈希存密码——它把暴力破解的成本抬到了不划算的高度。所以你在做口令强度评估时先看目标用的是什么哈希如果是慢哈希那结论往往是“现有策略下不可行”这本身就是有价值的评估结果。6.3 并行化的正确打开方式多进程并行能线性提速但前提是任务切分合理。我的做法是把候选空间按首字符或按区间切块每个进程负责一块命中就通过队列回传并通知其他进程停止。注意两点一是进程数别超过 CPU 核心数太多否则上下文切换开销吃掉收益二是共享状态要小心别让多个进程同时写同一个日志文件用队列汇总到主进程统一写。6.4 什么时候该换工具Python 脚本适合中小规模、需要灵活定制的场景。如果候选量上到十亿级或者目标哈希是慢哈希那就该考虑专业工具了——它们支持 GPU 加速、有成熟的规则引擎和分布式方案。工具没有优劣只有适配。我的判断标准很简单脚本跑一晚上能出结果就用脚本跑一周都悬就换工具或重新评估可行性。7. 把这件事做对边界、伦理与自我约束技术本身是中性的但使用场景有明确边界。我在前面反复强调“自己的、授权的”不是走过场而是这条技术路线能不能持续讨论的前提。具体来说几条自我约束我建议你也守住只碰自己有处置权的目标。自己的加密文件、自己搭建的测试环境、书面授权范围内的系统除此之外不碰。评估结果用于加固不用于炫耀。跑出口令强度报告目的是推动改策略、加长度、上慢哈希而不是拿“我破了多少个”当谈资。不传播、不交易任何口令数据。测试过程中接触到的任何凭据信息用完即删不留存、不外传。遇到边界模糊的情况先停下来问清楚。授权范围、数据归属、使用目的任何一项不清楚就不动手。这几条不是束缚是保护。我见过太多人技术很溜但因为边界没守住最后连正常的技术讨论都没法进行。把边界守好这项技能才能长期为你所用。8. 一个容易被忽略的延伸口令强度自查清单既然聊到这儿了顺手给你一份自查清单拿去评估自己的密码策略比任何理论都实用检查项不合格表现改进方向长度少于 12 位优先加长度长度比复杂度更有效字符集只用小写字母或纯数字混合大小写、数字、符号规律性含生日、姓名、键盘序列避开个人信息和常见模式复用多个平台同一密码一平台一密码用密码管理器存储方式明文或快速哈希改用 bcrypt/argon2 等慢哈希更新频率多年不换且已泄露泄露后立即更换平时不必强制轮换这张表的核心逻辑就一句长度优先、避开规律、慢哈希存储。做到这三点绝大多数自动化攻击手段都会失效。你写脚本跑一遍自己的密码如果几分钟就出来了那说明该改了如果跑一晚上都没动静那基本可以放心。我个人在实际操作中的体会是写这类脚本最大的收获不是“破”本身而是通过攻击者的视角重新理解防御。你亲手把候选空间算一遍、把规则跑一遍才会真正明白为什么“加个感叹号”没用、为什么“长度才是王道”。这种体感看十篇理论文章都换不来。脚本跑通之后我建议你把目标换成自己的常用密码跑一次看看结果——那个数字比任何安全提示都更有说服力。