本项目是《网络安全A》课程的一项附加作业

概述

本项目主要分为三个类,也分别代表了本项目的三个主要部分。首先是DynamicVerificationCode类,主要实现生成动态验证码每一帧的PNG图像文件,是本项目的核心;然后是LabelDisplay类,负责将gif文件动态显示在界面中;最后是GUI类,负责将PNG序列合称为gif文件,并基于tkinter构建图形界面。

P193\#y1

代码分析

下面来分析一下DynamicVerificationCode类的代码。要实现动态验证码,主要实现以下几个功能:生成随机颜色,生成随机字符(包含数字和大小写字母)。生成干扰线段,生成干扰点以及将每一帧合称为gif文件。以上几个功能分别对应以下几个函数:Colorconfusion(), StrInDVC(), LineConfusion(), ArcConfusion(), Create()

首先是Colorconfusion()函数,因为使用RGB色彩模式,因此只需要生成三个0-255之间的随机数即可,这个函数会被调用生成随机背景颜色和生成随机字体颜色。StrInDVC()函数也很简单,只需要生成数字和大小写字母各自范围内的ASCII码值即可,如小写字母则使用chr(random.randint(97, 122))生成。然后是LineConfusion(),由于gif的长和宽是确定的,因此生成线段只需生成两个随机坐标即可,分别作为起点和终点,然后调用PIL Image.line()函数即可生成线段。对于ArcConfusion()函数则需要调用PIL Image.point()函数,同样是生成随机数不再赘述。

最后在Create()函数内,首先生成随机字符,然后设置一个图像序列picSequence,设置字体为COOPBL.TTF(注:若环境中不包含本字体将报错),调用PIL Image.text()函数在每帧图像上写入字符,注意字符的位置也需要随机生成,然后分别调用self.LineConfusion(createpic)self.ArcConfusion(createpic)生成随机线和点,最后返回图像序列即可。

效果展示

主界面展示,包含输入框、提交按钮和刷新按钮。

maingui

当提交内容为空时,将提示空值警告信息。

P201\#y1

当提交了错误的验证码时,将报错。

P203\#y1

当验证码正确时,提示验证通过。

P206\#y1

点击刷新按钮后,动态验证码更换,当然验证信息也随之更换。

P207\#y1

完整代码

注意字体被设置为COOPBL.TTF,若系统中不包含本字体将报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# -*- coding: utf-8 -*-
# @Time : 2021/12/08
# @Author : zxy
# @Version : 1.0
# @Software: PyCharm
import os
import random
from io import BytesIO
import tkinter.messagebox
from tkinter import *
import imageio
from PIL import ImageDraw
from PIL import ImageFont
from PIL import Image, ImageTk


class LabelDisplay(Label):
def __init__(self, master, filename):
image = Image.open(filename)
sequence = []
try:
while 1:
sequence.append(image.copy())
image.seek(len(sequence))
except EOFError:
pass

try:
self.delay = image.info['duration']
except KeyError:
self.delay = 100

begin = sequence[0].convert('RGBA')
self.frames = [ImageTk.PhotoImage(begin)]

Label.__init__(self, master, image=self.frames[0])

frameTemp = sequence[0]
for image in sequence[1:]:
frameTemp.paste(image)
frame = frameTemp.convert('RGBA')
self.frames.append(ImageTk.PhotoImage(frame))

self.frameID = 0

self.cancel = self.after(self.delay, self.play)

def play(self):
self.config(image=self.frames[self.frameID])
self.frameID += 1
if self.frameID == len(self.frames):
self.frameID = 0
self.cancel = self.after(self.delay, self.play)


class DynamicVerificationCode(object):
def __init__(self, breadth=200, vertical=55, strcount=5, fontsize=40, arccount=20, linecount=3,
number_of_frames=30):
# 推荐的几种方案:
# (self, width=200, height=55, code_count=5, font_size=40, point_count=20, line_count=3, frame_count=30)
# (self, width=600, height=200, code_count=6, font_size=90, point_count=120, line_count=30, frame_count=90)
# (self, width=1200, height=400, code_count=8, font_size=150, point_count=1200, line_count=55, frame_count=90)
self.breadth = breadth
self.vertical = vertical
self.strcount = strcount
self.fontsize = fontsize
self.arccount = arccount
self.linecount = linecount
self.number_of_frames = number_of_frames

def Colorconfusion(self):
colorr = random.randint(0, 255)
colorg = random.randint(0, 255)
colorb = random.randint(0, 255)
return colorr, colorg, colorb

def StrInDVC(self):
number = str(random.randint(0, 9))
xiaoxieap = chr(random.randint(97, 122))
daxieap = chr(random.randint(65, 90))
reStr = random.choice([number, xiaoxieap, daxieap])
return reStr

def LineConfusion(self, createpic=None):
for i in range(self.linecount):
sourcex = random.randint(0, self.breadth)
destx = random.randint(0, self.breadth)
sourcey = random.randint(0, self.vertical)
desty = random.randint(0, self.vertical)
createpic.line((sourcex, sourcey, destx, desty), fill=self.Colorconfusion())

def ArcConfusion(self, createpic=None):
for i in range(self.arccount):
createpic.point([random.randint(0, self.breadth), random.randint(0, self.vertical)],
fill=self.Colorconfusion())
x = random.randint(0, self.breadth)
y = random.randint(0, self.vertical)
createpic.arc((x, y, x + 4, y + 4), 0, 90, fill=self.Colorconfusion())

def Create(self):
StrInDVCList = []
for i in range(self.strcount):
s = self.StrInDVC()
StrInDVCList.append(s)

background = self.Colorconfusion()

picSequence = []
for i in range(self.number_of_frames):
DVC = Image.new('RGB', (self.breadth, self.vertical), background)
createpic = ImageDraw.Draw(DVC)

fontInDVC = os.path.join(os.getcwd(), "COOPBL.TTF")
font = ImageFont.truetype(fontInDVC, size=self.fontsize)

for i, code in enumerate(StrInDVCList):
y = random.randint(int(-0.133 * self.vertical), int(0.267 * self.vertical))
x = random.randint(14, 22)
createpic.text((x + i * (self.breadth / self.strcount), y), code, self.Colorconfusion(), font=font)

self.LineConfusion(createpic)

self.ArcConfusion(createpic)

PNGfile = BytesIO()
DVC.save(PNGfile, "png")
data = PNGfile.getvalue()
PNGfile.close()

data = imageio.imread(data, format="png")
picSequence.append(data)

return picSequence, "".join(StrInDVCList)


class GUI:
def __init__(self):
self.ans = self.DVCCreate()
self.root = Tk()
self.root.geometry('500x400+500+300')
self.root.resizable(False, False)
self.root.title("Dynamic Verification Code")
self.anim = LabelDisplay(self.root, 'Dynamic_verification_code.gif')
self.anim.pack(side="top")
self.entryCode = tkinter.Entry(self.root)
self.entryCode.place(x=150, y=200, width=200, height=30)
self.w = Label(self.root, text="Please enter the verification code below:")
self.w.place(x=121, y=168)
self.ww = Label(self.root, text="Cybersecurity Coursework Build Date: December 8, 2021", font=('Consolas', 8),
fg='DarkRed')
self.ww.place(x=135, y=382)
self.www = Label(self.root,
text="You can refer to the comments to adjust the parameters of the DynamicVerificationCode("
") function to adjust the style of the dynamic verification code.",
wraplength=244,
font=('Consolas', 10))
self.www.place(x=135, y=255)

self.btnCheck = tkinter.Button(self.root, text='Submit', width=20, height=2, command=self.check)
self.btnCheck.pack(side='bottom', pady=20)

self.btnRe = tkinter.Button(self.root, text='Refresh', width=20, height=2, command=self.Re)
self.btnRe.place(x=158, y=75)
self.root.mainloop()
self.Re()

def DVCCreate(self):
img = DynamicVerificationCode(200, 55, 5, 40, 20, 3, 30)
# 宽度,高度,位数,字体大小,干扰点数量,干扰线段数量,帧数
seq, ans = img.Create()
imageio.mimsave("Dynamic_verification_code.gif", seq, 'GIF', duration=0.3)
print("The verification code characters are " + ans)
return ans

def check(self):
# print(self.ans)
if self.entryCode.get() == "":
tkinter.messagebox.showerror('DVC Fatal Error', 'The verification code is empty!')
else:
if self.entryCode.get() == self.ans:
tkinter.messagebox.showwarning('DVC Success', 'Successfully verified!')
else:
tkinter.messagebox.showerror('DVC Fail', 'Verification code error! Please try again.')

def Re(self):
print("Re")
self.ans = self.DVCCreate()
self.anim.pack_forget()
self.anim = LabelDisplay(self.root, 'Dynamic_verification_code.gif')
self.anim.pack(side="top")
self.root.update()
# return ans


if __name__ == "__main__":
GUI()