现代密码学基础技能

大数据安全与隐私这门课的lab1,要求如下图:

avatar

实现思路:
• 通过Socket实现文件传输,使用TCP协议确保数据传输的可靠性。
• 代码通过读取文件的二进制数据进行传输,支持任意类型的文件。
• 通过AES加密算法进行文件数据的加密和解密。
• 使用RSA加密算法对AES密钥进行加密,确保密钥的安全传输。
• 通过在AES密钥中加入时间戳的方式,确保每次生成不同的密钥,从而保证相同文件每次发送的加密结果不同。

具体实现:
客户端 (client.py)

  1. 连接服务器
    o 创建Socket并连接到服务器的指定端口。
  2. AES密钥生成与加密
    o 生成16字节的随机AES密钥。
    o 加入时间戳生成唯一的AES密钥。
    o 使用RSA公钥加密AES密钥并发送给服务器。
  3. 文件传输
    o 读取文件信息(文件名和大小),发送给服务器。
    o 分块读取文件数据,使用AES进行加密,发送加密数据到服务器。
    服务端 (server.py)
  4. 监听客户端连接
    o 创建Socket,绑定指定端口,并设置监听队列。
  5. 接收并解密AES密钥
    o 生成RSA密钥对,并将公钥发送给客户端。
    o 接收加密的AES密钥并使用RSA私钥解密。
  6. 文件接收
    o 接收文件信息(文件名和大小)。
    o 分块接收加密文件数据,使用AES解密,写入到文件中。

运行结果如下:
avatar

avatar
图中标黄的内容是RSA加密的AES秘钥, 标蓝色的是AES加密的密文, 标绿的是原文, 原文后面出现\x05的原因是客户端发送文件前对文件进行了PKCS7填充, 客户端这里打印出的是未经取消填充的内容

我们再来一次, 可以发现生成了不同的AES秘钥, 自然也就实现了相同文件每次发送的加密结果不同

avatar

avatar

服务端代码

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
import sys
import threading
import socket
import struct
from Crypto.Cipher import AES
import rsa
import time

iv = '1425374853627180' # 初始向量
BUFF = 1024

# 去除末尾填充字符
def unpadding(text):
length = len(text)
print(text[length - 1])
return text[0:length - text[length - 1]]


def socket_service():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置socket可以重用已绑定地址
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 9001)) # 绑定端口为9001
s.listen(10) # 设置监听数,最多允许10个客户端连接
except socket.error as msg:
print(msg)
sys.exit(1)
print('等待连接...')

while 1:
# 调用accept阻塞: 等待请求并接受(程序会停留在这一旦收到连接请求即开启接受数据的线程)
conn, addr = s.accept()
# 接收数据
t = threading.Thread(target=deal_data, args=(conn, addr))
t.start()


def deal_data(conn, addr):
print('接收到来自 {0}的连接'.format(addr))
# 收到请求后的回复
conn.send('welcome!'.encode('utf-8'))

key = ""
while 1:
if not key:
s1_recv_data = conn.recv(BUFF)
if s1_recv_data.decode('utf-8') == 'changekey':
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 开始交换秘钥!')
# 使用RSA产生一对公钥和私钥
(pubkey, privkey) = rsa.newkeys(512, poolsize=8)
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 创建RSA秘钥对!')
# 将公钥模数和指数发送给客户端
modulus = pubkey.n
exponent = pubkey.e
conn.send(str(modulus).encode('utf-8'))
conn.send(str(exponent).encode('utf-8'))

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 发送RSA公钥')
# 服务端收到消息
key = conn.recv(BUFF)
print(time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(time.time())) + ' 接收加密后的AES秘钥:' + key.decode('utf8',
'ignore'))
# 服务端用私钥进行解密,得到AES密钥
key = rsa.decrypt(key, privkey)
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 解密AES秘钥中')
key = key.decode()
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' AES秘钥:' + key)

# 申请相同大小的空间存放发送过来的文件名与文件大小信息
fileinfo_size = struct.calcsize('128sl')
# 接收文件名与文件大小信息
buf = conn.recv(fileinfo_size)
# 判断是否接收到文件头信息
if buf:
# 获取文件名和文件大小
filename, filesize = struct.unpack('128sl', buf)
fn = filename.strip(b'\00')
fn = fn.decode()
print('文件名是 {0}, 文件大小是 {1}'.format(str(fn), filesize))
recvd_size = 0 # 定义已接收文件的大小
# 存储在该脚本所在目录下面
fp = open('./' + str(fn), 'wb')
print('开始接收...')

if not filesize % 16 == 0:
filesize += 16 - filesize % 16
# 将分批次传输的二进制流依次写入到文件
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = conn.recv(1024)
recvd_size += len(data)
else:
data = conn.recv(filesize - recvd_size)
recvd_size = filesize

print(len(data))
print(data)

# 将加密数据转换位bytes类型数据
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
text_decrypted = cipher.decrypt(data)
print(len(text_decrypted))
print(text_decrypted)
if len(text_decrypted) < 1024:
text_decrypted = unpadding(text_decrypted)
print(text_decrypted)
fp.write(text_decrypted)
fp.close()
print('结束接收...')
# 传输结束断开连接
conn.close()
break


if __name__ == "__main__":
socket_service()

客户端代码

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
import socket
import os
import sys
import struct
from Crypto.Cipher import AES
import time
import rsa

key0 = os.urandom(16).hex() # 随机生成秘钥,16位字符串
iv = '1425374853627180' # 初始向量
BUFF = 1024



def padding(text):
bs = AES.block_size # 16字节
length = len(text) # 获取明文长度,字符
bytes_length = len(bytes(text, encoding='utf-8'))
padding = length if (bytes_length == length) else bytes_length
padding_size = bs - padding % bs
padding_text = chr(padding_size) * padding_size # 按照PKCS7填充方式生成填充文本
return text + padding_text

# 函数功能:对明文进行PKCS7填充
# 参数text:需要填充的明文
# 返回值:填充后的明文


def socket_client():
try:
# 创建socket,ipv4+流
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 9001))
except socket.error as msg:
print(msg)
sys.exit(1)
print(s.recv(1024))

# 加上给密钥加上时间戳,保证相同的明文,每次发送的密文不同
t = time.time()
t_str = str(int(t))
key = key0[0:8] + t_str[2:10] # 取key0前八位和时间戳中的后八位生成新key
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 开始交换秘钥!')
s.send('changekey'.encode('utf-8'))
modulus = int(s.recv(BUFF).decode('utf-8')) # 接收公钥模数
exponent = int(s.recv(BUFF).decode('utf-8')) # 接收公钥指数

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 开始构建RSA公钥')
pubkey = rsa.PublicKey(modulus, exponent) # 构建公钥

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 加密AES秘钥')
crypto = rsa.encrypt(key.encode('utf-8'), pubkey) # 用RSA公钥对AES密钥进行加密

print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 发送加密后的AES秘钥:' + crypto.decode(
'utf8', 'ignore'))
s.send(crypto) # 发送加密后的AES密钥

# 需要传输的文件路径
filepath = 'C:/XingYan/1.png'
# 判断是否为文件
if os.path.isfile(filepath):
# 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或long,在此为文件大小
fileinfo_size = struct.calcsize('128sl')
# 打包文件名和大小
filehead = struct.pack('128sl', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
s.send(filehead)

# 将传输文件以二进制的形式分多次上传至服务器
fp = open(filepath, 'rb')
while 1:
data = fp.read(1024)
print(data)
if not data:
print('{0} 发送结束...'.format(os.path.basename(filepath)))
break

if len(data) < 1024:
newdata = data.decode('utf8', 'ignore')
print(len(newdata))
newdata = padding(newdata)
print(len(newdata))
data = newdata.encode('utf8')
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8')) # AES加密对象
encryptedbytes = cipher.encrypt(data)
s.send(encryptedbytes)
print(encryptedbytes)
print(len(encryptedbytes))
s.close()


if __name__ == '__main__':
socket_client()