AI摘要
本文介绍利用已知密码片段(前缀/后缀)爆破加密 ZIP/RAR 压缩包的方法。内容涵盖暴力枚举原理、Python 单/多线程实现、字符集优化及 hashcat 工具加速,适用于 CTF 密码破解场景。
概述
CTF MISC / Crypto 题目中常见一类题型:给出一个加密的 ZIP/RAR 压缩包,并提示密码的一部分(如前缀、后缀或已知片段),要求爆破剩余未知位得到完整密码。
全空间爆破 $62^6 \approx 5.6 \times 10^{10}$ 不可行,但已知部分后剩余空间缩小到 $62^n$($n$ 为未知位数),枚举即可。
已知密码格式: ? ? ? ? LiHua
↑ ↑
4个未知字符 (62种可能/位)
总尝试次数: 62^4 ≈ 1.48 × 10^7暴力枚举原理
搜索空间计算
| 未知位数 | 字符集大小 | 尝试次数 | 单线程耗时估算 |
|---|---|---|---|
| 2 | 62 | 3,844 | < 1 秒 |
| 3 | 62 | 238,328 | ~2 秒 |
| 4 | 62 | 14,776,336 | ~2 分钟 |
| 5 | 62 | 916,132,832 | ~2 小时 |
| 6 | 62 | $5.68 \times 10^{10}$ | 不可行 |
瓶颈不在 CPU 而在 I/O:每尝试一次密码都需要打开压缩包 → 文件 I/O 是主要耗时。
攻击链路
已知部分密码: "LiHua" (后缀)
未知位: 前 4 位
│
▼
┌──────────────────────┐
│ 生成候选密码 │
│ itertools.product │
│ '????' + 'LiHua' │
└──────────┬───────────┘
│ 逐条生成
▼
┌──────────────────────┐
│ 尝试解压 │
│ zipfile.extractall │
│ / rarfile.extract │
└──────────┬───────────┘
├─ 密码错误 → 下一条
│ (无异常或 RuntimeError)
│
▼ 解压成功!
┌──────────────────────┐
│ 输出正确密码 │
│ 文件成功提取 │
└──────────────────────┘Python 实现
原始脚本分析(MD5 匹配法)
import string
from hashlib import md5
a = string.ascii_letters + string.digits # 62 个字符: a-z, A-Z, 0-9
for a1 in a:
for a2 in a:
for a3 in a:
for a4 in a:
pwd = a1 + a2 + a3 + a4 + 'LiHua'
h = md5(pwd.encode('ascii')).hexdigest()
if '1a4fb3fb5ee' in h:
print(pwd)
break脚本解读:
| 行 | 说明 |
|---|---|
string.ascii_letters + string.digits | 字符集 = 52 个字母 + 10 个数字 = 62 |
四层嵌套for | 枚举所有 4 位组合,时间复杂度$O(62^4)$ |
md5(...).hexdigest() | 对候选密码做 MD5 哈希 |
'1a4fb3fb5ee' in h | 比对部分哈希值(CTF 常只给出哈希片段) |
break | 仅跳出最内层循环(Python 的 break 不穿透多层) |
此方法的适用场景: 题目只给了密码的 MD5 部分值(而非加密压缩包),只需匹配哈希片段即可。
完整版:ZIP 密码爆破
import zipfile
import itertools
import string
def crack_zip(zip_path: str, known_suffix: str, unknown_len: int,
charset: str = None, output_dir: str = '.') -> str:
"""
已知密码后缀,爆破 ZIP 文件的前缀部分
参数:
zip_path: 加密 ZIP 文件路径
known_suffix: 已知的密码后缀(如 'LiHua')
unknown_len: 未知前缀的位数
charset: 字符集(默认 a-z, A-Z, 0-9)
output_dir: 解压输出目录
返回:
完整密码,或 None
"""
if charset is None:
charset = string.ascii_letters + string.digits # 62 字符
total = len(charset) ** unknown_len
print(f'字符集大小: {len(charset)}, 未知位: {unknown_len}')
print(f'总尝试次数: {total:,}')
with zipfile.ZipFile(zip_path, 'r') as zf:
for idx, prefix in enumerate(
itertools.product(charset, repeat=unknown_len)
):
pwd = ''.join(prefix) + known_suffix
if idx % 50000 == 0:
progress = idx / total * 100
print(f'[{progress:.2f}%] 尝试中... 当前: {pwd}', end='\r')
try:
zf.extractall(path=output_dir, pwd=pwd.encode())
print(f'\n[成功] 密码: {pwd} (第 {idx:,} 次尝试)')
return pwd
except (RuntimeError, zipfile.BadZipFile):
continue # 密码错误,继续
print(f'\n[失败] 遍历全部 {total:,} 个组合未找到密码')
return None
# 使用示例
crack_zip('flag.zip', known_suffix='LiHua', unknown_len=4)多线程加速版
单线程瓶颈在 ZIP 解压 I/O,多线程可并行尝试不同前缀:
import zipfile
import itertools
import string
from concurrent.futures import ThreadPoolExecutor, as_completed
def try_password(zip_path, pwd, output_dir):
"""尝试单个密码,成功返回密码,失败返回 None"""
try:
with zipfile.ZipFile(zip_path, 'r') as zf:
zf.extractall(path=output_dir, pwd=pwd.encode())
return pwd
except (RuntimeError, zipfile.BadZipFile):
return None
def crack_zip_mt(zip_path, known_suffix, unknown_len,
charset=None, workers=8):
if charset is None:
charset = string.ascii_letters + string.digits
total = len(charset) ** unknown_len
print(f'总尝试: {total:,} | 线程: {workers}')
# 预生成所有候选密码
passwords = (
''.join(p) + known_suffix
for p in itertools.product(charset, repeat=unknown_len)
)
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(try_password, zip_path, pwd, '.'): pwd
for pwd in itertools.islice(passwords, 10000) # 批次提交
}
for future in as_completed(futures):
result = future.result()
if result:
print(f'[成功] 密码: {result}')
executor.shutdown(wait=False, cancel_futures=True)
return result
return NoneRAR 文件爆破
import rarfile
import itertools
import string
def crack_rar(rar_path: str, known_part: str, unknown_len: int,
position: str = 'prefix') -> str:
"""
RAR 密码爆破 (需要系统安装 unrar)
position: 'prefix' (未知在前) / 'suffix' (未知在后)
"""
charset = string.ascii_letters + string.digits
for combo in itertools.product(charset, repeat=unknown_len):
extra = ''.join(combo)
pwd = (extra + known_part) if position == 'prefix' \
else (known_part + extra)
try:
with rarfile.RarFile(rar_path) as rf:
rf.extractall(pwd=pwd)
print(f'[成功] 密码: {pwd}')
return pwd
except rarfile.BadRarFile:
continue
return None字符集优化
场景自适应
| 已知信息 | 推荐字符集 | 大小 | 4 位枚举次数 |
|---|---|---|---|
| 无提示 | ascii_letters + digits | 62 | $1.48 \times 10^7$ |
| 仅小写字母 | ascii_lowercase | 26 | 456,976 |
| 仅数字 | digits | 10 | 10,000 |
| 仅小写+数字 | ascii_lowercase + digits | 36 | $1.68 \times 10^6$ |
| 已知密码为常见词 | 自定义词表 | ~10³ | 极快 |
# CTF 常见字符集预设
CHARSETS = {
'lower': string.ascii_lowercase, # 26
'upper': string.ascii_uppercase, # 26
'letters': string.ascii_letters, # 52
'digits': string.digits, # 10
'lower+digits': string.ascii_lowercase + string.digits, # 36
'all': string.ascii_letters + string.digits, # 62
'printable': string.printable.strip(), # 94
}优化策略对比
# 原始写法:4 层嵌套 — 代码冗余,无法动态调整位数
for a1 in a:
for a2 in a:
for a3 in a:
for a4 in a:
pwd = a1 + a2 + a3 + a4 + 'LiHua'
# ...
# 优化写法:itertools.product — 一行搞定任意位数
for prefix in itertools.product(a, repeat=4):
pwd = ''.join(prefix) + 'LiHua'
# ...CTF 实战
常见题目模式
| 题型 | 特征 | 解法 |
|---|---|---|
| 已知后缀爆破 | 提示"密码后4位是 LiHua" | 枚举前缀,如本文示例 |
| 已知前缀爆破 | 提示"密码以 FLAG 开头" | 枚举后缀 |
| 已知结构爆破 | 提示"密码为 字母+4位数字" | 先枚举字母,再枚举数字段 |
| 已知 MD5 片段 | 给出md5(pwd)[6:16] 的部分值 | 仅做 MD5 比对,无需解压(速度极快) |
| 掩码爆破 | 提示格式????LiHua???? | 前后同时枚举,或分步还原 |
| 生日攻击 | 提示"密码是某人生日" | 生成 1950~2020 所有日期格式 |
| 字典 + 规则 | 提示"密码是英文单词+2位数字" | 加载字典文件 + 枚举数字后缀 |
MD5 片段匹配 vs 解压尝试
| 方法 | 每次操作耗时 | 适用场景 |
|---|---|---|
| MD5 哈希比对 | ~0.5 μs(微秒) | 仅给出哈希片段,无需实际压缩包 |
| ZIP 解压尝试 | ~5 ms(毫秒) | 有实际加密 ZIP,需要解密 |
| RAR 解压尝试 | ~20 ms | RAR 加密包(比 ZIP 慢 4 倍) |
MD5 比对比 ZIP 解压快 10,000 倍。如果题目给了哈希片段且明确密码格式,优先用 MD5 匹配效率最高。
利用 hashcat 加速
对于更大搜索空间(如 6 位未知),Python 单线程太慢,可结合 hashcat 掩码模式:
# hashcat 掩码攻击:已知后缀 LiHua,前 4 位为小写字母
# 先提取 ZIP 的 hash
zip2john flag.zip > hash.txt
# 掩码爆破 (?l = 小写字母)
hashcat -m 13600 -a 3 hash.txt '?l?l?l?lLiHua'
# 数字 (?d) + 字母 (?l) 混合
hashcat -m 13600 -a 3 hash.txt '?l?d?l?dLiHua'| 工具 | 适用搜索空间 | 速度(4 位未知) |
|---|---|---|
| Python (单线程) | $< 10^7$ | ~2 分钟 |
| Python (8 线程) | $< 10^8$ | ~30 秒 |
| John the Ripper | $< 10^9$ | ~10 秒 |
| hashcat (GPU) | $< 10^{12}$ | < 1 秒 |
常见掩码模式速查
# hashcat 掩码字符
?l = abcdefghijklmnopqrstuvwxyz
?u = ABCDEFGHIJKLMNOPQRSTUVWXYZ
?d = 0123456789
?h = 0123456789abcdef
?H = 0123456789ABCDEF
?s = !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
?a = ?l?u?d?s (95 字符全集)
?b = 0x00 - 0xff
# 常见掩码示例
?a?a?a?a # 4 位全字符 (95^4)
?d?d?d?d # 4 位纯数字 (10^4)
?l?l?l?lLiHua # 4 位小写 + 固定后缀
FLAG?d?d?d?d # 固定前缀 + 4 位数字
?u?l?l?l?l?d?d # 1大写 + 4小写 + 2数字参考文献
- Python Software Foundation. itertools — Functions creating iterators for efficient looping [EB/OL]. https://docs.python.org/3/library/itertools.html
- Python Software Foundation. zipfile — Work with ZIP archives [EB/OL]. https://docs.python.org/3/library/zipfile.html
- hashcat. hashcat — Advanced Password Recovery [EB/OL]. https://hashcat.net/hashcat/
- Openwall. John the Ripper password cracker [EB/OL]. https://www.openwall.com/john/
- Python Software Foundation. hashlib — Secure hashes and message digests [EB/OL]. https://docs.python.org/3/library/hashlib.html


