20242402 实验三《Python程序设计》实验报告
20242402 2024-2025-2 《Python程序设计》实验三报告
课程:《Python程序设计》
班级:2424
姓名:张宇涵
学号:20242402
实验教师:王志强
实验日期:2025年4月16日
必修/选修: 公选课
1.实验内容
(1)创建服务端和客户端,选择一个通信端口,用Python语言编程及通过socket套接字实现通信演示程序;
(2)包含文件的基本操作,例如打开和读写操作。
(3)发送方从文件读取内容,使用AES加密或XOR加密后并传输;接收方收到密文并解密,保存在文件中。
(4)程序代码托管到码云。
2. 实验过程及结果
1.先安装pycryptodome库(AES加密的相关库);
2.在老师上课提供的程序,deepseek的帮助和自己亲自动手的多方协作下,完成了客户端,服务端的代码编写,具体逻辑如下:
-
若选择交流,则客户端和服务端一句一句进行互相交流,直至输入
bye
。 -
若选择传输文件,可以选择两种加密方式,异或加密或AES加密。输入文件地址,即可完成加密,传输,解密,存储。
代码如下:
- 服务端:
点击查看代码
# -*- coding: utf-8 -*-
# @Time : 2025/5/1 10:07
# @Author : weare
# @Email : 1021149940@qq.com
# @File : server.py
# @Project : python-experiment
# -*- what can I say -*-
# -*- coding: utf-8 -*-
# 文件名: FinalServer_v2.py
import socket
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# region 加密配置
XOR_KEY = b'secret_key' # 必须与客户端一致
AES_KEY = b'16byte-long-key!'
def xor_crypt(data: bytes) -> bytes:
"""异或解密函数(与加密使用相同函数)"""
return bytes([b ^ XOR_KEY[i % len(XOR_KEY)] for i, b in enumerate(data)])
def aes_decrypt(encrypted: bytes) -> bytes:
"""AES解密函数
Args:
encrypted: 包含iv的加密数据
Returns:
解密后的原始字节数据
"""
iv = encrypted[:16]
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(encrypted[16:]), AES.block_size)
# endregion
def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许端口重用
server.bind(('localhost', 9999))
server.listen(5)
print("服务器已启动,等待连接...")
try:
client, addr = server.accept()
print(f"接收到来自 {addr} 的连接")
mode = client.recv(1).decode() # 获取工作模式
if mode == "1":
# region 聊天模式处理
print("进入聊天模式")
while True:
data = client.recv(1024)
if not data or data.decode().lower() == "bye": # 退出条件
print("客户端终止对话")
break
print(f"客户端说:{data.decode('utf-8')}")
reply = input("回复:")
client.send(reply.encode('utf-8'))
# endregion
else:
# region 文件传输处理
encrypt_type = client.recv(1).decode() # 获取加密方式
print(f"文件传输模式,加密方式:{'异或' if encrypt_type == '1' else 'AES'}")
# 接收完整数据
encrypted = b''
while True:
chunk = client.recv(4096)
if not chunk: break
encrypted += chunk
# 解密处理
if encrypt_type == "1":
decrypted = xor_crypt(encrypted)
else:
decrypted = aes_decrypt(encrypted)
# 保存文件
with open('received_file', 'wb') as f:
f.write(decrypted)
print(f"文件接收成功({len(decrypted)}字节)")
# endregion
finally:
client.close()
server.close()
if __name__ == '__main__':
main()
- 客户端
点击查看代码
# -*- coding: utf-8 -*-
# @Time : 2025/5/1 10:17
# @Author : weare
# @Email : 1021149940@qq.com
# @File : client.py
# @Project : python-experiment
# -*- what can I say -*-
import socket
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
# region 加密配置
XOR_KEY = b'secret_key' # 异或加密密钥(需与服务端一致)
AES_KEY = b'16byte-long-key!' # AES密钥(需16/24/32字节)
def xor_crypt(data: bytes) -> bytes:
"""异或加密/解密函数(对称算法)
Args:
data: 输入字节数据(明文或密文)
Returns:
处理后的字节数据
"""
return bytes([b ^ XOR_KEY[i % len(XOR_KEY)] for i, b in enumerate(data)])
def aes_encrypt(data: bytes) -> bytes:
"""AES加密函数
Args:
data: 待加密的原始字节数据
Returns:
iv(16字节) + 加密后的数据
"""
iv = os.urandom(16)
cipher = AES.new(AES_KEY, AES.MODE_CBC, iv)
return iv + cipher.encrypt(pad(data, AES.block_size))
# endregion
def main():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client.connect(('localhost', 9999))
print("已连接服务器!请选择模式:\n1. 聊天模式\n2. 文件传输")
# region 模式选择
while True: # 输入验证循环
mode = input(">> ")
if mode in ("1", "2"):
client.send(mode.encode()) # 发送模式标识
break
print("请输入1或2")
# endregion
if mode == "1":
# region 聊天模式
print("进入聊天模式(输入bye结束)")
while True:
msg = input("你说:")
client.send(msg.encode('utf-8'))
if msg.lower() == "bye": # 退出条件
print("对话结束")
break
reply = client.recv(1024).decode('utf-8')
print(f"服务器回复:{reply}")
# endregion
else:
# region 文件传输模式
# 选择加密方式
while True:
encrypt_type = input("请选择加密方式:\n1. 异或加密\n2. AES加密\n>> ")
if encrypt_type in ("1", "2"):
client.send(encrypt_type.encode()) # 发送加密方式
break
print("输入无效,请重新选择!")
file_path = input("输入要传输的文件路径:")
try:
with open(file_path, 'rb') as f:
file_data = f.read()
# 加密处理
if encrypt_type == "1":
encrypted = xor_crypt(file_data)
else:
encrypted = aes_encrypt(file_data)
client.sendall(encrypted)
print(f"文件发送完成({len(file_data)}字节)")
except FileNotFoundError:
print("错误:文件不存在")
# endregion
finally:
client.close()
if __name__ == '__main__':
main()
-
对每个分支进行测试:
- 对话:
- AES加密:
- XOR加密:
3.代码托管到gitee中。
3. 实验过程中遇到的问题和解决过程
- 问题1:在安装库的时候,控制台上无法安装,提示Requirement already satisfied: pycryptodome in d:\python\python3.7\lib\site-packages (3.22.0)
- 问题1解决方案:在查询了CSDN后,转变思路,使用pycharm中自带的库安装的方法。如下图:
-
问题2:第一次运行的时候,客户端报错:Traceback (most recent call last):
File "D:\pycharm\python-experiment\client.py", line 78, in
main()
~~~~^^
File "D:\pycharm\python-experiment\client.py", line 33, in main
server.bind(('localhost', 9999))
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。接着我去询问了deepseek。 -
问题2解决方案:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('localhost', 9999)) # ❌ 客户端错误地绑定了端口
错误发生在客户端尝试绑定服务器端口(这是服务端的行为),导致端口冲突。客户端应该使用
connect()
而非bind()
。 将客户端中的错误修正,程序就可以正常运行了。
其他(感悟、思考等)
AES加密
首先呢,我很好奇AES加密算法的实现形式,但又怕看不懂,于是我就询问了deepseek:
现在我想了解一下aes加密实现方式是怎样的,他的每一步是怎样的,背后的数学原理是啥?请详细讲讲,说人话。谢谢。
deepseek给我的回复是:
我将用做菜的过程来类比AES加密,让你直观理解这个"加密厨房"如何运作。我们以最常见的AES-128(密钥长度128位)为例。
一、准备工作:切配食材(数据预处理)
-
切块处理:把明文切成16字节的小块(128位),比如"HelloWorld123456"会被切成:
| H | e | l | l | | o | W | o | r | | l | d | 1 | 2 | | 3 | 4 | 5 | 6 |
-
腌制食材(密钥扩展):用主密钥生成11把不同的"调料刀"(轮密钥)
- 主密钥是原始调料(比如:盐)
- 通过特殊配方(Rijndael密钥扩展算法),生成10轮调料组合+1把初始调料刀
二、烹饪流程(加密10轮)
每轮操作就像不同的烹饪手法,这里展示核心四道工序:
1. 撒盐(字节替换 - SubBytes)
- 每个字节通过一个特殊配方表(S盒)替换
- 类似把"西红柿"替换成"糖渍番茄"
- 示例:
A3 → 0x8A
2. 翻炒(行移位 - ShiftRows)
- 第一行不动
- 第二行左移1格
- 第三行左移2格
- 第四行左移3格
原始:
| A | B | C | D |
| E | F | G | H |
| I | J | K | L |
| M | N | O | P |
移位后:
| A | B | C | D |
| F | G | H | E |
| K | L | I | J |
| P | M | N | O |
3. 搅拌(列混淆 - MixColumns)
- 每列进行矩阵乘法(在伽罗瓦域GF(2^8)上)
- 类似用打蛋器把蛋液搅拌均匀
- 数学公式:
新字节 = 2*旧字节 ⊕ 3*旧字节 ⊕ 旧字节 ⊕ 旧字节
(这里的⊕是异或操作,*是特殊乘法)
4. 加调料(轮密钥加 - AddRoundKey)
- 当前数据块与轮密钥做异或操作(XOR)
- 类似最后撒一把秘制调料
# 示例:XOR操作
数据: 01000001 (A)
轮密钥: 00010001
结果: 01010000 (P)
三、核心数学原理
-
有限域(Galois Field):
- 使用GF(2^8)这个数学系统(256个元素的有限域)
- 所有运算结果都不会超过1字节(0-255)
- 例如:加法=异或,乘法=特殊位移运算
-
扩散与混淆:
- 扩散:改变1个比特会影响整个加密结果(像打翻调料瓶影响整锅汤)
- 混淆:密钥与密文的关系复杂到无法追踪(像调料比例成为商业机密)
-
轮次设计:
- 10轮操作形成"雪崩效应"
- 即使只改变1比特,经过多轮操作后差异会被放大到整个数据块
四、为什么AES安全?
-
密钥空间大:AES-128有
2^128 ≈ 3.4×10^38
种可能密钥- 对比:地球沙子总数约
7.5×10^18
粒
- 对比:地球沙子总数约
-
数学基础牢固:
- 基于有限域理论和线性代数
- 没有已知的数学捷径可破解
-
抗攻击设计:
- 差分攻击需
2^100
次操作 - 线性攻击需
2^118
次操作
- 差分攻击需
五、完整流程图示
六、现实类比
想象你在做一个10层的加密蛋糕:
- 每层都要:涂果酱(字节替换)→ 旋转蛋糕(行移位)→ 混合奶油(列混淆)→ 撒糖粉(轮密钥加)
- 最后一层不用混合奶油(最终轮没有MixColumns)
- 每层使用的糖粉配方不同(轮密钥不同)
这样的蛋糕即使你知道制作步骤,也无法逆向推出原始配方(密钥)。这就是AES的安全精髓!
它的解释很有意思啊,把加密类比成做饭,确实是实现了我提示词中的说人话
,便于让我这个小白有了一个初步的印象。
选择操作的实现
选择哪种操作这部分我不太会,是deepseek帮我实现的,然后我让他解释了一下这部分:
while True:
mode = input(">> ")
if mode in ("1", "2"): # 输入有效性验证
client.send(mode.encode()) # 发送模式标识
break
print("请输入1或2") # 错误提示
-
循环控制:
while True
创建无限循环,确保必须得到有效输入 -
输入验证:
if mode in ("1", "2")
检查输入是否为合法选项 -
协议同步:
client.send()
将模式标识发送给服务端,建立通信约定 -
流程突破:
break
退出循环继续后续操作 -
容错处理:错误提示帮助用户正确输入