20241207 实验三《Python程序设计》实验报告

20241207 2025-2026-2 《Python程序设计》实验三报告

课程:《Python程序设计》
班级: 2412
姓名: 陈琪雅
学号:20241207
实验教师:王志强
实验日期:2026年5月10日
必修/选修: 公选课

1.实验内容

本次实验要求使用Python创建加密通信程序,包括服务端和客户端。服务端在指定端口监听。双方能够实现文本消息的加密传输,同时输出明文和密文。
第一部分(命令行版):
编写TCP服务端和客户端程序,使用AES-256-GCM对称加密,实现加密通信。
发送方输入内容加密后传输,接收方解密并显示明文。
双方同时输出明文和对应的密文(十六进制形式)。
与队友互相通信,并将代码托管到码云。

第二部分(图形界面版):
使用LLM生成一个带图形界面的加密通信程序(tkinter实现),补充文件操作。
分析关键代码的功能和使用方法,分析生成程序的优点。
给出运行过程和结果截图,代码托管到码云。

2. 实验过程及结果

2.1撰写代码

2.1.1 加密实现思路

采用AES-256-GCM对称加密算法。GCM模式是流式加密,无需手动处理填充,可直接加密任意长度的数据,实现较为简单
密钥固定为32字节(随机设定为b'1234567890abcdef12345678'),nonce固定为12字节(随机设定为b'123456789012')。
实验中为调用函数需使用cryptography库,预先进行了“pip install cryptography”安装操作。
屏幕截图 2026-05-10 132341

2.1.2 服务端代码

import socket
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = b'1234567890abcdef12345678'

def encrypt(plaintext):
    nonce = b'123456789012'
    return nonce + AESGCM(key).encrypt(nonce, plaintext.encode('utf-8'), None)

def decrypt(ciphertext):
    nonce = ciphertext[:12]
    data = ciphertext[12:]
    return AESGCM(key).decrypt(nonce, data, None).decode('utf-8')

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "172.20.10.7"
port = 8080

server.bind((host, port))
server.listen(1)

print("加密通信服务端")
print(f"我的学号:20241207")
print(f"队友学号:20243222")
print(f"监听IP:{host}")
print(f"端口:8080")
print()

print("服务器已启动,等待客户端连接......")

conn, addr = server.accept()
print(f"已连接客户端: {addr}")

while True:
    encrypted_data = conn.recv(1024)
    if not encrypted_data:
        break
    plaintext = decrypt(encrypted_data)
    if plaintext == "exit":
        print("聊天结束")
        break
    print(f"接收密文: {encrypted_data.hex()}")
    print(f"客户端: {plaintext}")

    reply = input("我: ")
    encrypted_reply = encrypt(reply)
    conn.send(encrypted_reply)
    if reply == "exit":
        break

conn.close()
server.close()

2.1.3 客户端代码

import socket
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

key = b'1234567890abcdef12345678'

def encrypt(plaintext):
    nonce = b'123456789012'
    return nonce + AESGCM(key).encrypt(nonce, plaintext.encode('utf-8'), None)

def decrypt(ciphertext):
    nonce = ciphertext[:12]
    data = ciphertext[12:]
    return AESGCM(key).decrypt(nonce, data, None).decode('utf-8')

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_host = "172.20.10.22"
port = 8080

client.connect((server_host, port))

print("加密通信客户端")
print(f"我的学号:20241207")
print(f"队友学号:20243222")
print(f"连接服务端IP:172.20.20.22")
print(f"端口:8080")
print()

print("已连接服务端!输入exit退出聊天!")

while True:
    msg = input("我: ")
    encrypted_msg = encrypt(msg)
    client.send(encrypted_msg)
    if msg == "exit":
        break

    encrypted_reply = client.recv(1024)
    if not encrypted_reply:
        break
    reply = decrypt(encrypted_reply)
    if reply == "exit":
        print("聊天结束")
        break
    print(f"接收密文: {encrypted_reply.hex()}")
    print(f"服务端: {reply}")

client.close()

2.1.4 运行结果

与队友连接同一局域网,将IP手动修改为学号。
使用ipconfig确认IP,再用ping测试连通性。
两电脑进行连接。(队友连我电脑截图)
6bd0906ee6bd751a0dea12a4e1e744c6

启动服务端,再启动客户端连接:
我为服务端(IP为172.20.10.7)
屏幕截图 2026-05-10 132447

我为客户端(连接服务端IP172.20.10.22)
屏幕截图 2026-05-10 132341

2.2LLM撰写的图形代码

(代码来自AI:Deepseek)
使用tkinter构建GUI版本,同样采用AES-256-GCM加密,并增加文件传输功能(加分项)和多线程处理。

加密模块:
encrypt和decrypt函数直接使用命令行版本,采用AES-256-GCM,保证数据机密性。
网络模块:
服务端使用 threading.Thread分别启动_accept_client(接受连接)和 _receive_messages(接收消息)两个独立线程,避免阻塞GUI主循环;客户端同样使用独立线程 _receive_messages监听服务端消息。客户端可指定 IP 连接。
GUI模块:
tkinter创建主窗口,LabelFrame分组显示设置区域,ScrolledText作为日志滚动框。
按钮绑定回调函数(start_server、stop_server、connect_server、disconnect),实现状态控制。
消息输入框绑定 Return事件,支持回车发送。
文件传输预留:
使用send_file 和 receive_file搭建框架,通过 [FILE] 标记区分文件传输和普通消息,实际分块加密传输逻辑未完整实现。

2.2.1服务端代码

import socket               #提供网络通信功能
import threading           #实现多线程,避免界面卡顿
import tkinter as tk       #图形界面基础库
from tkinter import scrolledtext, filedialog  #滚动文本框和文件对话框
from cryptography.hazmat.primitives.ciphers.aead import AESGCM  #AES-GCM加密

#AES-256需要32字节密钥
KEY = b'1234567890abcdef12345678'
#GCM模式需要12字节nonce
NONCE = b'123456789012'

def encrypt(plaintext: str) -> bytes:
    # 加密:将明文转换为密文,返回nonce+密文字节串
    return NONCE + AESGCM(KEY).encrypt(NONCE, plaintext.encode('utf-8'), None)

def decrypt(ciphertext: bytes) -> str:
    # 解密:从密文中提取nonce,解密并返回明文字符串
    nonce = ciphertext[:12]      #前12字节是nonce
    data = ciphertext[12:]       #剩余部分是密文
    # 解密后解码为UTF-8字符串
    return AESGCM(KEY).decrypt(nonce, data, None).decode('utf-8')

class ServerGUI:
    def __init__(self):
        # 创建主窗口
        self.root = tk.Tk()
        self.root.title("加密通信服务端 - 学号:20241207")
        self.root.geometry("600x500")   # 窗口大小

        # 网络相关变量
        self.server_socket = None   # 监听socket(用于接受新连接)
        self.client_conn = None     # 与客户端通信的socket
        self.running = False        # 控制服务端运行状态

        # 构建界面
        self._create_widgets()

    def _create_widgets(self):
        """创建所有图形界面组件"""
        # 控制区域(端口设置、启动/停止按钮)
        frame = tk.LabelFrame(self.root, text="服务端设置", padx=5, pady=5)
        frame.pack(fill=tk.X, padx=10, pady=5)

        # 端口标签和输入框
        tk.Label(frame, text="端口:").grid(row=0, column=0)
        self.port_entry = tk.Entry(frame, width=8)
        self.port_entry.insert(0, "8080")   # 默认端口
        self.port_entry.grid(row=0, column=1)

        # 启动按钮:绑定 start_server 方法
        self.start_btn = tk.Button(frame, text="启动服务端", command=self.start_server)
        self.start_btn.grid(row=0, column=2, padx=10)

        # 停止按钮:初始禁用,等启动后启用
        self.stop_btn = tk.Button(frame, text="停止服务端", command=self.stop_server, state=tk.DISABLED)
        self.stop_btn.grid(row=0, column=3)

        # 日志显示区域(滚动文本框)
        self.log_area = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, wrap=tk.WORD)
        self.log_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 消息发送区域 
        msg_frame = tk.Frame(self.root)
        msg_frame.pack(fill=tk.X, padx=10, pady=5)

        self.msg_entry = tk.Entry(msg_frame)
        self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
        
        # 绑定回车键,按下回车直接发送消息
        self.msg_entry.bind("<Return>", self.send_message)

        self.send_btn = tk.Button(msg_frame, text="发送", command=self.send_message)
        self.send_btn.pack(side=tk.RIGHT)

        # 文件发送按钮(扩展功能)
        self.file_btn = tk.Button(self.root, text="发送文件", command=self.send_file)
        self.file_btn.pack(pady=5)

    def log(self, msg):
        """在日志区域追加一条消息(线程安全,直接调用)"""
        self.log_area.config(state=tk.NORMAL)   # 临时解锁文本框
        self.log_area.insert(tk.END, msg + "\n")
        self.log_area.see(tk.END)               # 自动滚动到底部
        self.log_area.config(state=tk.DISABLED) # 重新锁定,防止用户编辑

    # ---------- 服务端核心逻辑 ----------
    def start_server(self):
        """启动服务端:创建socket、绑定、监听,并启动接受客户端线程"""
        port = int(self.port_entry.get())
        try:
            # 创建TCP socket
            self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 绑定到所有网络接口(0.0.0.0),允许其他电脑连接
            self.server_socket.bind(('0.0.0.0', port))
            # 开始监听,最多1个等待连接
            self.server_socket.listen(1)
            self.log(f"[系统] 服务端启动,监听端口 {port}")

            # 更新按钮状态
            self.start_btn.config(state=tk.DISABLED)
            self.stop_btn.config(state=tk.NORMAL)
            self.running = True

            # 启动一个线程专门接受客户端连接,避免阻塞主界面
            threading.Thread(target=self._accept_client, daemon=True).start()
        except Exception as e:
            self.log(f"[错误] 启动失败: {e}")

    def _accept_client(self):
        """等待客户端连接(在独立线程中运行)"""
        try:
            # accept() 会阻塞直到有客户端连接
            self.client_conn, addr = self.server_socket.accept()
            self.log(f"[系统] 客户端 {addr} 已连接")
            # 启动另一个线程接收该客户端发来的消息
            threading.Thread(target=self._receive_messages, daemon=True).start()
        except Exception:
            # 如果服务端已关闭,accept可能抛出异常,忽略即可
            pass

    def _receive_messages(self):
        """持续接收客户端消息(在独立线程中运行)"""
        while self.running and self.client_conn:
            try:
                # 接收密文数据(最多4096字节)
                enc_data = self.client_conn.recv(4096)
                if not enc_data:           # 连接关闭
                    break
                # 解密得到明文
                plain = decrypt(enc_data)

                if plain == "exit":
                    self.log("[系统] 客户端请求退出")
                    break
                # 判断是否为文件传输指令(扩展功能)
                if plain.startswith("[FILE]"):
                    self.receive_file(enc_data)
                else:
                    # 显示明文和密文的十六进制形式
                    self.log(f"[客户端] 明文: {plain}")
                    self.log(f"[客户端] 密文: {enc_data.hex()}")
            except Exception as e:
                self.log(f"[错误] 接收消息异常: {e}")
                break
        # 退出循环后清理连接
        self.stop_server()

    def send_message(self, event=None):
        """发送消息:获取输入框文本,加密后发送"""
        if not self.client_conn:
            self.log("[系统] 没有客户端连接,无法发送")
            return
        msg = self.msg_entry.get().strip()
        if not msg:
            return
        try:
            # 加密消息
            enc_msg = encrypt(msg)
            self.client_conn.send(enc_msg)
            # 在本地日志中显示发送的明文和密文
            self.log(f"[我] 明文: {msg}")
            self.log(f"[我] 密文: {enc_msg.hex()}")
            self.msg_entry.delete(0, tk.END)   # 清空输入框
            if msg == "exit":
                self.stop_server()
        except Exception as e:
            self.log(f"[错误] 发送失败: {e}")

    def send_file(self):
        """发送文件(加分项框架)"""
        if not self.client_conn:
            self.log("[系统] 没有客户端连接,无法发送文件")
            return
        # 弹出文件选择对话框
        file_path = filedialog.askopenfilename()
        if not file_path:
            return
        # 这里仅演示文件选择,实际文件传输需要分块读取、加密并发送
        self.log(f"[系统] 准备发送文件: {file_path}")
        # 可在此处实现真正的文件传输逻辑(略)

    def receive_file(self, first_packet):
        """接收文件(加分项框架)"""
        # 本实验未完整实现,仅提示
        self.log("[系统] 收到文件,请实现完整接收逻辑(可扩展)")

    def stop_server(self):
        """停止服务端:关闭socket,重置状态"""
        self.running = False
        if self.client_conn:
            self.client_conn.close()
        if self.server_socket:
            self.server_socket.close()
        self.log("[系统] 服务端已停止")
        # 恢复按钮状态
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)

    def run(self):
        """启动GUI主循环"""
        self.root.mainloop()

# ---------- 程序入口 ----------
if __name__ == "__main__":
    app = ServerGUI()
    app.run()

2.2.2客户端代码

import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, filedialog
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

# ---------- 加密配置----------
KEY = b'1234567890abcdef12345678'
NONCE = b'123456789012'

def encrypt(plaintext: str) -> bytes:
    return NONCE + AESGCM(KEY).encrypt(NONCE, plaintext.encode('utf-8'), None)

def decrypt(ciphertext: bytes) -> str:
    nonce = ciphertext[:12]
    data = ciphertext[12:]
    return AESGCM(KEY).decrypt(nonce, data, None).decode('utf-8')

# ---------- 客户端图形界面类 ----------
class ClientGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("加密通信客户端 - 学号:20241207")
        self.root.geometry("600x500")

        self.client_socket = None   # 与服务端通信的socket
        self.connected = False      # 是否已连接
        self._create_widgets()

    def _create_widgets(self):
        """构建界面组件"""
        # ----- 连接设置区域 -----
        frame = tk.LabelFrame(self.root, text="连接设置", padx=5, pady=5)
        frame.pack(fill=tk.X, padx=10, pady=5)

        tk.Label(frame, text="服务端IP:").grid(row=0, column=0)
        self.ip_entry = tk.Entry(frame, width=12)
        self.ip_entry.insert(0, "127.0.0.1")
        self.ip_entry.grid(row=0, column=1)

        tk.Label(frame, text="端口:").grid(row=0, column=2)
        self.port_entry = tk.Entry(frame, width=6)
        self.port_entry.insert(0, "8080")
        self.port_entry.grid(row=0, column=3)

        self.connect_btn = tk.Button(frame, text="连接", command=self.connect_server)
        self.connect_btn.grid(row=0, column=4, padx=5)
        self.disconnect_btn = tk.Button(frame, text="断开", command=self.disconnect, state=tk.DISABLED)
        self.disconnect_btn.grid(row=0, column=5)

        # ----- 对话日志区域 -----
        self.log_area = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, wrap=tk.WORD)
        self.log_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # ----- 消息输入区域 -----
        msg_frame = tk.Frame(self.root)
        msg_frame.pack(fill=tk.X, padx=10, pady=5)
        self.msg_entry = tk.Entry(msg_frame)
        self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
        self.msg_entry.bind("<Return>", self.send_message)
        self.send_btn = tk.Button(msg_frame, text="发送", command=self.send_message)
        self.send_btn.pack(side=tk.RIGHT)

        # ----- 文件发送按钮(扩展)-----
        self.file_btn = tk.Button(self.root, text="发送文件", command=self.send_file)
        self.file_btn.pack(pady=5)

    def log(self, msg):
        """在日志区域添加一行"""
        self.log_area.config(state=tk.NORMAL)
        self.log_area.insert(tk.END, msg + "\n")
        self.log_area.see(tk.END)
        self.log_area.config(state=tk.DISABLED)

    # ---------- 客户端核心网络操作 ----------
    def connect_server(self):
        """连接到服务端"""
        ip = self.ip_entry.get()
        port = int(self.port_entry.get())
        try:
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client_socket.connect((ip, port))
            self.connected = True
            self.log(f"[系统] 已连接服务端 {ip}:{port}")
            # 更新按钮状态
            self.connect_btn.config(state=tk.DISABLED)
            self.disconnect_btn.config(state=tk.NORMAL)
            # 启动接收线程,持续监听服务端发来的消息
            threading.Thread(target=self._receive_messages, daemon=True).start()
        except Exception as e:
            self.log(f"[错误] 连接失败: {e}")

    def disconnect(self):
        """主动断开连接"""
        if self.client_socket:
            self.client_socket.close()
        self.connected = False
        self.log("[系统] 已断开连接")
        self.connect_btn.config(state=tk.NORMAL)
        self.disconnect_btn.config(state=tk.DISABLED)

    def _receive_messages(self):
        """接收服务端消息(运行在独立线程)"""
        while self.connected:
            try:
                enc_data = self.client_socket.recv(4096)
                if not enc_data:
                    break
                plain = decrypt(enc_data)
                if plain == "exit":
                    self.log("[系统] 服务端退出")
                    break
                # 显示收到的明文和密文(十六进制)
                self.log(f"[服务端] 明文: {plain}")
                self.log(f"[服务端] 密文: {enc_data.hex()}")
            except Exception as e:
                self.log(f"[错误] 接收消息失败: {e}")
                break
        # 接收循环结束,自动断开连接
        self.disconnect()

    def send_message(self, event=None):
        """发送消息给服务端"""
        if not self.connected:
            self.log("[系统] 未连接服务端")
            return
        msg = self.msg_entry.get().strip()
        if not msg:
            return
        try:
            enc_msg = encrypt(msg)
            self.client_socket.send(enc_msg)
            self.log(f"[我] 明文: {msg}")
            self.log(f"[我] 密文: {enc_msg.hex()}")
            self.msg_entry.delete(0, tk.END)
            if msg == "exit":
                self.disconnect()
        except Exception as e:
            self.log(f"[错误] 发送失败: {e}")

    def send_file(self):
        """发送文件(加分项框架)"""
        if not self.connected:
            self.log("[系统] 未连接服务端")
            return
        file_path = filedialog.askopenfilename()
        if not file_path:
            return
        self.log(f"[系统] 准备发送文件: {file_path}")
        # 实际文件传输需要分块读取、加密并发送,此处仅演示

    def run(self):
        """启动GUI主循环"""
        self.root.mainloop()

if __name__ == "__main__":
    app = ClientGUI()
    app.run()

2.2.3运行截图

屏幕截图 2026-05-11 185621
屏幕截图 2026-05-11 190039
屏幕截图 2026-05-11 190047
屏幕截图 2026-05-11 190107
屏幕截图 2026-05-11 190134
屏幕截图 2026-05-11 190147

2.3两代码对比(AI生成代码优点纯享版)
1.交互性强:图形界面程序通过按钮和输入框实现直观操作
2.信息展示更清晰:使用滚动区域分类显示,每条消息都加上了“我”或“服务端/客户端”的明确标签,可读性更强
4.原程序采用单线程模型,收发消息交替进行,一方发送时无法同时接收消息,而图形界面程序引入多线程,可以实现收发并行。
5.实现文件传输的功能扩展。

3. 实验过程中遇到的问题和解决过程

  • 问题1:实验中我首先尝试了国密SM4算法,向AI学习了加解密函数使用后发现,ECB/CBC模式要求特定长度的明文输入(明文长度为16字节的倍数),如果明文不符合要求,需要手动实现PKCS7填充字符长度,由于未完全理解逻辑,尝试撰写时反复出错多次。

  • 问题1解决方案:经AI建议,改用Python的cryptography库实现AES-256-GCM。该模式为流式加密,无需手动填充,且自带完整性校验,更加适合流式通信场景。

  • 问题2:开始时无法和队友的电脑实现网络连通(ping)

  • 问题2解决方案:在老师的指导下将子网掩码都改为255.255.255.0

其他(感悟、思考等)

选择国密算法进行编写程序时,对sm4等不同算法的具体实现逻辑有了了解

实验代码托管

posted @ 2026-05-11 19:32  山河13  阅读(17)  评论(0)    收藏  举报