import struct from tkinter import * import time from tkinter import filedialog import pickle import socket import threading import tkinter.messagebox from algorithms import AESalgorithm, generateKey, RSAalgorithm, hashalg from tkinter import ttk import json import os import base64 import sys # 使用tkinter建立GUI IP = '127.0.0.1' PORT = 4396 BUFF = 5120 FIP='127.0.0.1' FPORT=7932 # SERVERPUBLIC # CLIENTPUBLIC # CLIENTPRIVATE def initKey(): # 生成客户端非对称密钥对 global CLIENTPUBLIC, CLIENTPRIVATE (CLIENTPRIVATE, CLIENTPUBLIC) = generateKey.generateMyKey("keys/client/client") # 用字典生成 def fileEncrypt(data): # 对文件进行加密 onceKey = AESalgorithm.genKey() print("发送的密钥",onceKey) digest = RSAalgorithm.RsaSignal(data, CLIENTPRIVATE) message = {'Message': base64.b64encode(data), 'digest': digest.decode("utf-8")} # 把消息和摘要打包 message = pickle.dumps(message) # 转成json字符串 message = AESalgorithm.AesEncrypt(message, onceKey) encrykey = RSAalgorithm.RsaEncrypt(onceKey, SERVERPUBLIC) MES = pickle.dumps([message, encrykey.decode('utf-8')]) return MES def initSendSocket(filepath): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 始化一个TCP类型的socket s.connect((FIP, FPORT)) # 三次握手 except socket.error as msg: print(msg) sys.exit(1) print(s.recv(10240)) # s.recv(bufsize[,flag])接收套接字的数据,数据以字符串形式返回,bufsize指定最多可以接收的数量,flag提供有关消息的其他信息,可以忽略。 # 需要传输的文件路径 # 判断是否为文件 if os.path.isfile(filepath): # 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小 fileinfo_size = struct.calcsize('128sl') # 定义文件头信息,包含文件名和文件大小 fhead = struct.pack('128sl', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size) # 发送文件名称与文件大小 s.send(fhead) # 将传输文件以二进制的形式分多次上传至服务器 fp = open(filepath, 'rb') while True: global rere rere='' data = fp.read(1024) if not data: print('{0} 文件发送完毕...'.format(os.path.basename(filepath))) txtMsgList.insert(END, '{0} 文件发送完毕...'.format(os.path.basename(filepath)), 'greencolor') break print("发送的内容",data) tosend=fileEncrypt(data) s.send(str(len(tosend)).encode('utf-8')) s.send(tosend) while True: if s.recv(1024).decode('utf-8')=='I have receive the past one': break # 关闭当期的套接字对象 s.close() # 主页 def mainPage(): def sendMsg(Sock): # 发送消息 strMsg = "我:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '\n' txtMsgList.insert(END, strMsg, 'greencolor') Mes = txtMsg.get('0.0', END) txtMsgList.insert(END, Mes) onceKey = AESalgorithm.genKey() # 一次一密 密钥 digest = RSAalgorithm.RsaSignal(Mes, CLIENTPRIVATE) # 先hash再签名# 生成消息摘要 message = {'Message': Mes, 'digest': digest.decode("utf-8")} # 把消息和摘要打包 message = json.dumps(message) # 转成json字符串 message = AESalgorithm.AesEncrypt(message, onceKey) # 合并加密 encrykey = RSAalgorithm.RsaEncrypt(onceKey, SERVERPUBLIC) # 用服务器公钥加密一次密钥 txtMsg.delete('0.0', END) Message = pickle.dumps([message, encrykey.decode('utf-8')]) # 序列化消息,用于传输 Sock.send(Message) def RecvMsg(Sock, test): # 接受消息函数 global SERVERPUBLICs while True: Message = Sock.recv(BUFF) # 收到文件 (message, encrykey) = pickle.loads(Message) mykey = RSAalgorithm.RsaDecrypt(encrykey, CLIENTPRIVATE) # 用私钥解密获得一次密钥 print('mykey', mykey.decode('utf-8')) decryMes = AESalgorithm.AesDecrypt(message, mykey.decode('utf-8')) # 用一次密钥解密消息,获得包含消息内容和摘要的json decryMes = json.loads(decryMes) # 将json转换为python字典 content = decryMes['Message'] digest = decryMes['digest'].encode('utf-8') if RSAalgorithm.VerRsaSignal(content, digest, SERVERPUBLIC): strMsg = "对方:" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "通过数字签名认证,本次密钥为" + mykey.decode('utf-8') + '\n' txtMsgList.insert(END, strMsg, 'greencolor') txtMsgList.insert(END, content + '\n') def cancelMsg(): # 清空消息内容 txtMsg.delete('0.0', END) def sendMsgEvent(event, Sock): # 发送消息事件 if event.keysym == 'Up': sendMsg(Sock) def UploadAction(event=None): # 上传文件 filename = filedialog.askopenfilename() # 文件打开框,返回文件路径 print('Selected:', filename) initSendSocket(filename) # 接受server方消息并打印 def exchangePublicKey(dir): global ClientSock, txtMsgList with open(dir, 'rb') as fi: publicKey = fi.read() has = hashalg.hash_sha256(publicKey) Message = pickle.dumps([publicKey, has]) try: ClientSock.send(Message) txtMsgList.insert(END, "发送公钥成功\n") except: txtMsgList.insert(END, "密钥发送失败,正在尝试重新发送...\n") exchangePublicKey(dir) def verifyKey(Sock): global txtMsgList, SERVERPUBLIC while True: Message = Sock.recv(BUFF) (publickey, hash_256) = pickle.loads(Message) if hash_256 == hashalg.hash_sha256(publickey): txtMsgList.insert(END, "公钥完整性验证完成,可以开始传输文件\n") SERVERPUBLIC = publickey txtMsgList.insert(END, "收到公钥\n" + SERVERPUBLIC.decode('utf-8') + "\n") break else: txtMsgList.insert(END, "验证失败\n") def cnct(): # 连接操作 global txtMsgList, ClientSock ClientSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ServerAddr = IP ClientSock.connect((ServerAddr, PORT)) print('连接成功,可以开始传输消息和文件了\n') txtMsgList.insert(END, "连接成功,可以开始传输消息和文件了" + IP + ":" + str(PORT) + "\n") exchangePublicKey("keys/client/clientpublic.pem") # 发送公钥 verifyKey(ClientSock) # 验证对方密钥 thread = threading.Thread(target=RecvMsg, args=(ClientSock, None)) thread.start() return ClientSock def setIpWindows(): # 设置ip的子窗口 def setNewIP(newip, newport): global txtMsgList print(newip, newport) global IP IP = str(newip) global PORT PORT = int(newport) set.destroy() try: cnct() except: txtMsgList.insert(END, "连接异常,ip或端口不可访问") tkinter.messagebox.showwarning('连接失败', '连接异常,ip或端口不可访问\n') print("连接异常,ip或端口不可访问\n") set = Tk() set.title('设置ip地址和端口号') set.geometry('350x200') set.resizable(0, 0) # ip Label(set, text='IP地址:').place(x=10, y=10) ent1 = Entry(set) ent1.place(x=150, y=10) # port Label(set, text='端口号:').place(x=10, y=50) ent2 = Entry(set) ent2.place(x=150, y=50) bt_connect = Button(set, text='连接', command=lambda: setNewIP(ent1.get(), ent2.get())) bt_connect.place(x=150, y=130) set.mainloop() def start(): # 以下是生成聊天窗口的代码 def selectEven(*args): print(selal.get()) global app, frmLT, frmLC, frmLB, txtMsgList, txtMsg, btnSend, btnCancel, btnFile, btnSet # 创建窗口 app = Tk() app.title('网络加密软件-Client') app.resizable(0, 0) # 创建frame容器 frmLT = Frame(width=500, height=320, bg='white') frmLC = Frame(width=500, height=150, bg='white') frmLB = Frame(width=500, height=30) # frmRT = Frame(width = 200, height = 500) # 创建控件 txtMsgList = Text(frmLT) txtMsgList.tag_config('greencolor', foreground='#008C00') # 创建tag txtMsg = Text(frmLC) txtMsg.bind("", sendMsgEvent) selal = StringVar() btnSend = Button(frmLB, text='发送', width=8, command=lambda: sendMsg(ClientSocket)) btnCancel = Button(frmLB, text='取消', width=8, command=cancelMsg) btnFile = Button(frmLB, text='上传文件', width=8, command=UploadAction) btnSet = Button(frmLB, text='设置ip', width=8, command=setIpWindows) btnAlSel = ttk.Combobox(frmLB, textvariable=selal, state='readonly') btnAlSel['values'] = ('AES-CBC-一次一密', '待定2') btnAlSel.current(0) btnAlSel.bind("<>", selectEven) print("selal is ", selal.get()) # btnFile.pack() # imgInfo = PhotoImage(file = "timg-2.gif") # lblImage = Label(frmRT, image = imgInfo) # lblImage.image = imgInfo # 窗口布局 frmLT.grid(row=0, column=0, columnspan=2, padx=1, pady=3) frmLC.grid(row=1, column=0, columnspan=2, padx=1, pady=3) frmLB.grid(row=2, column=0, columnspan=2) # frmRT.grid(row = 0, column = 2, rowspan = 3, padx =2, pady = 3) # 固定大小 frmLT.grid_propagate(0) frmLC.grid_propagate(0) frmLB.grid_propagate(0) # frmRT.grid_propagate(0) btnSend.grid(row=2, column=0) btnCancel.grid(row=2, column=1) btnFile.grid(row=2, column=2) btnSet.grid(row=2, column=3) btnAlSel.grid(row=2, column=4) # lblImage.grid() txtMsgList.grid() txtMsg.grid() # 主事件循环 app.mainloop() thread_gui = threading.Thread(target=start) thread_gui.start() ClientSocket = cnct() # try: # ClientSocket=cnct() # except: # addSysTip("连接异常,ip或端口不可访问") # tkinter.messagebox.showwarning('连接失败', '连接异常,ip或端口不可访问,点击设置按钮重新设置\n') # print("连接异常,ip或端口不可访问\n") def main(): initKey() mainPage() if __name__ == "__main__": main()