• 忘记zip密码咋办?python在手密码我有


    整理资料时发现几个 zip 文件的密码忘记了,于是尝试用python暴力破解

    首先是读取和解压zip文件,使用 zipfile 库

    import zipfile
    z = zipfile.ZipFile(r'./file.zip')
    z.extractall(pwd=password.encode('utf-8'))
    

    定义一个密码元字符串,每次从里面取出一些字符,比如:

    meta_str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    

    随机密码

    使用 random.sample 生成指定长度的密码,然后出现过的密码放入一个 set

    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位密码字符,需对代码稍加修改:

    1. 增加n位字符, for x in range(n) 可以解决
    2. n位密码长度的中止条件

    密码个数

    由于将生成的密码全部放入set,所以只需要判断set的长度,就知道当前生成了多少密码。
    而密码字符可以重复,所以'abcd' 可生成的2位密码有:

    '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) ** n

    random.sample 不支持重复字符,替换为 numpy.random.choice(sq, n, replace=True)

    引入 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 生成器

    写了这么多,其实标准库 itertools 已经有了解决方案

    import itertools
    meta_str = 'abcdefg'
    itertools.product(meta_str, repeat=4)
    
  • 相关阅读:
    Houdini 快捷键使用说明
    在Houdini中创建自定义的Python函数
    用正则表达式校验QQ号码
    [Chatter] 看小说「数字风暴」有感
    [.NET] 当用System.Messaging.MessageQueue.Send传送数据遇到InvalidCastException、NullReferenceException
    [Visual Studio] 方案总管中,自定义档案与档案之间的父子关系
    [.NET] 子对象方法的参数,参考子对象型别做为输入型别
    [Chatter] 引用新技术的考虑
    [Architecture Pattern] Inversion of Logging
    [Architecture Design] DI Thread Tips
  • 原文地址:https://www.cnblogs.com/wbjxxzx/p/10588624.html
Copyright © 2020-2023  润新知