课程学习 · 2022年10月22日

ADFGVX加解密机的PyQT实现

这是信息系统安全课的作业思路(有实现思路,可以看懂了自己去实现,不放代码了),好不容易码了两天,炫耀一下,大伙来看看。

下面是实现说明:

ADFGVX加/解密机

这是一个ADFGVX加/解密机的程序说明,该程序有加密、解密功能,并且拥有美观的图形化界面。

一、功能介绍

1.1运行界面

image-20221011231547693

界面分为左右两个部分。左边的部分从上自下依次为6*6的方阵棋盘,移位密钥,加密解密模式选择按钮,和需要加密或解密的输入文本;右边的一栏自上而下是执行过程中的提示信息和最后的执行结果。

1.2本程序有如下亮点
  1. 方阵棋盘可以自由编辑。选择默认棋盘按钮可以使用英文字母A到Z和数字0到9进行顺序填充,点击中间的随机生成按钮可以随机打乱这36个字符的顺序。使用一键清除可以将这36个方格内容全部清除。值得说明的是,每次点击随机生成会随机生成新的棋盘,每次随机生成的内容原则上不会相同,并且保证每次生成的棋盘中有着36个字符并且每种字符只出现一次,也就是这36个字符的一种排列。如图是使用随机生成按钮后显示的某一次棋盘的格局状况。

    image-20221011232359187

  2. 选择模式单选按钮可以在加密模式和解密模式之间自由切换。为了方便用户使用同一个方阵棋盘和同一个加密移位密钥来进行加密再解密,用户只需要简单地从加密模式按钮点击到解密模式上就可以直接完成运行状态的切换,而无需重新填写36个方格。 需要加密或解密的文本均填写在左下角的输入框内节约了页面空间,十分美观。如图是将上述得到的密文通过同样的密钥进行解密的执行过程截图:

    image-20221011233014685

    值得说明的是,对于要进行解密的左下角密文输入框内的字符中间可以留有空格而不被解析,此外对于其中的符号例如_,也是可以直接输入在内的而不会影响结果的准确性。

  3. 执行过程清楚明了。程序将进行加密或解密的计算过程的中间步骤显示在右上方,能够方便用户在看到执行结果的同时看到计算过程,让用户心里踏实。此外,执行结果也采用文本框的内容输出,用户可以方便地对结果进行一个复制操作。

1.3使用本程序请注意
  1. 6*6的棋盘方格中的每一个格子里只能填写一个字符,这个字符只能由大写字母或数字构成,不能使用多个字符。输入在同一个格子里也不能使用中文汉字等特殊符号,否则可能触发来自马业迪的提示。

  2. 移位密钥需要使用大写字母组成的字符串(虽然我的程序也允许添加数字),但数字与字母排序时使用他们的字典序进行排序。值得注意的是,这个移位密钥必须要大于等于2个字符,否则没有移位的意义。此外,移位密钥中的字符,不能重复,否则也会失去移位的意义。不按要求设置移位密钥可能触发提示,见四、程序测试部分。

  3. 在点击开始执行前,需要选择一种模式加密模式解密模式之间是单选的关系。选择其中的一个,会自动取消另一种模式。

  4. 对于加密操作,如果使用棋盘加密后的中间密文不足以填满移位密钥的表格中的最后一个横行时,会使用下划线_补位。以某次加密过程为例,如图所示。

    image-20221011235736561

    这里补位的_会填写在最后的加密结果中,以保持加密结果显示时的美观,中间有空格隔开。

    说明:以这个加密结果为例,AAVFF ADDDA DAAFV GFGD_ GAAFA AAVFF ADDDA DAAFV GFGD GAAFA AAVFFADDDADAAFVGFGDGAAFA 都是正确的结果。将这些加密结果分别使用我的程序解密会得到相同的原文attackatonce

    对于解密操作输入的密文需要为上述三种写法中的任意一种即可,本程序都能识别,但不可以把_处替换为其他的随意字符。

二、开发环境

本程序使用python+PyQT进行开发,使用PyInstaller进行打包,生成的exe文件可以在64位Windows机器上直接运行。

如果您使用其他操作系统(如Linux、Mac),您可以自行配置python环境,运行main.py

我使用的版本说明如下:

Python 3.9.13

Package                   Version
------------------------- ----------
altgraph                  0.17.3
click                     7.1.2
future                    0.18.2
pefile                    2022.5.30
pip                       21.3.1
pyinstaller               5.5
pyinstaller-hooks-contrib 2022.10
PyQt5                     5.15.4
pyqt5-plugins             5.15.4.2.2
PyQt5-Qt5                 5.15.2
PyQt5-sip                 12.11.0
PyQt5-stubs               5.15.6.0
pyqt5-tools               5.15.4.3.2
python-dotenv             0.21.0
pywin32-ctypes            0.2.0
qt5-applications          5.15.2.2.2
qt5-tools                 5.15.2.1.2
setuptools                60.2.0
wheel                     0.37.1

其中qt5-tools中包含QT designer用以编辑图形化界面,文件保存成adfgvx.ui文件,以便后续修改图形化窗口的设计。

通过执行pyuic5 adfgvx.ui -o adfgvx.py可以将其转换成adfgvx.py,该文件中的Ui_Form类是图形化界面的基础。

main.py继承Ui_Form父类,在main.py中编写了按钮交互以及具体的算法。

PyInstaller打包生成exe的语句:PyInstaller -F -w --clean -i icon.ico -n adfgvx_myd.exe main.py

三、算法描述

3.1准备步骤

在进行加密或解密计算前需要保证输入的合法性。包括棋盘输入是否合法(是否都是一个字符,且没有重复),输入的移位密钥是否合法,等等。

下面是检验棋盘合法性的代码,遍历这些棋盘格子,查看每个格子是否为一个大写字母或数字,如果不满足则会给出提示。此外,棋盘密钥需要是36个字符的某种排列,必须每个字符出现一次,这一点我也做了检查。

# 检验棋盘输入是否合法
cov = "ADFGVX"
progresstxt = ""
flag = 1
for i in range(6):
    for j in range(6):
        if len(L[i][j]) != 1:
            progresstxt += "请检查第" + cov[i] + "行" + "第" + cov[j] + "列的棋盘,每个格子需要有且仅有一个字母或数字\n"
            flag = 0
        elif not (L[i][j].isdigit() or L[i][j].isalpha()):
            progresstxt += "请检查第" + cov[i] + "行" + "第" + cov[j] + "列的棋盘,每个格子必须是数字或字母\n"
            flag = 0
        elif L[i][j]>='a' and L[i][j]<='z':
            progresstxt += "请检查第" + cov[i] + "行" + "第" + cov[j] + "列的棋盘,请大写,谢谢!\n"
            flag=0
for i in range(6): # 判断ij和pq格子里的是否重复
    for j in range(6):
        for p in range(6):
            for q in range(6):
                if i==p and j==q:
                    continue
                else:
                    if L[i][j]==L[p][q]:
                        progresstxt += "第" + cov[i] + "行" + "第" + cov[j] + "列的棋盘与"+"第" + cov[p] + "行" + "第" + cov[q]+"列重复!\n"
                        flag=0

if flag == 0:
    self.plainTextEdit_progress.setPlainText(progresstxt)
    self.plainTextEdit_res.setPlainText("请先根据执行过程中的提示改正您的输入再说!")
    return 0

下面是移位密钥的检查:

值得一提的是,使用中文汉字会骗过python的isalpha()的检查,即结果为True,但中文汉字不属于字母,这一点我的程序也考虑在内了。此外,还要检查移位密钥是否大于等于2个字符,并且是否合乎要求。如下的代码写法可以使用数字作为移位密钥,也是允许的。

# 检查移位密钥
if len(self.lineEdit_cargo.text()) < 2:
    self.plainTextEdit_progress.setPlainText("移位密钥至少2位字符,谢谢!")
    self.plainTextEdit_res.setPlainText("请先根据执行过程中的提示改正您的输入再说!")
    return 0
for i in self.lineEdit_cargo.text():
    if not (i.isalpha() or i.isdigit()):
        self.plainTextEdit_progress.setPlainText("移位密钥只能包含大写字母或数字!")
        self.plainTextEdit_res.setPlainText("请先根据执行过程中的提示改正您的输入再说!")
        return 0
    elif i.isalpha() and i>='a' and i<='z':#小写
        self.plainTextEdit_progress.setPlainText("移位密钥中的字母请大写!")
        self.plainTextEdit_res.setPlainText("请先根据执行过程中的提示改正您的输入再说!")
        return 0
for i in range(len(self.lineEdit_cargo.text())):
    for j in range(len(self.lineEdit_cargo.text())):
        if i!=j and self.lineEdit_cargo.text()[i]==self.lineEdit_cargo.text()[j]:
            self.plainTextEdit_progress.setPlainText("移位密钥中的字符不能重复!")
            self.plainTextEdit_res.setPlainText("请先根据执行过程中的提示改正您的输入再说!")
            return 0
3.2加密算法

加密的过程有两个主要步骤,一个是用棋盘加密,第二步是使用移位密钥加密。

在进行棋盘加密的时候,需要将棋盘中用数字第几行第几列转换成ADFGVX表示的行列,这里使用一个cov列表(或字符串)进行转换。

cov = "ADFGVX"
......
for c in self.plainTextEdit_text.toPlainText().upper():
    if c.isdigit() or (c>="A" and c<="Z"):
        ......
        tmp += [cov[i] + cov[j] for i in range(6) for j in range(6) if L[i][j] == c][0]

其中,self.plainTextEdit_text.toPlainText().upper()为用户输入的明文(转换为大写),c则是依次遍历这个明文字符串中的每一个字符。对于每一个c,首先检查是否为数字或大写字母,满足条件后在6*6的棋盘中找到这个c字符所在的位置[i][j](关键代码为for i in range(6) for j in range(6) if L[i][j] == c][0],这种写法非常简洁,十分巧妙),然后利用covi,j转换成ADFGVX表示的行列号([cov[i] + cov[j]cov[i]cov[j]都是字符串,可以使用加号拼接)。

第二步,使用移位密钥进行加密。把这个棋盘加密后的密钥tmp进行了切分,分成长度为移位密钥长度的片段。当然,最后一段不足移位密钥长度的位数时,我对位数进行了补全,补全成了移位密钥长度的整数倍,不足的地方用_来表示。这样一来就可以按移位密钥长度整齐地分成相同长度的片段了。

newL = [tmp[i:i + lencargo] for i in range(0, len(tmp), lencargo)]
while len(newL[-1]) < lencargo:
    newL[-1] += "_"

接下来是对移位密钥进行排序,这里直接调用了sort()库函数。

s=list(self.lineEdit_cargo.text()[:])
s.sort()
progresstxt+="移位密钥的字典序为:"+"".join(i for i in s)+"\n"

最后根据排好序的移位密钥中的字符,找到对应的原来的这个字符所在的列ind,然后把这一列newL[j][ind]拼接在最终答案上。

for i in s:
    ind=0
    while self.lineEdit_cargo.text()[ind]!=i:
        ind+=1
    for j in range(len(newL)):
        restxt+=newL[j][ind]
    restxt+=" "
3.3解密算法

解密过程与加密过程为逆过程,思路只是顺序上反过来,不再赘述。这里只介绍两点。

第一,如何找到哪个地方有补位。刚刚加密的时候提到,对于补位的地方使用下划线_来标记,但最终允许用户使用没有下划线的结果:例如,之前提到,

AAVFF ADDDA DAAFV GFGD_ GAAFA

AAVFF ADDDA DAAFV GFGD GAAFA

AAVFFADDDADAAFVGFGDGAAFA 都是正确的结果,只是写法有差异,所以我的程序就需要自己找到哪里原本有补位的空格。算法思路是:补位的列一定是移位密钥中最后的几个字符表示的那一列。至于是几个字符的,可以通过取模运算得知。

progresstxt+="您输入的密文为:"+tmp+"\n\n"          #tmp表示的是用户输入的密文
tmplen=len(tmp)
lencargo = len(self.lineEdit_cargo.text())
daibulen=lencargo-len(tmp)%lencargo             #需要补充的个数
daibuL=self.lineEdit_cargo.text()[-daibulen:]   #移位密钥中后面daibulen个字符是进行了补位的
if daibulen==0:
    daibuL=[]   #完全填满,不用补位的情况

第二,使用了字典进行移位变换。

dic={}
progresstxt+="您输入的移位密钥是"+self.lineEdit_cargo.text()+"\n"

s = list(self.lineEdit_cargo.text()[:])
s.sort()
progresstxt += "您输入的移位密钥排序为" + "".join(i for i in s) + "\n\n"
# print(s)
cutlen=(len(tmp)+daibulen)//lencargo
for i in s:
    if i in daibuL:
        dic[i]=tmp[:cutlen-1]
        tmp=tmp[cutlen-1:]
    else:
        dic[i]=tmp[:cutlen]
        tmp=tmp[cutlen:]
progresstxt+=str(dic)+"\n\n"

这里的dic保存了移位密钥的每一个字符与对应的那部分密文之间的关系。例如某次解密过程中的dic

{'A': 'AAVFF', 'C': 'ADDDA', 'G': 'DAAFV', 'O': 'GFGD', 'R': 'GAAFA'}

然后利用移位密钥CARGO的顺序就可以很容易读出原文,而不用再遍历二维数组了。

四、程序测试

对于用户可能出现的各种问题,我的程序都做出了提示,不会突然崩掉。

程序的正确性您可以自行验证,毋庸置疑。下面测试一些出错的处理方式。

4.1棋盘填写不正确

用户可能在棋盘中重复某个字符,这是不允许的,否则一个原文会有多种密文,并且有重复就会有缺失,缺失的字符没法加密了就。这种情况会报错(棋盘中有两个K):

image-20221012093625953

或者用户更搞笑,他在一个格子里填多个字符,也会给出提示。

image-20221012093736990

更有甚者,他使用一键清除后没有自己填写棋盘密钥,那就会每个格子都是空的:

image-20221012093909372

4.2移位密钥填写错误

移位密钥必须是A-Z或0-9组成的字符串,并且不能重复,给出反例:

image-20221012094055063

当然,移位密钥中出现符号,比如空格也是不行的:

image-20221012094133689

另外,如果用户的移位密钥只有一位长度,那么他移位还移个寂寞:

image-20221012094235713

4.3明文/密文输入不合法

最简单的,用户他啥都不写:

image-20221012094333529

出现不可加密的符号(不在这36个字符中的):

image-20221012094428700

输入一个错误的密文(根本没有原文与之对应):

image-20221012094603537

以上测试也许没有详尽,但当遇到未知错误时,也会如上图给出报错,您可改正您的输入再次点击开始执行,而不会导致程序崩溃。

五、提交文件目录

ADFGVX加解密机说明文档_马业迪_10195102507.pdf—-本文档

ADFGCVX加解密机程序及代码_马业迪_10195102507.zip—-程序及代码压缩包,包含:

  • adfgvx_myd.exe—-可执行程序,可在Windows平台独立运行
  • main.py—-代码入口文件,包含加解密算法
  • adfgvx.ui—-由QT designer保存的图形化界面设计稿
  • adfgvx.py—-由adfgvx.ui转换而来的图形化界面父类
  • icon.icoicon.jpg—-图标图片 注:该图标为本人常用社交头像,非网图