Python排列组合实战:利用itertools高效生成密码字典

发布时间:2026/7/16 5:21:43
Python排列组合实战:利用itertools高效生成密码字典 1. 项目概述从“排列组合”到“密码字典”的实战思路最近在帮一个朋友做安全自查他公司内部有个老旧的测试系统密码策略弱得离谱想让我帮忙评估一下风险。我第一个想到的就是用Python写个脚本来生成可能的密码组合做个简单的字典攻击测试。这活儿听起来挺唬人但核心逻辑其实就俩字排列。Python标准库里的itertools.permutations()函数简直就是为这种场景量身定做的。很多人学Python时都见过这个函数知道它能生成序列的所有可能排列但真到了实战里比如生成密码字典怎么高效地用起来怎么避开那些新手必踩的坑这里面的门道就多了。简单说这个项目就是利用permutations()函数根据给定的字符集比如小写字母、数字、常见符号自动生成所有指定长度的密码排列并保存成一个文本文件也就是我们常说的“密码字典”。这玩意儿在授权范围内的安全测试、忘记密码时的暴力破解仅限自己遗忘的密码、或是研究密码强度时特别有用。它不适合也绝对不应该用于任何未经授权的非法访问尝试。整个过程会涉及到Python基础、文件操作、迭代器的高效使用以及如何避免内存爆炸等实际问题。无论你是刚学完Python基础语法想找个实战项目练手还是做安全相关的研究需要工具这篇内容都能给你一套可直接复制、修改和运行的完整方案。2. 核心工具解析深入理解itertools.permutations()在动手写代码之前我们必须把核心武器itertools.permutations()摸透。很多新手照着网上的代码抄结果一运行就报NameError: name permutations is not defined问题根源就在于没搞明白它的“住址”。2.1 函数导入的两种正确姿势permutations()函数住在Python标准库的itertools模块里。它不是内置函数所以不能直接喊它的名字得先“请”它出来。主要有两种请法方法一导入整个模块推荐给工具党import itertools # 使用时需要带上模块名作为前缀 result itertools.permutations(ABC, 2)这种方法的好处是一旦导入了itertools这个模块里的其他宝贝比如combinations组合、product笛卡尔积你都可以随时用代码结构清晰。缺点是每次调用都要多打几个字。方法二直接导入函数推荐给专注党from itertools import permutations # 使用时直接叫函数名干净利落 result permutations(ABC, 2)这种方法代码更简洁适合当前脚本只用到permutations的场景。但如果你写着写着突然想用combinations了就得回头去补充导入。踩坑实录我见过最经典的错误就是混用。有人开头写了import itertools后面调用时却直接写了permutations(...)Python解释器会一脸懵圈地问你“permutations是谁” 所以导入和调用必须配套。如果你不确定以后会不会用到其他函数用方法一更稳妥。2.2 函数参数与返回值的行为这个函数的签名是permutations(iterable, rNone)。iterable任何可迭代对象比如字符串、列表、元组。它会被用来提取元素。r生成的排列元组的长度。如果r没有指定或者为None则默认为可迭代对象的长度此时会生成所有全排列。它的返回值是一个迭代器。这是理解其高效性的关键。迭代器不是列表它不会一次性把所有结果都计算出来并塞进内存而是“惰性”的你每次向它要下一个结果它才现算一个给你。from itertools import permutations # 假设我们有一个字符集 chars [a, b, c] # 生成长度为2的所有排列 perm_iter permutations(chars, 2) # 此时perm_iter只是一个迭代器对象内存中并没有[(a,b), (a,c)...]这个列表 print(perm_iter) # 输出类似itertools.permutations object at 0x... # 我们需要遍历它来获取值 for p in perm_iter: print(p) # 依次输出(a, b), (a, c), (b, a), (b, c), (c, a), (c, b) # 或者用list()强制转换但这会消耗迭代器并占用内存 all_perms list(permutations(chars, 2)) print(all_perms) # 输出[(a, b), (a, c), (b, a), (b, c), (c, a), (c, b)]为什么迭代器如此重要想象一下如果你的字符集是26个小写字母要生成长度为4的密码。排列数 P(26,4) 262524*23 358,800 个。如果permutations()直接返回一个包含35万多个字符串的列表内存占用瞬间就上去了。而迭代器就像一条流水线你需要一个它生产一个内存里同一时间只维护很少的数据这对于生成动辄百万、千万级别的密码字典来说是救命的特性。2.3 排列Permutation与组合Combination的抉择这里必须厘清一个概念我们生成的是排列不是组合。在数学上组合 (Combinations)只关心选了哪些元素不关心顺序。从[a, b, c]里选2个组合只有3种(a,b),(a,c),(b,c)。排列 (Permutations)既关心元素也关心顺序。从[a, b, c]里选2个排列有6种(a,b),(a,c),(b,a),(b,c),(c,a),(c,b)。在密码场景中abc123和123abc是两个完全不同的密码。因此我们必须使用permutations来穷尽所有顺序的可能性。如果你错误地使用了itertools.combinations()将会漏掉海量的可能性导致字典完全无效。这是理论指导实践的一个典型例子。3. 密码字典生成器的完整设计与实现理解了核心函数我们就可以开始设计整个生成器了。一个健壮的密码字典生成器不能只是一个简单的函数调用它需要处理输入、计算、输出以及用户交互。下面我将分步骤拆解并给出每一部分的代码和详细解释。3.1 定义字符集与密码长度策略这是字典的“原料”。字符集定义得越全生成的密码覆盖面越广但数量也会呈爆炸式增长。我们需要根据目标系统的常见密码策略来灵活定义。# 定义常见的字符集类别 LOWER_LETTERS abcdefghijklmnopqrstuvwxyz UPPER_LETTERS ABCDEFGHIJKLMNOPQRSTUVWXYZ DIGITS 0123456789 SPECIAL_CHARS !#$%^*()_-[]{}|;:,.? # 常见键盘序列弱密码特征 COMMON_SEQUENCES [123, abc, qwerty, password, admin] def build_character_pool(use_lowerTrue, use_upperFalse, use_digitsTrue, use_specialFalse, custom_chars): 根据选项构建字符池。 参数: use_lower: 是否包含小写字母 use_upper: 是否包含大写字母 use_digits: 是否包含数字 use_special: 是否包含特殊符号 custom_chars: 用户自定义的额外字符 返回: 一个字符串即最终的字符池 pool if use_lower: pool LOWER_LETTERS if use_upper: pool UPPER_LETTERS if use_digits: pool DIGITS if use_special: pool SPECIAL_CHARS pool custom_chars # 添加自定义字符 if not pool: raise ValueError(字符池不能为空请至少选择一种字符类型。) print(f[信息] 字符池构建完成共包含 {len(pool)} 个唯一字符。) print(f[信息] 字符池内容前50个: {pool[:50]}...) return pool设计思路我将字符集设计成可配置的模块。在实际测试中很多系统密码可能只要求“字母数字”那么我们就只启用小写字母和数字这样可以极大减少不必要的计算量。custom_chars参数非常有用比如你知道目标可能喜欢用或!可以单独加进去。3.2 核心生成函数处理迭代与拼接这是脚本的心脏部分。它需要高效地遍历permutations迭代器并将生成的元组如(a, 1, b)拼接成字符串如a1b然后写入文件。from itertools import permutations def generate_passwords(char_pool, min_len, max_len, output_filepasswords.txt): 生成密码字典并保存到文件。 参数: char_pool: 字符串用于生成密码的字符集合 min_len: 整数密码最小长度 max_len: 整数密码最大长度 output_file: 字符串输出文件名 total_count 0 # 以写入模式打开文件如果文件已存在则清空 with open(output_file, w, encodingutf-8) as f: # 遍历从最小长度到最大长度的每一种密码长度 for length in range(min_len, max_len 1): print(f[生成中] 正在生成长度为 {length} 的密码...) length_count 0 # permutations 返回的是迭代器惰性计算节省内存 perm_iter permutations(char_pool, length) # 遍历该长度下的所有排列 for perm_tuple in perm_iter: # 将元组 (a, 1) 拼接成字符串 a1 password .join(perm_tuple) # 将密码写入文件每个密码一行 f.write(password \n) length_count 1 total_count 1 # 可选每生成一定数量输出一次进度避免刷屏 if total_count % 10000 0: print(f 已生成 {total_count} 个密码..., end\r) print(f[完成] 长度 {length} 的密码生成完毕共 {length_count} 个。) print(f\n[全部完成] 密码字典已保存至: {output_file}) print(f 密码总数量: {total_count}) # 计算理论值并与实际值对比用于验证 calc_total sum(len(char_pool)**length for length in range(min_len, max_len1)) print(f 理论排列总数: {calc_total} (验证一致))关键点解析嵌套循环外层循环控制密码长度内层循环处理特定长度的所有排列。这样逻辑清晰也便于输出进度信息。.join(perm_tuple)这是将字符元组快速转换为字符串的标准且高效的方法比用for循环拼接快得多。文件写入使用with open(...) as f上下文管理器可以确保在任何情况下包括程序异常中断文件都能被正确关闭。以w模式打开会覆盖旧文件如果你想追加可以用a模式。进度反馈在生成大量密码时程序可能运行很久。加入进度输出尤其是那个end\r的回车不换行技巧能让用户知道程序还在工作没有卡死。3.3 主程序与用户交互整合最后我们需要一个主函数来把以上部分串联起来并处理用户的输入。def main(): print( * 50) print(Python 密码字典生成器 (基于排列)) print( * 50) # 1. 配置字符池 print(\n第一步配置密码字符集) print(提示选择越多生成的字典越大耗时越长。) use_lower input(包含小写字母 (a-z) 吗 (y/n, 默认y): ).lower() ! n use_upper input(包含大写字母 (A-Z) 吗 (y/n, 默认n): ).lower() y use_digits input(包含数字 (0-9) 吗 (y/n, 默认y): ).lower() ! n use_special input(包含特殊符号 (!#...) 吗 (y/n, 默认n): ).lower() y custom input(请输入任何额外的自定义字符 (直接输入无需空格): ) try: char_pool build_character_pool(use_lower, use_upper, use_digits, use_special, custom) except ValueError as e: print(f[错误] {e}) return # 2. 配置密码长度 print(\n第二步配置密码长度范围) while True: try: min_len int(input(请输入密码最小长度 (例如 4): )) max_len int(input(请输入密码最大长度 (例如 6): )) if min_len 0 or max_len 0: print(长度必须为正整数) continue if min_len max_len: print(最小长度不能大于最大长度) continue if max_len len(char_pool): print(f警告最大长度 {max_len} 大于字符池大小 {len(char_pool)}。) print(对于排列来说当长度 r 字符数 n 时permutations 不会产生结果。) confirm input(是否继续 (y/n): ).lower() if confirm ! y: continue break except ValueError: print(请输入有效的整数) # 3. 预估字典大小非常重要 print(\n第三步预估生成规模) total_estimates [] for l in range(min_len, max_len 1): # 排列数公式P(n, r) n! / (n-r)! # 当 nlen(char_pool), rl # 这里用更直观的连乘计算n * (n-1) * ... * (n-l1) n len(char_pool) if l n: count 0 else: count 1 for i in range(l): count * (n - i) total_estimates.append(count) print(f 长度 {l}: P({n},{l}) {count:,} 种可能) total sum(total_estimates) print(f 预计生成密码总数: {total:,}) if total 10_000_000: print(警告预计生成数量超过一千万可能会生成超大文件并耗时极长) print( 请考虑缩小字符集或长度范围。) confirm input(确定要继续吗 (输入 yes 继续): ) if confirm ! yes: print(操作已取消。) return # 4. 执行生成 output_file input(f\n第四步输入输出文件名 (默认 passwords.txt): ).strip() if not output_file: output_file passwords.txt print(f\n开始生成密码字典...) print(f字符池大小: {len(char_pool)}) print(f密码长度: {min_len} - {max_len}) print(f输出文件: {output_file}) print(- * 30) try: generate_passwords(char_pool, min_len, max_len, output_file) except KeyboardInterrupt: print(\n\n[中断] 用户中断了生成过程。) print(部分数据可能已保存至文件。) except Exception as e: print(f\n[错误] 生成过程中发生未知错误: {e}) if __name__ __main__: main()4. 性能优化与大规模生成策略当你用上面的脚本尝试生成一个稍大的字典时比如26个小写字母10个数字长度4-6你会立刻遇到挑战速度慢、内存虽然用了迭代器但写入文件是瓶颈、磁盘空间。下面我们来解决这些问题。4.1 使用product替代permutations处理“可重复”密码permutations生成的是无重复元素的排列。这意味着密码aa11或book字母‘o’重复是不会被生成的。然而现实中很多弱密码是允许字符重复的。例如1111、aaaa。为了覆盖这种情况我们需要itertools.product笛卡尔积。product可以理解为多重循环它生成的所有可能组合是允许元素重复的。from itertools import product # 生成所有由‘AB’组成长度为3的密码允许重复 for p in product(AB, repeat3): print(.join(p)) # 输出AAA, AAB, ABA, ABB, BAA, BAB, BBA, BBB如何选择使用permutations当你确定或猜测密码由完全不同的字符组成时。例如从名字和生日中抽取字符。使用product这是更通用的场景能生成所有允许重复的密码。绝大多数暴力破解字典使用product逻辑。但请注意它的数量增长是指数级的len(pool) ** length比排列增长更快更需要谨慎控制规模。我们可以修改生成函数提供一个选项让用户选择模式from itertools import permutations, product def generate_passwords_adv(char_pool, min_len, max_len, output_file, allow_repeatTrue): 高级生成函数可选择是否允许字符重复。 参数: allow_repeat: True 使用 product (允许重复), False 使用 permutations (不允许重复) total_count 0 with open(output_file, w, encodingutf-8) as f: for length in range(min_len, max_len 1): print(f[生成中] 正在生成长度为 {length} 的密码 (模式: {允许重复 if allow_repeat else 无重复})...) length_count 0 if allow_repeat: # 使用 product repeat参数指定长度 iter_obj product(char_pool, repeatlength) # 理论数量是 len(pool) ** length else: # 使用 permutations iter_obj permutations(char_pool, length) # 理论数量是 P(len(pool), length) for item_tuple in iter_obj: password .join(item_tuple) f.write(password \n) length_count 1 total_count 1 print(f[完成] 长度 {length} 生成完毕共 {length_count:,} 个。) print(f\n[全部完成] 共生成 {total_count:,} 个密码。)4.2 分块写入与进度保存生成千万级别的密码时程序运行时间可能长达数小时。如果中途断电或崩溃一切就要重来。我们可以实现一个分块写入和进度保存的机制。思路是不是一口气生成所有密码再写也不是一个一个写I/O操作太频繁而是积累一定数量比如1万个后批量写入一次。同时定期将当前进度已处理的长度和数量保存到一个临时文件或记录中。import os import pickle from itertools import islice def generate_with_resume(char_pool, min_len, max_len, output_file, chunk_size10000, resumeTrue): 支持断点续传的密码生成函数。 参数: chunk_size: 每积累多少个密码写入一次文件 resume: 是否尝试从上次中断处恢复 progress_file output_file .progress start_length min_len start_count 0 # 尝试恢复进度 if resume and os.path.exists(progress_file): try: with open(progress_file, rb) as pf: progress pickle.load(pf) start_length progress.get(current_length, min_len) start_count progress.get(count_within_length, 0) total_so_far progress.get(total_count, 0) print(f[恢复] 从长度 {start_length} 的第 {start_count} 个密码开始恢复。) print(f 已生成 {total_so_far:,} 个密码。) # 以追加模式打开输出文件 mode a except: print([恢复] 进度文件损坏将重新开始生成。) mode w start_length min_len start_count 0 else: mode w total_count 0 with open(output_file, mode, encodingutf-8) as f: for length in range(start_length, max_len 1): print(f[生成中] 长度 {length}...) iter_obj product(char_pool, repeatlength) # 如果是从某个长度的中间恢复需要跳过已生成的部分 if length start_length and start_count 0: # islice 可以跳过迭代器前N个元素 iter_obj islice(iter_obj, start_count, None) count_in_this_length start_count else: count_in_this_length 0 chunk [] for item_tuple in iter_obj: password .join(item_tuple) chunk.append(password \n) count_in_this_length 1 total_count 1 # 积累到一个块的大小批量写入 if len(chunk) chunk_size: f.writelines(chunk) chunk.clear() # 清空块 # 保存进度 with open(progress_file, wb) as pf: pickle.dump({ current_length: length, count_within_length: count_in_this_length, total_count: total_count }, pf) print(f 进度: 长度{length} - 已生成 {count_in_this_length:,} / 预计 {len(char_pool)**length:,} | 总计 {total_count:,}, end\r) # 写入最后不满一个块的部分 if chunk: f.writelines(chunk) print(f\n[完成] 长度 {length} 完成共 {count_in_this_length:,} 个。) # 这个长度完成后重置下一个长度的起始计数 start_count 0 # 全部完成后删除进度文件 if os.path.exists(progress_file): os.remove(progress_file) print(f\n[全部完成] 共生成 {total_count:,} 个密码已保存至 {output_file})这个函数增加了断点续传功能对于长时间运行的任务非常友好。它通过pickle模块将进度保存到文件即使程序意外终止下次运行也能从上次中断的地方继续。4.3 使用生成器函数与yield提升灵活性上面的代码将生成和写入耦合在一起。有时我们可能只想获取密码流用于其他处理比如直接发送到网络请求进行测试而不是保存到文件。这时可以使用生成器函数。from itertools import product def password_generator(char_pool, min_len, max_len, allow_repeatTrue): 密码生成器yield 每个生成的密码。 这是一个生成器函数调用时返回一个迭代器。 for length in range(min_len, max_len 1): if allow_repeat: iter_obj product(char_pool, repeatlength) else: iter_obj permutations(char_pool, length) for item_tuple in iter_obj: yield .join(item_tuple) # 使用示例 if __name__ __main__: pool ab12 gen password_generator(pool, 2, 3, allow_repeatTrue) # 可以像遍历列表一样遍历它但内存友好 for i, pwd in enumerate(gen): print(pwd) if i 20: # 只看前20个避免无限打印 break # 或者配合write使用 with open(passwords.txt, w) as f: for pwd in password_generator(pool, 2, 3): f.write(pwd \n)使用yield的生成器让代码更清晰、更模块化。生成逻辑和消费逻辑写入文件、网络测试等完全分离符合“单一职责原则”。5. 实战技巧、常见问题与高级应用掌握了基础生成我们来看看实战中会遇到哪些坑以及如何将这个工具用得更巧妙。5.1 内存管理与超大规模字典处理问题即使使用了迭代器当字符集很大、密码长度较长时生成的密码数量本身就会导致输出文件巨大几十GB甚至TB级这超出了单机处理的合理范围。策略分文件存储不要把所有密码写进一个文件。可以按密码长度、首字母等规则分到多个文件中。def generate_by_first_char(char_pool, length, output_dirdict_parts): 按首字母分文件生成固定长度的密码 import os os.makedirs(output_dir, exist_okTrue) for first_char in char_pool: # 生成长度-1的排列/组合然后前面加上首字母 sub_pool char_pool # 或者你可以定义不同的子池 iter_obj product(sub_pool, repeatlength-1) with open(os.path.join(output_dir, fdict_{first_char}.txt), w) as f: for suffix_tuple in iter_obj: password first_char .join(suffix_tuple) f.write(password \n)使用更高效的数据结构对于最终的字典文件可以考虑使用sqlite3数据库存储并建立索引这样在后续的查询和去重中会快得多。分布式生成如果条件允许可以将字符集或长度范围划分成多个任务在多台机器或多核CPU上并行生成。Python的multiprocessing模块可以用于此目的但要注意协调输出文件避免冲突。5.2 密码策略模拟与智能字典生成单纯的穷举暴力破解效率最低。高效的字典是“智能”的它融合了社会工程学和对目标密码策略的了解。常见模式拼接人的密码常有模式如“单词数字符号”Summer2024!或“键盘序列”qwerty123。我们可以用product或嵌套循环来组合这些模式块。from itertools import product words [sun, moon, star] numbers [123, 456, 789] specials [!, , #] # 生成所有组合word number special for w, n, s in product(words, numbers, specials): print(w n s) # sun123!, sun123, ..., star789#字典变形Mangling Rules这是专业密码破解工具如Hashcat、John the Ripper的核心概念。对基础字典中的每个词条应用一系列规则如首字母大写、尾部加数字、字母替换a-,s-$等。我们可以实现简单的规则引擎base_words [password, admin, welcome] rules [ lambda x: x, # 原样 lambda x: x.capitalize(), # 首字母大写 lambda x: x 123, # 加后缀 lambda x: x.replace(a, ), # 字符替换 ] for word in base_words: for rule in rules: print(rule(word))基于已知信息的生成如果你知道目标的一些信息如姓名、生日、宠物名、公司名可以基于这些信息生成相关字典。这比盲目穷举小写字母有效得多。5.3 常见错误排查与解决MemoryError或程序卡死原因虽然permutations/product返回迭代器但如果你用list()把它强制转换成列表或者你的chunk_size设置得太大就会在内存中积累大量数据。解决永远不要对大规模的迭代器使用list()。确保你的代码是流式处理生成一个处理一个丢弃一个。减小chunk_size。磁盘空间不足原因生成的字典文件太大。解决生成前务必用公式总数量 sum(len(pool)**L for L in range(min_len, max_len1))估算文件大小每个密码长度换行符约总数量 * (平均长度1)字节。考虑使用压缩方式写入例如直接写入.gz文件import gzip with gzip.open(passwords.txt.gz, wt, encodingutf-8) as f: for pwd in password_generator(...): f.write(pwd \n)生成速度太慢原因Python单循环处理海量数据本身就不快且频繁的I/O操作写文件是主要瓶颈。解决批量写入如前所述积累一定数量后批量写入减少I/O次数。使用更快的语言对于核心生成逻辑可以考虑用Cython编写或者直接用C/C实现然后通过Python调用。并行化使用multiprocessing.Pool将不同长度或不同字符子集的任务分配到多个CPU核心。生成的字典无法破解成功原因目标密码不在你的字符集和长度范围内或者密码有特殊规则如必须包含大小写和符号。解决尽可能多地收集目标信息调整字符集和生成策略。没有“万能字典”。5.4 伦理与合法使用提醒我必须再次强调这个工具以及任何密码字典生成器其唯一的合法用途是测试你自己拥有或得到明确书面授权测试的系统和账户。恢复你自己遗忘的密码且不违反任何服务条款。学术研究或密码强度教育。未经授权对他人系统、账户、网络进行密码猜测或破解是明确的违法行为可能导致严重的法律后果。技术本身无罪但使用技术的人必须为其行为负责。请务必在合法、合规、合乎道德的框架内使用你的技能和工具。这个项目从一个小小的permutations()函数出发延伸到了密码学的边缘、性能优化的深水区以及工程化的实践。它完美地展示了如何将一个简单的编程知识点通过不断的思考、迭代和解决实际问题变成一个功能完整、考虑周全的实用工具。希望你在实现它的过程中不仅学会了这个函数更掌握了这种“从点到面”的解决问题思路。