From ba2dd4c682771067db6712c13bc07cefd7dd55c2 Mon Sep 17 00:00:00 2001 From: Mming-145460 <175156969+Mming-145460@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:43:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 3 + .idea/.name | 1 + .idea/dbnavigator.xml | 400 ++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/实验.iml | 8 + GUI.py | 47 ++ NEV.py | 195 +++++++++ NEW.py | 269 ++++++++++++ __pycache__/NEV.cpython-311.pyc | Bin 0 -> 12011 bytes __pycache__/NEW.cpython-311.pyc | Bin 0 -> 12926 bytes __pycache__/receiver.cpython-311.pyc | Bin 0 -> 11882 bytes __pycache__/sender.cpython-311.pyc | Bin 0 -> 12093 bytes main.py | 77 ++++ receiver_private_key.pem | 28 ++ receiver_public_key.pem | 9 + sender_private_key.pem | 28 ++ sender_public_key.pem | 9 + yicixing.py | 25 ++ 20 files changed, 1117 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/dbnavigator.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/实验.iml create mode 100644 GUI.py create mode 100644 NEV.py create mode 100644 NEW.py create mode 100644 __pycache__/NEV.cpython-311.pyc create mode 100644 __pycache__/NEW.cpython-311.pyc create mode 100644 __pycache__/receiver.cpython-311.pyc create mode 100644 __pycache__/sender.cpython-311.pyc create mode 100644 main.py create mode 100644 receiver_private_key.pem create mode 100644 receiver_public_key.pem create mode 100644 sender_private_key.pem create mode 100644 sender_public_key.pem create mode 100644 yicixing.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..5ffb332 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +GUI.py \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 0000000..c7a8a85 --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a971a2c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5a6690a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/实验.iml b/.idea/实验.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/实验.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/GUI.py b/GUI.py new file mode 100644 index 0000000..798613d --- /dev/null +++ b/GUI.py @@ -0,0 +1,47 @@ +import tkinter as tk +import subprocess +import threading + +# 使用线程来运行 NEW.py和 NEV.py。 +def run_new(): + try: + threading.Thread(target=lambda: subprocess.run(["python", "NEW.py"])).start() + except FileNotFoundError: + print("找不到NEW.py文件") + + +def run_nev(): + try: + threading.Thread(target=lambda: subprocess.run(["python", "NEV.py"])).start() + except FileNotFoundError: + print("找不到NEV.py文件") + + +# 创建主窗口 +root = tk.Tk() +root.title("文件安全传输工具") +# 设置窗口大小 +root.geometry("300x150") + +# 创建标签 +label = tk.Label(root, text="请选择您的身份:") +label.pack(pady=10) + +# 创建按钮框架,用于在同一行放置按钮 +button_frame = tk.Frame(root) +button_frame.pack() + +# 创建发送方按钮 +sender_button = tk.Button(button_frame, text="发送方", command=run_new) +sender_button.pack(side=tk.LEFT, padx=10) + +# 创建接收方按钮 +receiver_button = tk.Button(button_frame, text="接收方", command=run_nev) +receiver_button.pack(side=tk.LEFT, padx=10) + +# 创建关闭按钮 +confirm_button = tk.Button(button_frame, text="关闭", command=root.destroy) +confirm_button.pack(side=tk.LEFT, padx=10) + +# 运行主循环 +root.mainloop() \ No newline at end of file diff --git a/NEV.py b/NEV.py new file mode 100644 index 0000000..a3876c4 --- /dev/null +++ b/NEV.py @@ -0,0 +1,195 @@ +import os +import pickle +import socket +import tkinter as tk +from tkinter import filedialog, ttk, messagebox +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +import hashlib + + +# 去除填充函数 +def unpad(data): + padding_length = data[-1] + if padding_length > len(data): + return data + return data[:-padding_length] + + +# 解密文件函数 +def decrypt_file(encrypted_data, key, iv, algorithm, mode): + mode_mapping = { + "CBC": modes.CBC, + "CFB": modes.CFB + } + if algorithm == "AES": + # 对于AES算法,IV长度为16字节,无需处理 + cipher = Cipher(algorithms.AES(key), mode_mapping[mode](iv)) + elif algorithm == "DES": + # 确保IV长度为8字节 + iv = iv[:8] + cipher = Cipher(algorithms.TripleDES(key), mode_mapping[mode](iv)) + else: + raise ValueError("不支持该算法") + + decryptor = cipher.decryptor() + decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() + return decrypted_data + + +# 计算哈希值函数 +def calculate_hash(data): + digest = hashes.Hash(hashes.SHA256()) + digest.update(data) + return digest.finalize() + + +# 验证签名函数 +def verify_signature(public_key, signature, hash_value): + try: + public_key.verify( + signature, + hash_value, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + return True + except Exception: + return False + + +# 解密对称密钥函数 +def decrypt_symmetric_key(private_key, encrypted_key): + decrypted_key = private_key.decrypt( + encrypted_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + return decrypted_key + + +# 计算文件的 MD5 校验和 +def calculate_md5(data): + md5 = hashlib.md5() + md5.update(data) + return md5.hexdigest() + + +# 接收文件函数 +def receive_file(server_ip, server_port, output_dir, algorithm, mode): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((server_ip, server_port)) + s.listen(1) + print(f"等待连接,监听端口 {server_port}...") + conn, addr = s.accept() + with conn: + print(f"接收到连接来自: {addr}") + data = b"" + while True: + packet = conn.recv(4096) + if not packet: + break + data += packet + + package = pickle.loads(data) + + encrypted_file = package['encrypted_file'] + iv = package['iv'] + encrypted_symmetric_key = package['encrypted_symmetric_key'] + signature = package['signature'] + received_algorithm = package['algorithm'] + received_mode = package['mode'] + received_md5_checksum = package['md5_checksum'] + file_extension = package['file_extension'] + # 从封装包中提取发送方公钥 + sender_public_pem = package['sender_public_key'] + sender_public_key = serialization.load_pem_public_key(sender_public_pem) + + if algorithm!= received_algorithm or mode!= received_mode: + raise ValueError("选择的解密算法或模式与发送方不一致") + + with open("receiver_private_key.pem", "rb") as f: + receiver_private_key = serialization.load_pem_private_key( + f.read(), + password=None + ) + + symmetric_key = decrypt_symmetric_key(receiver_private_key, encrypted_symmetric_key) + + decrypted_file = decrypt_file(encrypted_file, symmetric_key, iv, algorithm, mode) + decrypted_file = unpad(decrypted_file) + + file_hash = calculate_hash(encrypted_file) + + is_valid = verify_signature(sender_public_key, signature, file_hash) + return is_valid, output_dir, file_extension, decrypted_file + + +# 主函数,用于启动 GUI 界面 +def receiver_main(): + def select_output_dir(): + output_dir.set(filedialog.askdirectory()) + + def start_receiving(): + if not output_dir.get(): + print("请选择保存文件的路径!") + return + algorithm = algorithm_var.get() + mode = mode_var.get() + try: + is_valid, output_dir_path, file_extension, decrypted_file = receive_file( + server_ip.get(), int(server_port.get()), output_dir.get(), algorithm, mode + ) + result_label.config(text=f"接收方验证签名结果: {is_valid}") + if is_valid: + output_file_path = os.path.join(output_dir_path, f"decrypted_file.{file_extension}") + with open(output_file_path, "wb") as f: + f.write(decrypted_file) + messagebox.showinfo("提示", "接收成功") + except Exception as e: + print(f"接收过程中发生错误: {e}") + + root = tk.Tk() + root.title("接收方") + root.geometry("450x300") # 适当增加窗口高度以显示新标签 + + server_ip = tk.StringVar(value="127.0.0.1") + server_port = tk.StringVar(value="12345") + output_dir = tk.StringVar() + algorithm_var = tk.StringVar(value="AES") + mode_var = tk.StringVar(value="CBC") + + ttk.Label(root, text="监听 IP:").grid(row=0, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=server_ip).grid(row=0, column=1, pady=5) + + ttk.Label(root, text="监听端口:").grid(row=1, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=server_port).grid(row=1, column=1, pady=5) + + ttk.Label(root, text="选择保存文件的路径:").grid(row=2, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=output_dir, width=30).grid(row=2, column=1, pady=5) + ttk.Button(root, text="选择目录", command=select_output_dir).grid(row=2, column=2, pady=5) + + ttk.Label(root, text="选择解密算法:").grid(row=3, column=0, sticky=tk.W, pady=5) + ttk.Combobox(root, textvariable=algorithm_var, values=["AES", "DES"], state="readonly").grid(row=3, column=1, pady=5) + + ttk.Label(root, text="选择解密模式:").grid(row=4, column=0, sticky=tk.W, pady=5) + ttk.Combobox(root, textvariable=mode_var, values=["CBC", "CFB"], state="readonly").grid(row=4, column=1, pady=5) + + ttk.Button(root, text="开始接收文件", command=start_receiving).grid(row=5, columnspan=3, pady=20) + + # 新增显示验证结果的标签 + result_label = ttk.Label(root, text="") + result_label.grid(row=6, columnspan=3, pady=10) + + root.mainloop() + + +if __name__ == "__main__": + receiver_main() \ No newline at end of file diff --git a/NEW.py b/NEW.py new file mode 100644 index 0000000..e5e5a4b --- /dev/null +++ b/NEW.py @@ -0,0 +1,269 @@ +import os +import pickle +import socket +import tkinter as tk +from tkinter import filedialog, ttk, messagebox +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +import hashlib + + +# 生成并保存发送方密钥对 +def generate_and_save_sender_keys(): + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + public_key = private_key.public_key() + + with open('sender_private_key.pem', 'wb') as f: + f.write(private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + )) + + with open('sender_public_key.pem', 'wb') as f: + f.write(public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + )) + + return private_key, public_key + + +# 加载发送方密钥对 +def load_sender_keys(): + try: + with open('sender_private_key.pem', 'rb') as f: + private_key = serialization.load_pem_private_key( + f.read(), + password=None + ) + with open('sender_public_key.pem', 'rb') as f: + public_key = serialization.load_pem_public_key(f.read()) + return private_key, public_key + except FileNotFoundError: + return generate_and_save_sender_keys() + + +sender_private_key, sender_public_key = load_sender_keys() + +# 用于存储接收方公钥的变量 +receiver_public_key = None + + +# 填充数据函数 +def pad(data, block_size): + padding_length = block_size - (len(data) % block_size) + if padding_length == 0: + padding_length = block_size + padding = bytes([padding_length]) * padding_length + return data + padding + + +# 加密文件函数 +def encrypt_file(file_path, key, algorithm, mode): + with open(file_path, 'rb') as f: + file_data = f.read() + if algorithm == "AES": + block_size = 16 + elif algorithm == "DES": + block_size = 8 + else: + raise ValueError("Unsupported algorithm") + file_data = pad(file_data, block_size) + + mode_mapping = { + "CBC": modes.CBC, + "CFB": modes.CFB + } + if algorithm == "AES": + iv = os.urandom(16) + elif algorithm == "DES": + iv = os.urandom(8) # 生成8字节的IV用于DES算法 + else: + raise ValueError("Unsupported algorithm") + + if algorithm == "AES": + cipher = Cipher(algorithms.AES(key), mode_mapping[mode](iv)) + elif algorithm == "DES": + cipher = Cipher(algorithms.TripleDES(key), mode_mapping[mode](iv)) + else: + raise ValueError("Unsupported algorithm") + + encryptor = cipher.encryptor() + encrypted_data = encryptor.update(file_data) + encryptor.finalize() + return encrypted_data, iv + + +# 计算哈希值函数 +def calculate_hash(data): + digest = hashes.Hash(hashes.SHA256()) + digest.update(data) + return digest.finalize() + + +# 对哈希值进行签名函数 +def sign_hash(private_key, hash_value): + signature = private_key.sign( + hash_value, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + return signature + + +# 加密对称密钥函数 +def encrypt_symmetric_key(public_key, symmetric_key): + encrypted_key = public_key.encrypt( + symmetric_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) + ) + return encrypted_key + + +# 计算文件的 MD5 校验和 +def calculate_md5(data): + md5 = hashlib.md5() + md5.update(data) + return md5.hexdigest() + + +# 发送文件函数 +def send_file(server_ip, server_port, package): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((server_ip, server_port)) + s.sendall(pickle.dumps(package)) + return True + except Exception as e: + print(f"连接失败: {e}") + return False + + +# 导入接收方公钥的函数 +def import_receiver_public_key(): + global receiver_public_key + file_path = filedialog.askopenfilename(filetypes=[("PEM files", "*.pem")]) + if file_path: + try: + with open(file_path, 'rb') as f: + receiver_public_key = serialization.load_pem_public_key(f.read()) + messagebox.showinfo("提示", "接收方公钥导入成功") + except Exception as e: + messagebox.showerror("错误", f"导入接收方公钥失败: {e}") + + +# 主函数,用于启动 GUI 界面 +def sender_main(): + def select_file(): + file_path.set(filedialog.askopenfilename()) + + def send(): + global receiver_public_key + file = file_path.get() + algorithm = algorithm_var.get() + mode = mode_var.get() + + if not file: + messagebox.showerror("错误", "请选择文件") + return + + if algorithm == "AES": + key_size = 32 + elif algorithm == "DES": + key_size = 24 + else: + print("不支持的算法!") + return + + if mode == "CBC": + pass + elif mode == "CFB": + pass + else: + print("不支持的模式!") + return + + if not receiver_public_key: + messagebox.showerror("错误", "请导入证书") + return + + symmetric_key = os.urandom(key_size) + encrypted_file, iv = encrypt_file(file, symmetric_key, algorithm, mode) + + file_hash = calculate_hash(encrypted_file) + signature = sign_hash(sender_private_key, file_hash) + + sender_public_pem = sender_public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + encrypted_symmetric_key = encrypt_symmetric_key(receiver_public_key, symmetric_key) + + md5_checksum = calculate_md5(encrypted_file) + + file_extension = os.path.splitext(file)[1][1:] + # 在封装包中添加发送方公钥 + package = { + 'encrypted_file': encrypted_file, + 'iv': iv, + 'encrypted_symmetric_key': encrypted_symmetric_key, + 'signature': signature, + 'algorithm': algorithm, + 'mode': mode, + 'md5_checksum': md5_checksum, + 'file_extension': file_extension, + 'sender_public_key': sender_public_pem + } + + if send_file(server_ip.get(), int(server_port.get()), package): + messagebox.showinfo("提示", "发送成功") + else: + messagebox.showerror("错误", "连接失败") + + + root = tk.Tk() + root.title("发送方") + root.geometry("490x250") + + file_path = tk.StringVar() + algorithm_var = tk.StringVar(value="AES") + mode_var = tk.StringVar(value="CBC") + server_ip = tk.StringVar(value="127.0.0.1") + server_port = tk.StringVar(value="12345") + + ttk.Label(root, text="选择文件:").grid(row=0, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=file_path, width=30).grid(row=0, column=1, pady=5) + ttk.Button(root, text="浏览", command=select_file).grid(row=0, column=2, pady=5) + ttk.Button(root, text="导入证书", command=import_receiver_public_key).grid(row=0, column=3, pady=5) + + ttk.Label(root, text="选择加密算法:").grid(row=1, column=0, sticky=tk.W, pady=5) + ttk.Combobox(root, textvariable=algorithm_var, values=["AES", "DES"], state="readonly").grid(row=1, column=1, pady=5) + + ttk.Label(root, text="选择加密模式:").grid(row=2, column=0, sticky=tk.W, pady=5) + ttk.Combobox(root, textvariable=mode_var, values=["CBC", "CFB"], state="readonly").grid(row=2, column=1, pady=5) + + ttk.Label(root, text="接收方 IP:").grid(row=3, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=server_ip).grid(row=3, column=1, pady=5) + + ttk.Label(root, text="接收方端口:").grid(row=4, column=0, sticky=tk.W, pady=5) + ttk.Entry(root, textvariable=server_port).grid(row=4, column=1, pady=5) + + ttk.Button(root, text="发送文件", command=send).grid(row=5, columnspan=3, pady=20) + + root.mainloop() + + +if __name__ == "__main__": + sender_main() \ No newline at end of file diff --git a/__pycache__/NEV.cpython-311.pyc b/__pycache__/NEV.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43c5a10841e59a72bf6a563859b580916819bf48 GIT binary patch literal 12011 zcmcIKZFCdYl{3J&&fGO27c9?z&V`#vwPp@ zb8IIi-Q;O>=Y8CF-<$jHyYJom-g`!)j({*h`MSnS2;wg|kqU}r}5A-$FD2;K^xvRBov>Q%R^NsOoTX?m%4N{S<&wpZ7#>(#gGrL@Xt=ry(* zdrj@8UUR!y$|HT2b_=b3m1xhQH2`yI3Sb_s1!$#p0By7$U_NaCSU?*A7JBTo>6p}4 z#-JcPh1a0B*Hhm>09~?8`a8|{ac-+qA?l8LeIDBD_652`CCeUjlA@;1P19a~7nG{I z-N9~8P&5WT4B&WAxmj<(?^KHFdT(F1hY@vdUsr(fvfaHwQPmruJ;6>g-5^0Ah%4d$ z`3nFp5n*WEo-Xbu!z7!99VV_p>Uuio7_Ln)iZIcJYZ75nPkdjIsozF86|MLG7Xoxz z>GSxZ!-#gXZqbzdf)2>+V!MNwB@V}Nh z$A3F=cVsL+{)4;GGx3|(4#z)u;*)01+Z)vyUzt zIT&e;X>3=NquYO`y{w(mlyRD}m^p7iAJdyTi(}X|TnaBiU&f)b&!HaCslqTB)bN&g z^b7d%O9Y$Bhj&k!7qZ-xGy*p-EBaMoC9Oz~dKIh5RA;rBcu%^;Fe&GCZ~4_GW)481v03@(@0srtsX$*Hc?sE&?YLkH?)Ov0q4%-k7h^4 zXNS(*8^3z@gG;mjdeNz8Fnz{~fei!PeFpc9$p;{6_c7i+p9d(2x&v-s$kV_u0Y=o) zo=)au9}7`+s1L;4BT`4bemp5XPOWJ2_@!bG-62f@Bw{ytO;1OJ0pS=MkFjv#_Q#Mz#bJH_NEB8?&PSEgZzFzAwmMae8qh@41X^5Fw>8yO)of&nA&Y?p4zwp@!o_OZ+vN$e zNiCTeW>crz*BSDGly%@GfLRNOtKlDf3P6OIG3E^&x{`l+$lVJs$ zRkJ6}SXpiwV{zptXOesn*fd2L%yhg5b5II5NVzI}q84DNuBV3!7GOmiv7BHr+mm4h z!61_OHf#{uh!e!i%5M`VNGFn1N^jRuQ6F^s*yJ*{uhB_~>f^8yA3e!neJ(2ZwzY|< zWk+MBG|-F_XX;w&4tDHrXx*`|Nz^vH-0A5{uGzZ2&=H@vvjb}eZP450ce5eJBkJ$~ zcO1u>CWzSw^F1L0mPAt>X;cO;pf30a2LMEfIfX%Gi&44Llzob_#|*ZZA%Dh@H?(WU zT0FA*YW}-R-YR{!l($w2*2zHXnz*632CZCAw$f;~~l8ggB!l`5`;3dUHmCt>|<8!J-O;S-OBn9oMYuL+FVl*t!lBB>kPT$9P zkK;*>Wx|+QLZoD7F@U68G;FC=Ab7I3*TXUisbXq@H2wz<0)X9-wRGCLddj*wM%iX4 z^N?Z2QZQn;di>q~x4!%CcR9-@-m*!sY?{e08R@+c7zxC3^XD{3t9w8Ic!($YDJ4#r?R8f>}I4eTWn=K^4>NCZ(B22yxit>t5KBpvlSgIEU z`3N0`@$Of{PEcI~wvdc63Ts{`t}N&$t?H*>Nm56%cGa0iXpOud{o1fLNcz=bDy%&P zMnXR|v{Z|2KlsTxMiPGga-xz5q7#aQ%EwCp3BRv~&0=#T8G5jelR}u9*DqaIM^l*{ ze!m{l+N`tz(z>j)5z_jsvD;g~Y(6H*M^GI`VM`*; zm|47cOa|H_@AWGrTaYS)eSZ#}E9W6dT^^fhgy_L$nH0i#(X0hFl{VP8_=3O|X0m$H648T~MJdS2^HRAz*Z`5j4ck0E zWAm<=-N0c)+SpZFpF$)XvsqmD<9luwA|a_ZP|ZMM1b|F?|s( zVotCdGn9JpHd_kOC8=2UJFK7!!?_YA&`nuZc5$|3g^OvsoFiEFL57xQ(@MqXfB9F| zDcKsg2=|UI4i`fo=ULvEZkt?-mdLr9z;G^Uy^jr|5az|*n}hL>zJKozZ_NJq>R&$` zy8HTt`0$Op*T>`M-*KE$x?HXhp7T&RJMydez(fLZ_R7_JXW!rAkQTOmUIHdTwz(@A zqNPwxEXZtNW1AUL-|SWfOo24{cx z^SiITdhfk=ut!L;+-C=VIy?4O{KNBiCVw11f8l=Q%u|Hl2FT%G;t**m}c6>#$2_^)T*iOjxv_1>??;~%{`J31a8 zzBYUFox5*+@R!IdAuN3fu0of=t95Vu%-x$G#fN`%_jmt1`^M!hj#K8$ZV&UrB`QuF z2`$6bq$ZM;2ZrP4-h}4jzy8IYKU|659KAF7TSo{>jAJ(XA+QFz=m_B%kf;{_U}*N@ zFJ~j~CEAR?`!Dx?i3dJedVc)g_kMrx?dx~P- z8}^C%wmtQ`I@Xc>BvbIJfT!WYKWA38(Kue zUJ0>L8ZFV<7CO@7>0}d`yF4eG{YL|0P9re1HNZ9oLVj9u=13c5$$26<6ea6gGOk2L zASj|fH`^_uo`4s=@B}zlJlAjI~C*bdsuWJx#)R5}BG zzlgw<%OuVGK6iq#`0eiUFgt)944ybl69#yah`C82raLljBV#&G@*mkU$*gI_?8Mc{ zqoOrgESm;m&H|C2pM@uiQ8sO}-BDO3MXfYb@$Ooryg_WTdTG&;WPntLITyr=CgrqR z?45LECHt9hWQCzl8~lTB!^RZ0+gkI`l8eVjPG3DPtbU5uZV|Lw<_LqTbkZH8wCC05 z)Wh1*r#bU#o+=lpa*is;+0;2|IA>5lq#w}FP)5%5?ARdyiHE1Q3e;AP+8VRvUpn}c zgBRLI+6Q$5Xn>9xbLI$*wp0Q$h8!-pYHT|Icz8p#V5sH{)v>(%f$eh&WGs!@m%M3z z%^ux=Juk!RSiur7jB|=&R(s519aUd24Wl_N;LWKCThW{`MOPyJ05=2HRI;s1tMg)D zRL{#vB=x`mi3<{VfcfzdgG}CHF^SqjtVbn;**4@G-N2h1g2@rt@tLiJTT&s|T;P7w zT86DdOCsB1OILEMYK5iGai}?O1~wP*2N6Q^Nr^k2b+ePz|{}F3kMjd0Kj+Q&~a4BoDz_w8WI4*RfP@L z?7U2ACML0D5iEmFWu7sD1USbw@W%Cmas3>j(wZjfW*ilQqwZGOpH|&o^~dsmE9V`% z1jjDUuq5WFoOW!Ta%`QkuM+I*1bg*N!3v?EQYdJgQxRpg(AzTXX(=-S@|+5YNfagU z0MmYmK_+jpnAF>aSdSk+G7&(OP^ioqAPN2pJk=&pZ5-7WqfFQ*JEsC9pabq6+Zt}o zR_?hMdFw&JdN9%wx0OX*6BV~|Z+)BFyN|c+7i{|@t#Mn?=vLmgMzF1kv_8@iIi;hg zd5cT1xFXGuz%%6F9F-GKO?r8AlVEO&G|eC_r`s@A4gek=)d;ADLp7giplfPX6FF17 zF&($zz%4%jc=+NMh2j@Es!(G3vgr1ND-&jvcE#B9c<^_e+cDhA8)o%Hya@SF+%l}_G3zk<9ZUu4N|-q)t72MUPc1^_m$4Z zBIPGVsBx+0lQI&+RhFhT#3vi;N}84`Kg~5j;?t$5X_@BJWl9`hN#gh_DZa+utWo}l zngsY?%ky`ulz&#C-CE6`4J5!$=u|pjgIsbtybW9Qv&ysRtm>@#tmYCipgKy@ z4YhnzdrtiYn3@Hcn#D2YUvo~iI2KJUpr`x-Ozi?plx?hJJlJ4v=x*rcrow8|bIz8|bIy8|bHs&bg5*H$VT*QOdZ46m(vg zxMBTTsAi9o?Q8med=8Wx`#~j5(fM5py5L6PHJFK+^OL`m3)%g(e*^tRw3aUV8vC0) zYQKm)TJotaU55Pt85-~_bA+|8c&PLK|S+3;f zs}k%vNO;SYqB02Q2Y8_n4esDEFk`@e3NR<(Y@-JxF{#JXI-Bm0apcc+dek z9kAsnapPU+&qWn*LlsRIW=G`T9n8t*&O+}t=EI=!NhtLbgXoFyNbpLgePRSLsD6@K z*|$apHBVAQKB7;?4SW_L_t_;x54?xz-CbtE?lcpS1^BlrGr_U_mZ?@ZnVC(GTDH}8-9 z?B4kJ7Kc+MD!V{N_X9Zw+litQoDNJi#GDl4z`3|3k@Sehw9yR4DI-$SO$&%gqRez- zR4;%a{#>w3BRC{v+Z<%w4BL?qXK(2&|*^08y%f%gNvt5$H;@@t+G);xD>&7WMiUHrBegl#Wy`~HFJ=;RHw zV4%6w^LWNwht3LRL<7!*G4#5R_&g!(!q4_vLd6hv!+xod zy>`C}(^N3$;RP-oFU@vK?I+!guYVB#{Y5Aq!o`}-KyR>6=$GmWFpbZXt{apt@rtu0Ps&?b!X3VTkz;1wfaZ*%o3$bj#FH-e^ z-Xnn{ftO(-fOhr;0(~%H&OkM00M|uKAiy#|!0EFXV12)El01h)Vv?jBQ4HK(^7@%~ z0RJuc2mce69V|642j&!{stSZ|Ay8{oAdOE39_14T(@@RerlC!d?J-12=dJ(5qhbLS zV^0L+;EOI!9+e0PUu=n6az_l&r#Z_y-m*@xtcx_yAMjgxreAu9=*? zk&+8bN0vr*&RFwDc1J^;wSu=+2-b>7%Zxc^C>&kMnag=|xnM4jG{uqT)#d?ubSa0* zc~mZ-@@Z5tg(@a0IaHB0aAy)&Q8*Z1%cHddT04!_Poeb_ZVs*I(PjZ{=FsLOEzZ8? zOVLuq;2WWZ3ISD2qpB%XHA!-)ibq=nw1q=k(!`#EL?U+@)lQ+>ThDW-mPb1Tw1Y!C zQpAcTsyMWfM;isSaT+~6g`U2Z!=a~nR4<@<4%K5kILsb(aaEAq&ZF%D+CGh%r%>~! z|Hz?c9z8E0p!|Hg-Sr%*{U5Pt{!K|B%ct6k7VSo=L6Rz&HIJOA` zd`CpO;UKN2(nQ7|?05HiIy!z%NF0!+{QpqTz#1CzdA2gwp$-ZE;8Or$eIUsgp^Zqt z7-5V^zpRLrBa%;ySk5i>#E54(`4c0a;}&~j#BT13pBQ0^NWVD>k;}lzmjV_OSu{ta z!3re_&ayNx8A;n5kp|1Oq#e{#3KS{HVvL*wrky0&M9vYi*I~t467EK&!C{g>hJjO4 YN;{{t$52kBB_mXGN&*!@w#3^110Y)vGynhq literal 0 HcmV?d00001 diff --git a/__pycache__/NEW.cpython-311.pyc b/__pycache__/NEW.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d57d0d2a935fe6053b9fe0718cd85d0d796266c GIT binary patch literal 12926 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 literal 0 HcmV?d00001 diff --git a/__pycache__/receiver.cpython-311.pyc b/__pycache__/receiver.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..230bbba44d5060c714e474bc881360923c59e217 GIT binary patch literal 11882 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 literal 0 HcmV?d00001 diff --git a/__pycache__/sender.cpython-311.pyc b/__pycache__/sender.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d36cb616447ac42d55387c2ad9e6a2a00e0dfcf GIT binary patch literal 12093 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