在做完PC软件的基础功能后,接下来就是后续的功能完善和进一步拓展了
2018-10-28更新
- 加入U盘认证功能
- 加入拖拽文件功能
- 加入MD5码完整性校验
- 解决在接收服务器密钥时UI无响应的情况
- 加入顶栏菜单
-
U盘认证
U盘认证作为我一开始就放入这个软件的代办事项,在基础功能完成后我第一个想实现的就是它了。本来在初次构思这个功能的时候我是想通过python的某些系统模块来获取U盘的唯一识别码,再将这个识别码作为解密因子的一部分,结果上网查找发现对于U盘的唯一识别码的获取是十分模糊的, 特别是对于Python语言来说这更加困难(将Python和U盘作为关键词搜索搜到的几乎全是偷U盘文件的。。),所以就打算自己设计一个算法来实现U盘身份获取的唯一性。
首先我想到是在U盘内存放一个数据文件储存密钥因子,但是我认为只用这个是不够的,盗取U盘文件的成本还是很低的,所以应该还需要一个稳定的标志来辅助,我想到了U盘的一个天然的唯一标识符——U盘容量,然或曰劣质U盘有可能会突然缩水,但是以我的想法这种概率应该是小于用户误删密钥因子文件的概率。得到U盘的容量应当是非常容易的,得到后将其转化为字符串再与之前的密钥因子拼接,然后进行某些hash加密得到一段二进制字符串将其作为整个U盘的认证符号。
为了实现以上算法,在这里我使用了两个系统模块——pstuil和win32api,其中psutil一般是用来获取当前计算机的各类硬件信息的,包括内存使用率CPU占用率等等当然也包括磁盘信息啦。然后win32api是用来调用Windows的一些系统API的;其中在这个算法中psutil用来检测U盘的插入情况以及其容量信息,win32api用来隐藏密钥因子文件。
首先用psutil的disk_partitions方法得到当前磁盘信息List,取其长度,再提示用户插入U盘,检测长度的改变以及是否插入的是U盘,再获得U盘的挂载点并利用其获得U盘总容量(以字节为单位)并储存;接着在U盘根目录创建文件夹,以及文件夹里的文件,将一段经过序列化的随机字符串存入其中,之后再利用win32api的SetFileAttributes方法设置隐藏文件、文件夹;最后将随机字符串和容量拼接再使用hash加密得到密钥因子,传入主函数中。
在这之中需要注意的是当文件设置为隐藏后,就无法以写方式打开了(系统管理员可打开),即使设置Attributes时设置了NOMAL标签,不懂为什么,不过这确实才是我想要的,密钥文件被生成后应该需要被避免被修改。
-
拖拽文件
拖拽文件是一个炫酷的功能(对于菜鸟来说),所以想做个出来,网上一搜大一片案例,看起来确实很简单,总体的思路就是继承wx.FileDropTarget类,然后处理一下初始化函数,再重写一下OnDropFiles函数,这个函数在文件被拖拽并放下时被调用,在里面写上对拖拽对象的处理。需要注意的是,这个继承类一般在Frame内实例化,一般是传给他需要接收文件信息的控件作为初始化参数,然后用控件的SetDropTarget函数来设置拖拽对象到指定控件,在这里我直接使用主Panel作为DropTarget,路径编辑框作为拖拽类参数。
-
MD5码校验
MD5码校验的操作也算比较简单,只需调用hashlib的MD5函数获得处理类,然后用处理类的update方法导入需要加密的bytes串,再利用其digest函数获得MD5码串,发现多长的bytes串都是保存成16位的MD5码,然后放到密文的末尾就好。
-
解决在接收服务器密钥时UI无响应的情况
这个问题之前一直没怎么在意,因为认为只是没用单独的线程去运行认证函数而已,然后就用Thread把他放进了一个单独的线程里,再使用了join函数让主线程等待其拿到Key,结果发现界面还是会无响应,哔了狗了,再试了试独特的操作把join去掉看看还会不会卡,结果还卡,然后发现弹MessageBox的地方确实会卡住,然后看网上文章很多都说什么对UI的操作要放在主线程里,照做了(还单独为弹MessageBox写了个函数),结果还是会在接收密钥时卡住,然后再找文章,发现了DelayedResult这个函数,据文章上所说(这个文章好长。。看了半天看不到重点很捉急,不过不得不承认写的非常有条理),这是一个专门用来处理wxPython中的多线程处理的,不过不知道它的原理跟自己搞一个多线程有什么区别。
DelayedResult是wx.lib中的一个函数,参数很多,借文章的代码:
startWorker(consumer,
workerFn,
cargs=(), ckwargs={},
wargs=(), wkwargs={},
jobID=None,
group=None,
daemon=False,
sendReturn=True,
senderArg=None)
其中,workerFn是需要作为独立线程运行的函数;consumer是线程运行后运行的回调函数,需要注意的是此函数的第一个参数会被传入一个DelayedResult对象,所以consumer函数应该在定义时设置好,接到DelayedResult对象后可以在函数体中调用其.get()方法来获取线程的返回值。daemon是守护线程(线程是否随主线程一起结束),sendReturn就是是否chuandi返回值了,参数列表是谁的看其参数名首字母。整个函数非常简便,充分展现了封装性,在做这个函数时我发现之前做的倒计时线程没什么用处了,就把它的相关代码全删除了。
-
加入顶栏菜单
没什么说的,略。
总结
以上功能写了整一天,经过这次更新,总代码超过了300行,也算是个有点意思的项目了吧,就是写的代码愈多愈感觉代码缺少条理性,也就是所谓的程序设计结构,或者说其面向对象的思想没有充分贯彻到位吧(虽然一个main里都写了三个类了!),不过接下来一段时间都可以松口气了。