本篇是最后一篇,加密小程序已经做完了,也可以打包带走。

这篇列出了几个改进,我的目的是:

  • 尽量改善用户体验
  • DEBUG

一、Fix the Bugs

函数参数顺序不同导致BUG

之前写完太激动,昨天耐着性子把所有功能走了一遍(原谅我,还不会写测试),结果是这样的:

  • 生成密钥正常
  • DES加解密正常
  • RSA加解密正常
  • 混合模式读不出密钥,报错信息如下:
File "E:\crypto\crypto\base.py", line 44, in getPubKey
    pub_key = rsa.PublicKey.load_pkcs1(keydata)
  File "C:\Python34\lib\site-packages\rsa\key.py", line 75, in load_pkcs1
    return method(keyfile)
  File "C:\Python34\lib\site-packages\rsa\key.py", line 243, in _load_pkcs1_pem
    der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY')
  File "C:\Python34\lib\site-packages\rsa\pem.py", line 91, in load_pem
    raise ValueError('No PEM start marker "%s" found' % pem_start)
ValueError: No PEM start marker "b'-----BEGIN RSA PUBLIC KEY-----'" found
  • 数字签名签名、正反验证均正常

为此我同时把混合模式、RSA,以及被调用的base都打开,一行一行的比对,结论是混合模式和RSA对应的调用部分完全相同,不可能出错。我又想到具体的方法调用发生在crypto.py里,一并打开重新看代码,发现以下不同:

# crypto.py
mymix.encMix(rawfilename,key_filename,mode,operation)

# mymix.py
def encMix(key_filename,rawfilename,mode,operation):

# myrsa.py
def encFile(rawfilename,key_filename,mode,operation):

唯一的区别就是:encMix()的参数名位置和其他两个不同,当crypto中调用时,会把源文件的路径传给密钥文件,造成无法读取。我自己编写时,都是每个函数单独测试,文件路径是input的,单个模块内使用参数顺序都是一样的,所以没发现问题。

果然,修改参数顺序后,运行正常。到这里整个程序就可以完全运行了。

操作成功后焦点停在DES初始值

测试时常用DES加密,发现操作成功后虽然输入框都清空了(本篇后面的一处改进),但鼠标焦点却在DES初始值处,这时除非用户输入8字节,否则必然会出现一次弹窗。

查找发现,在DES模式中,DES初始值是最后一个使用焦点输入的内容,步骤最后的源文件通常都是通过浏览文件选择的。

之前有想过让每个输入框默认为’disabled’,待选择模式后再判断激活,这样也好看,刚巧也可以解决这个BUG。

# ginterface.py

# 组件默认禁用状态
des_key_entry = Entry(key_frame,**state='disabled'**,textvariable=des_key)

#执行按钮调用方法
def cryption():
    ...
    #清空输入框
    if textVar == '操作完成':
        mode_choice.set('')
        operation_choice.set('')
        des_key_entry.delete(0,END)
        des_key_entry['state'] = 'disabled'  #清空后恢复禁用状态

二、Improvement

DES密钥有效性验证

DES密钥和初始值要求必须是8个字节,entry组件的验证功能就可以完成,没必要使用事件绑定。

对于entry组件的内容验证,可以看看鱼C的讲解,很详细

我希望当用户把焦点离开输入框时触发验证,如果不足8字节则清空输入框,并弹出提示框;为了清空输入框,就必须调用delete()方法,所以没办法让DES密钥和初始值验证使用同一个方法;弹出提示框可以用tkinter的标准对话框messagebox

# ginterface.py

# 验证DES密钥和初始值合法性
def valiDeskey():
    if len(des_key.get()) == 8:
        return True
    else:
        des_key_entry.delete(0,END)  # 不是8字节就直接清空
        messagebox.showerror('输入错误','请输入8字节DES密钥',default='ok',icon='warning')
        return False   # 验证函数的返回必须是True或False

# DES密钥
...
des_key_entry = Entry(key_frame,textvariable=des_key,validate='focusout',validatecommand=valiDeskey,width=10,show='*')
...

DES初始值的验证完全一样,只是把相应的函数名和变量名更改。

目前这种写法要求用户一旦选择输入DES密钥,就要确保输入8个字节,中途做任何其他的事情都会使输入框失去焦点,即弹出提示。

改进模式选择

目前的逻辑是:用户选择模式和操作,当焦点离开时,会根据已选模式和操作,判断密钥区哪一个输入框可用,但是我设置了默认值是DES和加密,用户有可能压根就不会把焦点放在模式选择框,因此不会对输入框状态进行更改,这种情况我是不允许的!

把模式和操作设置默认值的代码删掉,妥妥的逼用户选择。

操作成功后清空输入

用户很可能在操作一次DES后紧接着开始一次RSA操作,这时应该让软件界面恢复到刚打开的状态,也就是把所有的输入框都清空。我把清空操作添加在了’执行操作’的方法里,且只有当用户操作成功后才会清空。

# ginterface.py

def cryption():
    ...
    #清空输入框
    if textVar == '操作完成':
        mode_choice.set('')
        operation_choice.set('')
        des_key_entry.delete(0,END)  # Entry组件没有set方法
        des_IV_entry.delete(0,END)
        key_filename_entry.delete(0,END)
        sig_filename_entry.delete(0,END)
        rawfilename_entry.delete(0,END)

添加对话框清空功能

当用户觉得对话框内容过多时,可以一键清空。同时为了避免用户在对话框随意输入内容,我把对话框的默认state设为disabled,只有当打印操作进度或清空时短暂恢复normal

# ginterface.py

text = Text(height=10,width=60,bd=3,relief=SUNKEN,wrap=WORD,state='disabled') 
#高度、宽度、边框宽度和样式、按单词换行
text.pack(padx=1,pady=5)
def empty():
    text['state'] = 'normal'
    text.delete(1.0,END)  #1.0是起始位置
    text['state'] = 'disabled'
empty_button = Button(text='清空对话框',command=empty)
empty_button.pack(padx=20,pady=5,side=RIGHT)

捕获错误信息

为了让用户随心输入随时可以点击’执行操作’按钮,需要把代码运行中的错误信息捕获,并且对于用户选择的错误模式给出提示。

将原先的

# ginterface.py

def cryption():
    ...
    textVar = crypto.doCrypto(mode,operation,des_key,des_IV,key_filename,sig_filename,rawfilename)
    text['state'] = 'normal'
    text.insert(END,textVar)
    text.insert(END,'\n')
    text['state'] = 'disabled'

改为

def cryption():
    ...
    try:
        textVar = crypto.doCrypto(mode,operation,des_key,des_IV,key_filename,sig_filename,rawfilename)
    except:
        textVar = '请正确输入各项参数'
    text['state'] = 'normal'
    text.insert(END,textVar)
    text.insert(END,'\n')
    text['state'] = 'disabled'

然后在’crypto.py’中,根据用户的选择给返回值赋值,例如用户选择了DES-签名,就返回’请选择正确的操作’,测试结果如图:

wrfpython库最简单三个步骤 wolfram python_tkinter

给进度对话框安装垂直滚动条

tkinter的Scrollbar组件可以给Text组件安装垂直和水平滚动条,我需要做的事情有:

  • 新建一个Frame组件,作为对话框和滚动条的父组件
  • 设置Text组件的yscrollcommand选项为Scrollbar组件的set()
  • 设置Scrollbar组件的command选项为Text组件的yview()
#ginterface.py

#对话框框架,此框架作用是方便排版
text_frame = Frame(root)
text_frame.pack()

#滚动条和对话框
textbar = Scrollbar(text_frame,takefocus=False)  #滚动条不需要焦点
textbar.pack(side=RIGHT,fill=Y)
text = Text(text_frame,height=10,width=60,bd=3,yscrollcommand=textbar.set,relief=SUNKEN,wrap=WORD,state='disabled')
#高度、宽度、边框宽度和样式、按单词换行
text.pack(side=RIGHT,fill=BOTH)
textbar['command'] = text.yview  #yview是Text组件自带方法

清空无效输入框

用户可能会先选择DES模式,输入密钥后发现选错了,应该是RSA模式,这时候肯定是直接更改模式,已经输入的DES密钥需要在禁用前自动清空。

只需要在模式判断里加一行清空就可以,不过只有需要禁用的输入框添加这一行。

# ginterface.py

def judgMode(event):
    if mode.get() == 'DES':
        des_key_entry['state'] = 'normal'
        des_IV_entry['state'] = 'normal'
        **key_filename_entry.delete(0,END)**
        **sig_filename_entry.delete(0,END)**
        key_filename_entry['state'] = 'disabled'
        sig_filename_entry['state'] = 'disabled'

目前为止这个小软件就算完工了,还有一些考虑添加的功能暂时想不到办法,比如:

  • 只允许通过浏览文件选择文件,避免用户输入的错误
  • 进度对话框实时打印代码执行进度和操作耗时