整理资料时发现几个 zip 文件的密码忘记了,因而尝试用python暴力破解python
首先是读取和解压zip文件,使用 zipfile 库算法
import zipfile z = zipfile.ZipFile(r'./file.zip') z.extractall(pwd=password.encode('utf-8'))
定义一个密码元字符串,每次从里面取出一些字符,好比:app
meta_str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
使用 random.sample 生成指定长度的密码,而后出现过的密码放入一个 setdom
choiced_set = set() rand_str = ''.join(random.sample(meta_str, 4)) if rand_str not in choiced_set: choiced_set.add(rand_str)
为了避免断的产生密码并尝试解压,此处应该有循环,那么将尝试解压和生成密码封闭为函数:函数
import zipfile import random meta_str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' choiced_set = set() def guess(z, password): try: z.extractall(pwd=password.encode('utf-8')) print('guessed pwd: {}'.format(password)) return True except Exception: print('invalid password [{}]'.format(password)) return False def main(): z = zipfile.ZipFile(r'./file.zip') while True: rand_str = ''.join(random.sample(meta_str, 4)) if rand_str not in choiced_set: choiced_set.add(rand_str) if guess(z, rand_str): raise Exception('found')
此方法代码里固定写了只支持4个字符长度,若密码不是4个字符,则会无止境的循环下去,显然不科学。
为了支持n位密码字符,需对代码稍加修改:code
因为将生成的密码所有放入set,因此只须要判断set的长度,就知道当前生成了多少密码。
而密码字符能够重复,因此'abcd' 可生成的2位密码有:orm
'aa', 'ab', 'ac', 'ad', 'ba', 'bb', 'bc', 'bd', 'ca', 'cb', 'cc', 'cd', 'da', 'db', 'dc', 'dd,
至关于对字符串'abcd' 和 'abcd' 做笛卡尔积,那么一共是 len('abcd') * len('abcd') = 16 个密码,
因此n位密码的个数就是 len(meta_str) ** nip
random.sample 不支持重复字符,替换为 numpy.random.choice(sq, n, replace=True)utf-8
引入 numpy 并修改 main 函数:字符串
def main(): z = zipfile.ZipFile(r'./file.zip') meta_str_len = len(meta_str) meta_str_list = list(meta_str) while True: for n in range(1, 5): while len(choiced_set) != meta_str_len ** n: rand_str = ''.join(np.random.choice(meta_str_list, n, replace=True)) if rand_str not in choiced_set: choiced_set.add(rand_str) if guess(z, rand_str): raise Exception('found') choiced_set.clear()
以上代码虽然能够正常运行,可是解一个4位长度的密码须要计算 62 * 62 * 62 * 62 = 14776336 次,并且随着 choiced_set 的增加,生成非重复密码的几率愈来愈低,几乎是一个不可能完成的任务。
若是按顺序生成密码,总有一个密码能知足条件,好比: 元字符'abcd'生成2位密码,是一个笛卡尔积,其实就是一个字符全排列的过程,因此这里可使用深度优先算法生成字符
调用 dfs_str(0) 便可不断生成 4 位长度的密码
一个简单的栗子:
def guess(v): if v == 'abcd': print('guessed pwd: {}'.format(v)) return True else: print('invalid pwd: {}'.format(v)) return False meta_str = 'abcd1234' chars = [] def dfs_str(step): if step >= 4: if guess(''.join(chars)): raise Exception('found') return for ch in meta_str: chars.append(ch) dfs_str(step + 1) chars.pop()
使用这种方式 guess 函数处于深度优先搜索算法中,耦合紧密,那么有没有办法一次生成一个密码,而后传给 guess 呢?固然是有的,这时候轮到生成器出场了
实现一个密码生成器,只须要一些小的改动,在函数里增长 yield 关键字
def dfs_str_gen(step): if step >= 4: yield ''.join(chars) return for ch in meta_str: chars.append(ch) yield from dfs_str_gen(step + 1) chars.pop()
上面的代码已经限定了只能生成 4 位长度的密码,然而咱们的密码多是 1 2 3 4 5 甚至8位以上,难道每次都要手动修改吗?或者是在 dfs_str_gen 里传入参数?
固然传参能够,离个人目标很近了,然而更好的办法是包装成一个 class, 初始化时传入想要生成密码的长度:
class PasswordGenerator: meta_str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' def __init__(self, n=4): self.maxlen = n self.endstr = self.meta_str[-1] * self.maxlen # 标记循环结束字符串 self.chars = [] self.g = self._proxy_gen() def _dfs_str(self, step): if step >= self.maxlen: yield ''.join(self.chars) return for ch in self.meta_str: self.chars.append(ch) yield from self._dfs_str(step + 1) self.chars.pop() def _proxy_gen(self): while True: yield from self._dfs_str(0) def __iter__(self): return self def __next__(self): s = self.g.send(None) if s == self.endstr: self.g.close() return s
调用 PasswordGenerator(n) 便可生成 n 位长度的密码
写了这么多,其实标准库 itertools 已经有了解决方案
import itertools meta_str = 'abcdefg' itertools.product(meta_str, repeat=4)