python桌面端开发手记(序列化、压缩包、加密、图形界面GUI)
0x00
前段时间接到一个小项目是给某行业内部开发离线桌面端,业务流实现上总体分信息录入、加密导出。因为是win桌面端,所以老板说依托Access用VBA做,我据理力争了一下。之前就是用Access+VBA给项目组里各个单位做报销平台,二次开发的速度快,但是等到下发部署的时候遇到诸多问题,系统版本、位数的问题和Access版本、位数的问题,十分坎坷。然后这次的小项目单个用户产生的数据量不大,没有必要拖一个数据库在后面。所以跟老板说:直接把用户录入的信息加密后序列化到磁盘就好了,然后做一个加密打包的功能;而且甲方要的急,就想着用python许多函数不用自己再去写,开发快,最后转可执行文件就好了……以上一段freestyle之后,然后老板觉得OK!然后就开始填坑之旅了……
在把需求分析和设计做了一下后,自己决定按照【核心模块】-->【GUI】-->【编译成可执行文件】的流程走下去
0x01 核心模块##
这个部分比较简单,涉及到的主要功能有序列化为.json、反序列化、AES和RSA加密解密等。
1 序列化
1.1 从内存中的dict到磁盘上的json
import json
class Detail(object):#用一个类来封装必要的信息
def __init__(self, infodict):
self.serial_num = infodict['序号'] # 序号
self.type_code = infodict['类型'] # 类型
self.corporation = infodict['单位'] # 单位
self.submitter = infodict['姓名'] # 姓名
# ...
def inst2json(inst):#序列化函数
json_stuff = json.dumps(inst, default=lambda obj: obj.__dict__, ensure_ascii=False)
with open('filename.json', 'a') as f:#追加
f.write(json_stuff)
return
if __name__=='__main__':
myinfodict={}
#向infodict添加必要的键值
myinfodict['序号']='XUHAO'
myinfodict['类型']='LEIXING'
myinfodict['单位']='DANWEI'
myinfodict['姓名']='XINGMING'
# ...
myinst=Detail(myinfodict)#实例化
try:
inst2json(myinst)
except:
print('序列化失败')
else:
print('序列化成功')
1.2 从磁盘上的json到内存中的dict
import json
with open('filename.json','r') as f:
dict_stuff=json.load(f)
print(dict_stuff)
KEY WORDS:
- 将字典序列化为json
- 将字典保存为json
- json反序列化
- 将json读成字典
2 加解密
填坑:
装pycrypto这个包的时候可能会报错,需要先装winrandom-1.2.1-cp35-cp35m-win32
2.1 AES加密
import struct
from Crypto.Cipher import AES
import hashlib
import os
def encrypt_file(password, in_filename, out_filename=None, chunksize=64 * 1024):
key = hashlib.sha256(password.encode('utf-8')).digest()
if not out_filename:
out_filename = in_filename + '.crypt'
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
from random import Random
random = Random()
for i in range(16):
str += chars[random.randint(0, length)]
iv = str.encode()
encryptor = AES.new(key, AES.MODE_CBC, iv)
filesize = os.path.getsize(in_filename)
with open(in_filename, 'rb') as infile:
with open(out_filename, 'wb') as outfile:
outfile.write(struct.pack('<Q', filesize))
outfile.write(iv)
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += bytes(' ', encoding="utf8") * (16 - len(chunk) % 16)
outfile.write(encryptor.encrypt(chunk))
if __name__=='__main__':
try:
encrypt_file('password','plain.txt','secret.crypt')
time.sleep(3)
except:
print('加密成功')
else:
print('加密失败')
由于程序直接将用户输入序列化到磁盘,所以应该加密存储,也可以用上面的代码实现。读入到程序中时在解密,保证用户不会绕过程序读到有价值的数据。
为了防止被逆向出password,在这个项目里不讲AES的密钥硬编码到程序中,而是采用让用户输入密钥种子,在用种子生成AES的密钥,并把这个密钥后用RSA公钥加密的方法来保证数据安全。用AES+RSA而不是直接对所有数据进行RSA是因为数据量大时计算耗时比较小
2.2 RSA密钥对生成
from Crypto.PublicKey import RSA
def generate_RSA(bits=2048):
new_key = RSA.generate(bits, e=65537)
public_key = new_key.publickey().exportKey("PEM")
private_key = new_key.exportKey("PEM")
return private_key, public_key
def keygen():
private,public=generate_RSA()
with open('private.pem','wb') as private_key:
private_key.write(private)
with open('public.pem','wb') as public_key:
public_key.write(public)
if __name__=='__main__':
keygen()
2.3 RSA加密
# -*- coding:utf-8 -*-
import base64
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.PublicKey import RSA
def encrypt_key(pwd):
with open('public.pem', 'rb') as f:
pubkey = f.read()
rsakey = RSA.importKey(pubkey)
cipher = Cipher_pkcs1_v1_5.new(rsakey)
cipher_text = base64.b64encode(cipher.encrypt(pwd.encode()))# encrypt的参数是变长字节串
with open('key.crypt', 'wb') as k:
k.write(cipher_text)
if __name__=='__main__':
password='d0main'#
try:
encrypt_key(password)
except:
print('失败')
else:
print('密钥加密成功')
至此,就可以将公钥算法加密的对称密钥和对称加密的数据一起打包了
2.4 RSA解密
# -*- coding=utf-8 -*-
from random import Random
import os
import base64
from Crypto import Random
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.PublicKey import RSA
def decrypt_key(key_file):
random_generator = Random.new().read
if os.path.exists('private.pem'):
with open('private.pem','rb') as f:
key = f.read()
rsakey = RSA.importKey(key)
cipher = Cipher_pkcs1_v1_5.new(rsakey)
with open(key_file,'rb') as k:
encrypt_text=k.read()
pwd = cipher.decrypt(base64.b64decode(encrypt_text), random_generator)
return pwd.decode()
else:
print('未找到私钥')
if __name__=='__main__':
print('口令是:'+decrypt_key('key.crypt'))
拿到了口令值后,就可以进行AES解密了
2.5 AES解密
import struct
from Crypto.Cipher import AES
import hashlib
import os
def decrypt_file(password, in_filename, out_filename=None, chunksize=24*1024):
key=hashlib.sha256(password.encode('utf-8')).digest()
if not out_filename:
out_filename = os.path.splitext(in_filename)[0]+'.txt'
with open(in_filename, 'rb') as infile:
origsize = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0]
iv = infile.read(16)
decryptor = AES.new(key, AES.MODE_CBC, iv)
with open(out_filename, 'wb') as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(decryptor.decrypt(chunk))
outfile.truncate(origsize)
if __name__=='__main__':
try:
decrypt_file('password', 'secret.crypt', 'plain.txt')
time.sleep(3)
except:
print('解密成功')
else:
print('解密失败')
KEY WORDS:
- python加密
- RSA加密
- AES加密
3 zip文件操作
3.1 打包
import zipfile
import os
def compress(startdir):
z = zipfile.ZipFile('filename.zip', 'w', zipfile.ZIP_DEFLATED)
for dirpath, dirnames, filenames in os.walk(startdir):
for filename in filenames: #可以在这里对要打包的文件做筛选
z.write(os.path.join(dirpath, filename))
z.close()
if __name__=='__main__':
dirpath='需要打包的文件夹路径'
try:
compress(dirpath)
except:
print('打包失败')
else:
print('打包成功')
3.2 解包
import zipfile
def uncompress(zippath):
f = zipfile.ZipFile(zippath,'r')
for file in f.namelist():
f.extract(file,".")
if __name__=='__main__':
zippath='filename.zip'
uncompress(zippath)
KEYWORDS:
- python zip压缩文件
- python 解压缩zip
0x02 图形化界面-GUI
用python来做GUI的过程是艰难的。虽然有诸如PyQt、PySide、Tkinter、wxPython许多工具包,但是想要像C#、VB那样在IDE中拖拽控件基本做不到,都是要一行一行写,换言之就是“写界面”!所以决定直接用Python3.5自带的Tkinter来写,而且这一个包相比于其他的工具包,学习起来最快,成本最低。最终决定用Tkinter!
项目涉及到的组件(widget)种类很多,要说起来篇幅过长,一些细节可能会日后更新在主页中,而且网上的教学贴不少,所以就不过多赘述。
心得:由于我的这个项目涉及到许多页面跳转,需要来回调用窗口,因此将窗口(组件)对象化是正确的做法,而非过程化的创建。将不同的窗口封装类,调用的时候实例化,从而避免代码重复。
KEYWORDS:
- python 图形化界面
- python GUI
- tkinter
0x03 从 .py 到 .exe##
这个过程也是痛苦的(为什么这么坎坷)。就我所知有py2exe和pyinstaller,wiki告诉我还有cx_Freeze。
我的环境是python3.5,直觉告诉我应该找一个不断更新的,否则后面坑更多。py2exe在2014出了支持python3的版本之后也没有再维护,cx_Freeze的官网看起来维护的也没pyinstaller的漂亮(我怎么以貌取人),所以我还是选择使用pyinstaller V3.2.1
填坑:
装这个pyinstaller也没少报错,如果报错可能是缺少一些开发组件。PyInstaller在win上需要两个模块。一个是PyWin32或者pypiwin32。如果还没装PyWin32并且使用pip安装PyInstaller,那么 pypiwin32会伴随着自动给你装上。出了上述的模块,还需要pefile这个包。我是在装了pywin32-221.win32-py3.5之后才把pyinstaller装上的。
- pyinstaller的使用请参阅pyinstaller文档
- 也可参考友站https://www.crifan.com/use_pyinstaller_to_package_python_to_single_executable_exe/
常用-F来让pyinstaller只生成一个文件而没有一对其余的组件,用-w来让生成的程序只打开窗口而不是控制台,使用--icon 来设置程序的图标(图标文.ico)。如:
pyinstaller --clean --win-private-assemblies -w -F --icon=new.ico demo.py
这样,在demo.py的目录下回生成两个文件夹,编译好的可执行文件在dist文件夹下,尝试打开,如果不能正常运行,可以前往build下查看warn.txt查看是不是有哪些依赖没有被编译而出错。
填坑:
起初,我的加密模块需要用PyCryptodome。PyCryptodome是从PyCrypto fork出来的,对比PyCrypto加了一些特性,被视为PyCrypto的替代品,所以我已开始也没有装PyCrypto而是直接装PyCryptodome,结果就是编译出来的exe无法运行,提示FATAL ERROR:failed to execute script。后来卸载了PyCryptodome,装了PyCrypto,把代码改了(就是本文第一部分提到的代码),在重新编译后方可正常运行可执行文件。
KEYWORDS:
- 将python文件转换成exe
- 将python文件转换成可执行文件
- 将py转换成exe
0x04 参考文献
1.Mark, Lutz. Python编程:第4版[M]. 北京:中国电力出版社, 2015.
python桌面端开发手记(序列化、压缩包、加密、图形界面GUI)的更多相关文章
- python selenium 使用htmlunit 执行测试。非图形界面浏览器。
其实就是换个浏览器,只是这个浏览器没有图形界面而已. browser = webdriver.Chrome() 换成 browser = webdriver.Remote(desired_capabi ...
- python全栈开发day99-DRF序列化组件
1.解释器组件源码分析 https://www.processon.com/view/link/5ba0a8e7e4b0534c9be0c968 2.基于CBV的接口设计 1).django循环que ...
- python之图形界面GUI开发 Tkinter 2014-4-7
1.导入Tkinter 可以使用以下三种方法(1)from Tkinter import *#导入Tkinter(2)import TkinterTkinter.methodA使用 Tkinter.m ...
- 【Python】 用户图形界面GUI wxpython III 更多组件
wxpython - 更多组件 我写到的这些组件可能一来不是很详细,二来不是最全的,想要更好地用这些组件,应该还是去看看教程和别的示例.比较简单的,推荐http://download.csdn.net ...
- 【Python】 用户图形界面GUI wxpython I 基本用法和组件
wxpython - 基本用法和组件 wxpython是python对跨平台GUI库wxWidgets的封装.wxWidgets是由C++写成的. wxpython被包装进了wx模块中,用它设计GUI ...
- python简单图形界面GUI入门——easygui【转】
原文:https://blog.csdn.net/mingqi1996/article/details/81272621 感觉gui做起来成就感比较高,学完基础语言顺便花一个下午看看GUI设计,现在回 ...
- 【Python】 用户图形界面GUI wxpython IV 菜单&对话框
更多组件 ■ 菜单栏 Menu 菜单是很多GUI必不可少的一部分.要建立菜单,必须先创建菜单栏: menuBar = MenuBar() menu = Menu() item1 = menu.Appe ...
- 【Python】 用户图形界面GUI wxpython II 布局和事件
wxpython - 布局和事件 这章主要记录布局器Sizer以及事件的用法. // 目前还需要记录的:Sizer的Add方法加空白,Sizer的Layout,Sizer的Remove如何有效 ■ 布 ...
- Python做web开发,推荐几个能立马上手的小项目
Python这门优美的语言是非常适合web开发的,基于Python的Django框架简单便捷且很强大. 那么作为新手该如何上手这门语言?一切不敲代码的学编程手段都是扯淡,今天就推荐一些适合新手练手的P ...
随机推荐
- 这些git命令判断提交到哪个分支哪个项目上
git branch -r fuweikun@pengfei:~/e1_cp/AMSS$ git branch* 8939-E1-2104026-dev git config -l fuweikun@ ...
- python文件相关
文件操作基本流程初探 f = open('chenli.txt') #打开文件 first_line = f.readline() print('first line:',first_line) #读 ...
- [ZJOI2015]诸神眷顾的幻想乡(广义后缀自动机)
/* 题目中的神仙性质真的是令人愉悦 因为我一眼看成了每个点的度数不超过二十, 心想这他喵的和字符串什么关系 统计树上不同子串个数, 按道理直接dfs n次把所有的串插到后缀自动机里就行了 但是我们发 ...
- linux&php:ubuntu安装php-7.2
1.下载php源码,地址:http://www.php.net/downloads.php 这里下载的是tar.gz的包 2.解压安装 将安装包解压到/usr/local/php 安装C的编译工具 s ...
- js样式之渐变线
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- eclipse 添加svn资源库卡死。长时间等待
使用https://localhost:8443/svn/xx方式打入浏览器判断其服务器是否正常 如果正常通过,而eclipse新建库卡死时.可以等待一点时间看是否卡 问题依旧,考虑更改地址 主机名 ...
- 搭建eclipse开发环境
eclipse-jee配置 基本配置: 快捷查找:window->perferences->搜索框搜索 utf8: window->perferences->general-& ...
- <转载> nginx服务器安装及配置文件详解 https://segmentfault.com/a/1190000002797601
nginx在工作中已经有好几个环境在使用了,每次都是重新去网上扒博客,各种编译配置,今天自己也整理一份安装文档和nginx.conf配置选项的说明,留作以后参考.像负载均衡配置(包括健康检查).缓存( ...
- Python基础3 函数、递归、内置函数
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- idea 添加代码自动提示支持,已PHP扩展 swoole 为例
1,下载代码支持包 => swoole-ide-helper-en => https://github.com/eaglewu/swoole-ide-helper.git 2,如果安装了 ...