整理資料時發現幾個 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)