本文继续记录学习下 Python 的有趣应用:借助 Python 脚本暴力破解 ZIP 加密文件的密码。虽然有相关的工具 ARCHPR 可实现 RAR、ZIP 等压缩加密文件的可视化暴力破解,但是主要是为了学习 Python 编程应用。
既然本意是学习 Python 编程,那自然是要对本实战应用场景的编码过程遇到的相关语法知识进行学习。
在此先推荐一个 Python 教程:Python教程。
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,迭代器有两个基本的方法:iter() 和 next()。
1、迭代器对象可以使用常规 for 语句进行遍历:
- #!/usr/bin/python3
- list=[1,2,3,4]
- it = iter(list) # 创建迭代器对象
- for x in it:
- print (x, end=" ")
-
- 执行以上程序,输出结果如下:
- 1 2 3 4
-
2、也可以使用 next() 函数:
- #!/usr/bin/python3
- import sys # 引入 sys 模块
-
- list=[1,2,3,4]
- it = iter(list) # 创建迭代器对象
- while True:
- try:
- print (next(it))
- except StopIteration:
- sys.exit()
-
- 执行以上程序,输出结果如下:
- 1
- 2
- 3
- 4
-
3、Python 支持编写 class 来自定义迭代器,如何自定义一个迭代器:
来看看一个简单的自定义可迭代的类示例:
- class TopTen(object):
- def __init__(self):
- self.num = 1
- def __iter__(self):
- return self
- def __next__(self):
- if self.num <= 10:
- val = self.num
- self.num += 1
- return val
- else:
- raise StopIteration
-
- if __name__ == "__main__":
- values = TopTen()
- for v in values:
- print(v, end=' ')
-
代码运行效果:
线程是 CPU 分配资源的基本单位,但一个程序开始运行后这个程序就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程也是一个主线程,但有多线程编程时,一个进程包含多个线程,包括主线程。使用线程可以实现程序的并发,Python 多线程快速入门可参见:python3 多线程编程。
Python3 线程中常用的两个模块为:
其中 thread 模块已被废弃,用户可以使用 threading 模块代替。所以在 Python3 中不能再使用 “thread” 模块,为了兼容性,Python3 将 thread 重命名为 “_thread”。
1、函数创建多线程
Python3 中提供了一个内置模块threading.Thread,可以很方便的创建多线程,threading.Thread()一般接收2个参数:
下面来看看一个简单的多线程示例:
- import time
- import threading
-
- # 自定义线程函数
- def my_threadfunc(name='python3'):
- for i in range(2):
- print("hello", name)
- time.sleep(1)
-
-
- if __name__=="__main__":
- # 创建线程01,不指定参数
- thread_01 = threading.Thread(target=my_threadfunc)
- # 启动线程01
- thread_01.start()
- # 创建线程02,指定参数,注意逗号不要少,否则不是一个tuple
- thread_02 = threading.Thread(target=my_threadfunc, args=('Tr0e',))
- # 启动线程02
- thread_02.start()
-
代码运行效果:
2、类创建多线程
首先,自定义一个类,对这个自定义的类有两个要求:
来看看示例代码:
- import time
- from threading import Thread
-
- class MyThread(Thread):
- def __init__(self, name='Python3'):
- super().__init__()
- self.name = name
-
- def run(self):
- for i in range(2):
- print("Hello", self.name)
- time.sleep(1)
-
3、 join() 方法
多线程中 join() 作用是调用 join() 的线程阻塞直到某一线程结束才继续执行。来看看示例代码:
- import threading
- import time
-
-
- class mythread(threading.Thread):
- def run(self):
- self.i = 1
- print("子线程运行秒数:",'%d' % (self.i))
- self.i = self.i + 1
- time.sleep(1) # 睡眠一秒
- print("子线程运行秒数:",'%d' % (self.i))
- time.sleep(1)
- print("子线程运行结束!")
-
-
- if __name__ == '__main__':
- ta = mythread() # 实例化线程
- ta.start() # 开启ta线程
- ta.join() # 主线程等待 ta线程结束才继续执行
- print("主线程结束!")
-
代码运行效果:
4、线程的同步——锁
当一个进程拥有多个线程之后,如果他们各做各的任务互没有关系还行,但既然属于同一个进程,他们之间总是具有一定关系的。比如多个线程都要对某个数据进行修改,则可能会出现不可预料的结果。为保证操作正确,就需要引入锁来进行线程间的同步。
Python3 中的 threading 模块提供了 RLock 锁(可重入锁):
来看看一个简单的演示案例:
- import threading
-
-
- class mythread(threading.Thread):
- def run(self):
- global x # 声明一个全局变量
- lock.acquire() # 上锁,acquire()和release()之间的语句一次只能有一个线程进入,其余线程在acquire()处等待
- x += 10
- print('%s:%d' % (self.name, x))
- lock.release() # 解锁
-
-
- x = 0
- lock = threading.RLock() # 创建 可重入锁
-
-
- def main():
- l = []
- for i in range(5):
- l.append(mythread()) # 创建5个线程,并把他们放到一个列表中
- for i in l:
- i.start() # 开启列表中的所有线程
-
-
- if __name__ == '__main__':
- main()
-
代码运行效果:
5、多线程函数小结:
- t = Thread(target=func)
- # 启动子线程
- t.start()
- # 阻塞子线程,待子线程结束后,再往下执行
- t.join()
- # 判断线程是否在执行状态,在执行返回True,否则返回False
- t.is_alive()
- t.isAlive()
- # 设置线程是否随主线程退出而退出,默认为False
- t.daemon = True
- t.daemon = False
- # 设置线程名
- t.name = "My-Thread"
-
下面将从单线程、多线程两种角度实现 ZIP 加密文件的密码爆破。
先来生成一个用数字密码(“101”)加密的 ZIP 压缩文件 password.zip,压缩文件为图片 pasword.png(注意勾选 “ZIP 传统加密” 的选项,后面的代码不支持 WinRAR 新式的默认加密方式),如下图所示:
爆破密码的脚本也相对简单,直接上代码:
- import zipfile
- import os
- import time
- import sys
-
- """
- 获取zip文件
- """
- def get_zipfile():
- os.chdir(r'D:\Code\Python\MyTest\Basic')
- files = os.listdir()
- for file in files:
- if file.endswith('.zip'):
- return file
-
- """
- 爆破zip文件
- """
- def extract():
- file = get_zipfile()
- zfile = zipfile.ZipFile(file) # 读取压缩文件
- start_time = time.time()
- for num in range(1, 99999): # 设置爆破的数字密码区间
- try:
- zfile.extractall(path='.', pwd=str(num).encode('utf-8'))
- print('解压密码是:', str(num))
- end_time = time.time()
- print('单线程破解压缩包花了%s秒' % (end_time - start_time))
- sys.exit(0) # 让程序在得到结果后,就停止运行,正常退出
- except Exception as e:
- print(e)
- #pass
-
-
- if __name__ == "__main__":
- extract()
-
以上代码没什么需要特别解释的,简单补充两点:
下面直接来看看 Pycharm 中运行脚本的效果:
先来看看脚本:
- import zipfile
- import random
- import time
- import sys
-
- class MyIterator():
- # 用于暴力破解的字符集合
- letters = 'abcdefghijklmnopqrstuvwxyz012345678'
- min_digits = 0
- max_digits = 0
-
- def __init__(self, min_digits, max_digits):
- # 下面的if-else是为了解决extract函数中,for循环中传递的密码长度可能前者的值大于后者,这一bug
- if min_digits < max_digits:
- self.min_digits = min_digits
- self.max_digits = max_digits
- else:
- self.min_digits = max_digits
- self.max_digits = min_digits
-
- # 迭代器访问定义,直接返回self实列对象
- def __iter__(self):
- return self
-
- # 通过不断地轮循,生成密码
- def __next__(self):
- rst = str()
- for item in range(0, random.randrange(self.min_digits, self.max_digits + 1)):
- # 从letters中随机取选取一个值,并把选取几次的结果拼接成一个字符,即一个密码
- rst += random.choice(MyIterator.letters)
- return rst
-
-
- def extract():
- start_time = time.time()
- zfile = zipfile.ZipFile("password.zip")
- # 随机迭代出5~6位数的密码
- for password in MyIterator(5, 6):
- try:
- zfile.extractall(path=".", pwd=str(password).encode('utf-8'))
- print("the password is {}".format(password))
- now_time = time.time()
- print("spend time is {}".format(now_time - start_time))
- sys.exit(0)
- except Exception as e:
- print(e)
- pass
-
-
- if __name__ == '__main__':
- extract()
-
将 password.png 重新压缩并将解压密码设置为 “ab12” 数字与字母组合的字符串,上述利用自定义迭代器生成的字符组合范围太广了,爆破起来可能跑到天荒地老……故演示此代码时我依据已知的密码对代码做了如下更改:
来看看脚本运行效果,还足足跑了 78 秒之久:
直接上脚本:
- import zipfile
- import threading
- import sys
- import os
-
-
- '''
- 多线程爆破完成
- '''
- def extractfile(zip_file, password):
- try:
- zip_file.extractall(path='.', pwd=str(password).encode("utf-8"))
- print('[+] 解压密码是:', password)
- sys.exit(0)
- except Exception as e:
- print("当前爆破的密码:%s 不正确!" % (password))
- pass
-
-
- def main():
- os.chdir(r'D:\Code\Python\MyTest\Basic')
- password_file = 'pwd.txt'
- files = os.listdir()
- for file in files: # 遍历当前路径下的所有文件
- if file.endswith('.zip'): # 爆破zip文件
- zip_file = zipfile.ZipFile(file)
- pass_file = open(password_file)
- for line in pass_file.readlines():
- password = line.strip('\n')
- t = threading.Thread(target=extractfile, args=(zip_file, password))
- t.start()
-
-
- if __name__ == '__main__':
- main()
-
代码运行效果:
个人感觉最后的多线程脚本实际上意义不大,仅供简单学习多线程使用……因为此程序中对每个密码的尝试都单开了一个线程、而尝试密码是否正常的逻辑函数 extractfile() 又十分简单,没有必要单开一个线程来浪费资源,除非说处理的逻辑函数 extractfile() 执行了十分耗时的操作(比如需要下载文件、或者说每次执行 extractfile() 函数都对一个单独的大型字典进行爆破等)。