一、 程序功能总览
核心功能是根据包含用户个人信息的原始数据文件,为每个用户生成一份高度定制化的密码猜测字典。其设计理念是:人们常常使用与自己相关的信息(如姓名、生日、电话、账号等)来创建密码,因此针对特定目标生成密码时,基于其个人信息的字典往往比通用字典更有效。
该程序的主要工作流程可以概括为以下几个阶段:
环境检查:确保依赖的工具(Pydictor)和输入数据文件存在。
数据读取与解析:逐行读取原始数据文件,并从中提取出结构化的用户信息。
基础字典生成:针对单个用户,从其个人信息中提炼出可能被用作密码基础的“关键词”或“词根”。
密码猜测生成:利用 Pydictor 工具,对基础词根进行多种变形和组合,生成大量可能的密码变体。
结果整合与输出:将每个用户的猜测密码集合写入输出文件,并用分隔符隔开,同时提供处理进度和统计信息。
该程序的价值在于它将散乱的非结构化信息(数据文件中的一行文本)自动化、批量化地转化为有针对性的攻击字典,极大地提高了密码破解或安全测试中“猜测”环节的效率和精准度。
二、 输入输出与全局配置
在深入分析内部机制前,我们首先审视其外部接口和配置。
-
输入数据文件 (DATA_FILE = "datasets.txt"):
-
格式:每行包含一个用户的若干信息字段,字段以类似 field:value的格式呈现,字段间可能由制表符或空格分隔。支持的字段包括 email, name, account, phone, birth。
-
示例:一行数据可能看起来像 email:john.doe@example.com name:John|Doe account:johndoe2024 phone:13800138000 birth:19900101。
-
预处理:程序会忽略以 #开头的注释行和空行。
-
-
输出文件 (OUTPUT_FILE = "answer.txt"):
-
格式:为每个用户生成一系列密码猜测,每个用户的猜测列表以 <END>行作为结束标记。文件结构如下:
guess1_user1
guess2_user1
…
<END>
guess1_user2
guess2_user2
…
<END>
-
-
关键全局参数:
-
PYDICTOR_PATH = "./pydictor.py":所依赖的核心密码生成工具路径。
-
MAX_GUESSES = 10000:为每个用户生成的密码猜测数量的上限,防止字典过大,是控制资源消耗的关键参数。
-
MIN_WORD_LEN = 3/ MAX_WORD_LEN = 16:生成的基础词根和最终密码的长度限制,符合常见密码策略。
-
-
降级方案 (WEAK_PASSWORDS):
-
当无法从用户信息中有效生成密码(如数据无效或提取失败)时,程序会使用一个预定义的常见弱密码列表作为降级方案,确保输出不为空。
-
三、 核心数据结构:用户信息字典
程序的核心在于处理用户信息。它定义了一个内部数据结构(通过 Python 字典实现)来规范化存储从原始数据行中提取的信息。extract_user_info(line)函数负责这个转换过程。
生成的用户信息字典结构如下:
{
'email': 'john.doe@example.com', # 字符串,邮箱地址
'name_parts': ['John', 'Doe'], # 列表,从`name`字段解析出的姓名部分
'account': 'johndoe2024', # 字符串,账号名
'phone': '13800138000', # 字符串,电话号码
'birth': '19900101' # 字符串,8位数字表示的生日(YYYYMMDD)
}
关于 name_parts的精细处理:
这是数据结构中的一个亮点。原始 name字段允许使用竖线 |分隔多个部分(如 name:John|Doe|Jr.)。该程序并非简单地将整个字符串作为一体,而是:
拆分:通过 split('|')将字符串拆分成多个部分。
清洗:对每个部分进行去除首尾空格的处理。
过滤:只保留长度大于等于2的有效部分。
这样做的目的是为了更细致地利用姓名中的每一个独立单元(名、姓、中间名等),因为用户可能会使用其中任意一部分或组合来创建密码。这种设计极大地增强了后续生成密码的多样性和针对性。
四、 算法与流程深度剖析
1. 主控制流程 (main函数)
主函数是程序的调度中心,其算法流程清晰,体现了模块化设计思想。
环境检查:调用 check_environment()验证 Pydictor 和数据文件的存在性。
数据读取:读取 datasets.txt,过滤空行和注释行,得到待处理行的列表 lines。
循环处理:对于 lines中的每一行(代表一个用户):
a. 信息提取:调用 extract_user_info(line),失败则标记为“失败”并写入降级密码。
b. 基础字典生成:将用户信息字典传递给 generate_base_dictionary(user_info),得到基础词根列表。
c. 密码猜测生成:将基础词根列表和数量限制传递给 generate_passwords(base_words, MAX_GUESSES),得到该用户的最终密码列表。
d. 结果写入:将密码列表写入文件,末尾追加 <END>分隔符。
进度反馈:使用 create_progress_bar函数在控制台打印美观的进度条,提升用户体验。
统计报告:处理完成后,打印成功/失败用户数,并计算输出文件的大小和总行数。
2. 基础字典生成算法 (generate_base_dictionary函数)
此函数是“个性化”的关键,它像一个信息炼金术士,从原始信息中提炼出密码的“胚子”。其算法策略是针对每个字段类型进行特定模式的特征提取。
-
账号 (account):
-
保存完整账号。
-
提取账号的前4位和前6位(如果账号长度允许)。例如 "johndoe2024"会提取出 "john"和 "johndo"。
-
-
姓名部分 (name_parts):
-
保存每个完整的姓名部分。
-
提取每个部分的前4个字符,并生成其小写和大写版本。
-
如果姓名部分包含点号(如 "John.Doe"),会进一步拆分成 "John"和 "Doe"并分别处理。
-
-
邮箱 (email):
-
提取 @符号前的本地部分。例如 john.doe@example.com提取出 john.doe。
-
-
电话 (phone):
-
使用正则表达式 r'\\D'移除非数字字符,得到纯数字串。
-
保存完整数字串、末4位、末6位,以及前4位(如果长度足够)。
-
-
生日 (birth):
-
假设生日是8位数字 YYYYMMDD。
-
保存完整生日、去掉世纪的年份(YYMMDD)、完整年份(YYYY)、两位年份(YY)、月日组合(MMDD)和日月组合(DDMM)。
-
-
通用弱项补充:额外加入一些常见数字模式,如 "123", "1234", 近年份等。
最终,所有这些提取出的字符串会经过一个过滤器:长度必须在 [MIN_WORD_LEN, MAX_WORD_LEN]之间,并放入一个集合 (set) 中以自动去除重复项,最终返回去重后的列表。 这个集合就是为该用户定制的“密码词根库”。
3. 密码猜测生成算法 (generate_passwords函数)
这是程序中最复杂且最强大的部分,它利用外部工具 Pydictor,对基础词根库应用多种密码构造学中常见的规则。其算法核心是多策略组合逼近。
-
临时工作区:函数开始时创建一个临时目录,所有 Pydictor 的中间文件都生成于此,处理完毕后自动清理 (shutil.rmtree),体现了良好的资源管理。
-
策略一:组合器 (-tool combiner)
-
算法:将基础词根文件同时作为两个输入提供给 Pydictor 的 combiner工具。该工具会生成所有可能的词根两两组合(笛卡尔积)。
-
示例:如果基础词根有 ["john", "1990"],组合后可能产生 "johnjohn", "john1990", "1990john", "19901990"。
-
目的:模拟用户将多个个人信息简单拼接的行为,能快速产生大量有意义的候选密码。
-
-
策略二:数字扩展 (-extend)
-
算法:为每个基础词根的后面添加指定长度的数字后缀。程序循环尝试了添加 1 到 4 位数字。
-
示例:对词根 "john"进行2位数字扩展,会生成 "john00", "john01", …, "john99"。
-
目的:模拟用户在密码后添加生日、年份、幸运数字等常见习惯。这是一种非常高效的生成策略。
-
-
策略三:1337 替换 (-leet)
-
算法:应用“黑话”替换规则,将字母替换为形状相似的数字或符号。例如:a-> @或4, e-> 3, i-> 1, o-> 0, s-> $等。
-
示例:"password"可能被变形为 "p@ssw0rd", "p@$$w0rd"等。
-
目的:模拟用户为了满足密码复杂性要求而进行的常见字符替换,极大地增加了字典的覆盖率。
-
-
流程控制与集合管理:
-
所有生成的密码都被添加到一个总集合 all_guesses中,利用集合的自动去重特性避免重复。
-
在每个策略执行后,检查 all_guesses的大小是否已达到 MAX_GUESSES。如果达到,则提前终止后续策略,有效控制字典膨胀。
-
如果所有策略生成的密码数量仍不足,则会回退到直接将基础词根加入集合。
-
函数最后,将集合转换为列表并截取前 MAX_GUESSES项作为最终结果。
这种算法设计确保了在有限的资源下,优先应用产出率高的策略,从而生成质量高、冗余度低的个性化密码字典。
五、 工程实现与优化点评析
健壮性:
-
异常处理:对 Pydictor 的调用进行了超时 (TimeoutExpired) 和错误 (CalledProcessError) 捕获,防止因单个用户处理失败而导致整个程序崩溃。
-
降级机制:当数据提取或密码生成失败时,使用预定义的弱密码列表,保证了程序的“韧性”。
-
资源清理:使用 try…finally结构确保临时目录无论处理成功与否都会被删除,避免了磁盘空间泄漏。
可维护性:
-
模块化:功能被清晰地划分成不同的函数,如 extract_user_info, generate_base_dictionary等,职责单一,便于测试和修改。
-
常量定义:将路径、限制等Magic Number定义为全局常量,易于查找和调整。
用户体验:
-
进度指示:create_progress_bar函数提供了直观的图形化进度条,对于处理大量用户时非常有用。
-
详细日志:在控制台打印每个策略生成的密码数量,让用户对生成过程有清晰的感知。
六、 总结
一个设计精良、实现稳健的专用密码字典生成工具。它成功地将一个复杂的安全测试任务自动化、智能化。其强大之处不在于发明新算法,而在于高效地整合利用现有工具(Pydictor)和领域知识(密码心理学),构建了一个高度定制化的数据处理管道。
通过精细的数据结构设计(如用户信息字典和姓名部分列表)、多层次的密码生成策略(组合、扩展、替换)以及严格的流程控制(去重、数量上限),该程序能够从有限的个人信息中挖掘出大量高价值的密码猜测,充分展示了在安全工程中,针对性的策略往往比蛮力更有效的核心思想。它不仅是一个实用的脚本,更是一个关于如何将行为分析应用于密码破解的优秀案例研究。
源代码
#!/usr/bin/env python3
import re
import subprocess
import os
import sys
import tempfile
import shutil
from pathlib import Path
PYDICTOR_PATH = "./pydictor.py"
DATA_FILE = "datasets.txt"
OUTPUT_FILE = "answer.txt"
MAX_GUESSES = 10000 # 每个用户最大猜测数
MIN_WORD_LEN = 3 # 最小词长度
MAX_WORD_LEN = 16 # 最大词长度
WEAK_PASSWORDS = [
"123456", "password", "12345678", "qwerty", "123456789",
"letmein", "1234567", "football", "iloveyou", "admin123",
"welcome", "monkey", "login", "abc123", "111111",
"123123", "password123", "1234", "12345", "dragon"
]
def check_environment():
if not os.path.exists(PYDICTOR_PATH):
print(f"错误: 未找到pydictor.py,请确认路径: {PYDICTOR_PATH}")
sys.exit(1)
if not os.path.exists(DATA_FILE):
print(f"错误: 未找到数据文件: {DATA_FILE}")
sys.exit(1)
def extract_user_info(line):
if not line or line.startswith('#'):
return None
def get_field(pattern, text, default=''):
match = re.search(pattern, text)
return match.group(1).strip() if match else default
email = get_field(r'email:([^\\t\\n]+)', line)
name_raw = get_field(r'name:([^\\t\\n]+)', line)
account = get_field(r'account:([^\\t\\n]+)', line)
phone = get_field(r'phone:([^\\t\\n]+)', line)
birth = get_field(r'birth:([^\\t\\n]+)', line)
name_parts = []
if name_raw:
parts = [p.strip() for p in name_raw.split('|')]
name_parts = [p for p in parts if p and len(p) >= 2]
return {
'email': email,
'name_parts': name_parts,
'account': account,
'phone': phone,
'birth': birth
}
def generate_base_dictionary(user_info):
words = set()
if user_info['account']:
acc = user_info['account']
words.add(acc)
if len(acc) > 3:
words.add(acc[:4])
words.add(acc[:6])
for part in user_info['name_parts']:
words.add(part)
if len(part) >= 4:
words.add(part[:4].lower())
words.add(part[:4].upper())
if '.' in part:
words.update([p.strip() for p in part.split('.') if len(p.strip()) >= 3])
if user_info['email'] and '@' in user_info['email']:
prefix = user_info['email'].split('@')[0]
if len(prefix) >= 3:
words.add(prefix)
if user_info['phone']:
digits = re.sub(r'\\D', '', user_info['phone'])
if len(digits) >= 4:
words.add(digits)
words.add(digits[-4:])
words.add(digits[-6:])
if len(digits) >= 8:
words.add(digits[:4])
if user_info['birth'] and len(user_info['birth']) == 8:
birth = user_info['birth']
year, month, day = birth[:4], birth[4:6], birth[6:]
words.update([
birth,
birth[2:],
year,
year[-2:],
month + day,
day + month
])
words.update(['123', '1234', '2020', '2021', '2022', '2023', '2024', '0', '1'])
final_words = {w for w in words if MIN_WORD_LEN <= len(w) <= MAX_WORD_LEN}
return list(final_words)
def execute_pydictor(args, temp_output):
try:
cmd = [sys.executable, PYDICTOR_PATH] + args + ['–output', temp_output]
subprocess.run(cmd, check=True, capture_output=True, timeout=30)
if os.path.exists(temp_output):
with open(temp_output, 'r', encoding='utf-8') as f:
return [line.strip() for line in f if line.strip()]
except subprocess.TimeoutExpired:
print("pydictor执行超时,跳过…")
except subprocess.CalledProcessError as e:
print(f"命令失败: {e.stderr[:80] if e.stderr else str(e)[:80]}")
except Exception as e:
print(f"意外错误: {str(e)[:80]}")
return []
def generate_passwords(base_words, limit=MAX_GUESSES):
if not base_words:
return WEAK_PASSWORDS[:limit]
temp_dir = tempfile.mkdtemp(prefix='pydictor_')
all_guesses = set()
try:
base_file = os.path.join(temp_dir, 'base.txt')
with open(base_file, 'w', encoding='utf-8') as f:
f.write('\\n'.join(base_words))
if len(base_words) >= 2 and len(all_guesses) < limit:
temp_out = os.path.join(temp_dir, 'comb.txt')
cmd = ['-tool', 'combiner', base_file, base_file]
guesses = execute_pydictor(cmd, temp_out)
all_guesses.update(guesses)
print(f" 组合策略: +{len(guesses)}条")
if len(all_guesses) < limit:
temp_out = os.path.join(temp_dir, 'ext.txt')
for digits in range(1, 5):
cmd = ['-extend', base_file, '0', '9', str(digits), str(digits)]
guesses = execute_pydictor(cmd, temp_out)
all_guesses.update(guesses)
if len(all_guesses) >= limit:
break
print(f" ├─ 数字扩展: +{len(guesses)}条")
if len(all_guesses) < limit:
temp_out = os.path.join(temp_dir, 'leet.txt')
cmd = ['-leet', base_file]
guesses = execute_pydictor(cmd, temp_out)
all_guesses.update(guesses)
print(f" ├─ 1337变形: +{len(guesses)}条")
if len(all_guesses) < limit // 2:
all_guesses.update(base_words)
print(f" ├─ 基础词汇: +{len(base_words)}条")
final_list = list(all_guesses)[:limit]
return final_list
finally:
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
def create_progress_bar(current, total, bar_length=40):
"""创建进度条"""
percent = current / total
filled = int(bar_length * percent)
bar = '█' * filled + '░' * (bar_length – filled)
return f'\\r|{bar}| {percent:.1%} ({current}/{total})'
def main():
print("=" * 60)
check_environment()
print(f"\\n读取数据文件: {DATA_FILE}")
with open(DATA_FILE, 'r', encoding='utf-8', errors='ignore') as f:
lines = [l.strip() for l in f if l.strip()]
print(f" 共 {len(lines)} 行数据")
successful = 0
failed = 0
with open(OUTPUT_FILE, 'w', encoding='utf-8') as out_f:
for idx, line in enumerate(lines, 1):
print(f"\\n[{idx}/{len(lines)}] 处理用户…", end=' ')
user_info = extract_user_info(line)
if not user_info:
failed += 1
print("跳过(无效数据)")
out_f.write('<END>\\n')
continue
base_words = generate_base_dictionary(user_info)
guesses = generate_passwords(base_words, MAX_GUESSES)
if guesses:
for guess in guesses:
out_f.write(guess + '\\n')
successful += 1
print(f"生成 {len(guesses)} 条")
else:
for pwd in WEAK_PASSWORDS[:100]:
out_f.write(pwd + '\\n')
failed += 1
print("使用默认密码")
out_f.write('<END>\\n')
sys.stdout.write(create_progress_bar(idx, len(lines)))
sys.stdout.flush()
print("\\n\\n" + "=" * 60)
print(" 处理完成!")
print(f"成功: {successful} 用户")
print(f" 降级: {failed} 用户")
print(f"输出文件: {OUTPUT_FILE}")
if os.path.exists(OUTPUT_FILE):
size_mb = os.path.getsize(OUTPUT_FILE) / 1024 / 1024
print(f"文件大小: {size_mb:.2f} MB")
with open(OUTPUT_FILE, 'r') as f:
total_lines = sum(1 for _ in f)
print(f"总行数: {total_lines:,} (含{total_lines // (MAX_GUESSES + 1)}个分隔符)")
if __name__ == '__main__':
main()

