algo_present/data_provider.py
2024-04-09 14:34:58 +08:00

472 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import socket
import sys
import threading
import time
import pickle
from io import StringIO
import CoreAlgorithm
import DataSearch
# 审批回执类消息head01001001
# sql密文类消息head01001010
# 元数据类消息head01001100
# 实现GUI界面
import tkinter as tk
from tkinter import *
from tkinter import messagebox, scrolledtext
from PIL import ImageTk
# 美化
from ttkbootstrap import Style # pylint: disable=e0401 # type: ignore
from tkinter import ttk
sleep_time = 1
# 用于生成本地公钥和私钥
def generate_key_pair_data():
public_key_data, private_key_data = CoreAlgorithm.generate_paillier_keypair()
return private_key_data, public_key_data
# 用公钥为本地生成数字证书
def get_certificate(temp_public_key, cert_name):
public_key_str = "\n".join(
temp_public_key[i : i + 60] for i in range(0, len(temp_public_key), 60)
)
pack_public_key = (
"-----BEGIN PUBLIC KEY-----\n" + public_key_str + "\n-----END PUBLIC KEY-----"
)
cert = {"public_key": pack_public_key, "name": "<数据提供方>" + cert_name}
return cert
# 公钥转公钥信息
def key_to_data(key):
# str转bytes
byte = bytes.fromhex(key)
# bytes转PaillierPublicKey
data = pickle.loads(byte)
return data
# 加密字符串
def str_to_encrypt(message, public_data):
# str 转 int
if message.isdigit():
int_message = int(message)
else:
int_message = int.from_bytes(message.encode(), "big")
enc_message = public_data.encrypt(int_message)
print("int_message", int_message)
return enc_message
class MyClient:
def __init__(self):
# 实现GUI主界面框用到的参数
self.root = None
self.data_text = None
self.name_text = None
self.message_text = None
self.window = None
# 初始化最近一次的待处理的sql申请信息
self.recent_message = ""
# 初始化本端名
self.name = ""
# 初始化sql
self.sql = ""
# 准备界面函数
self.root_window()
self.give_name()
# 生成私钥和公钥信息
(
self.private_key_data,
self.public_key_data,
) = generate_key_pair_data() # PaillierPublicKey 类型
# 生成私钥和公钥字符串
self.private_key, self.public_key = self.generate_key_pair()
# 初始化数字证书
self.certificate = {} # 等名字输入了再生成
# 初始化socket
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 初始化根界面
def root_window(self):
self.root = tk.Tk()
self.root.title("数据提供方智慧医疗SQL查询系统")
self.root.geometry("450x540")
# 增加背景图片
global photo
photo = ImageTk.PhotoImage(file="background.png")
label = Label(self.root, image=photo)
label.pack()
self.create_widgets() # 调用函数创建功能按钮
# 为本端取名
def give_name(self):
self.window = tk.Toplevel()
self.window.title("命名窗口")
self.window.geometry("300x320")
# 初始化信息输入框
self.name_text = tk.StringVar()
tk.Label(self.window, bg="green", text="请为本端取名:").place(x=50, y=100)
input_message = tk.Entry(self.window, textvariable=self.name_text)
input_message.pack()
input_message.place(x=80, y=150)
# 确认按钮及位置
bt_confirm = tk.Button(
self.window, bg="green", text="确认", width=10, command=self.get_name
)
bt_confirm.pack()
bt_confirm.place(x=125, y=220)
# 读取输入框的name保存在self.name中
def get_name(self):
self.name = self.name_text.get()
messagebox.showinfo("提示", "命名成功!")
self.window.destroy()
self.root.title("<数据提供方>" + self.name + "的智慧医疗系统")
# 安全通信
def safe_connect(self):
if self.certificate != {}:
# 与服务器端地址建立通信, 8080为服务端程序的端口号
self.client.connect(("localhost", 1111))
# 为接收消息函数添加线程
threading.Thread(target=self.get_message).start()
time.sleep(3)
else:
messagebox.showinfo("提示", "请先生成你的公钥证书!")
# 向平台发送本地公钥信息
def send_public_key(self):
print("正在向平台发送本地公钥信息...")
self.client.send(str(self.certificate).encode())
print(self.certificate)
time.sleep(sleep_time)
print("发送成功!")
# 产生公私钥字符串
def generate_key_pair(self):
# Paillier 转 bytes
public_key_bytes = pickle.dumps(self.public_key_data)
private_key_bytes = pickle.dumps(self.private_key_data)
# bytes 转 str
public_key = public_key_bytes.hex()
private_key = private_key_bytes.hex()
return private_key, public_key
# 打印公钥
def print_public_key(self):
print("本地公钥信息为:")
print(self.public_key_data)
print("打印公钥如下:", end="")
public_key_str = "\n".join(
self.public_key[i : i + 60] for i in range(0, len(self.public_key), 60)
)
pack_public_key = (
"\n-----BEGIN PUBLIC KEY-----\n"
+ public_key_str
+ "\n-----END PUBLIC KEY-----\n"
)
print(pack_public_key)
messagebox.showinfo("本地公钥", pack_public_key) # GUI界面显示
# 打印私钥
def print_private_key(self):
print("本地私钥信息为:")
print(self.private_key_data)
private_key_str = "\n".join(
self.private_key[i : i + 60] for i in range(0, len(self.public_key), 60)
)
pack_private_key = (
"-----BEGIN PRIVATE KEY-----\n"
+ private_key_str
+ "\n-----END PRIVATE KEY-----\n"
)
print("打印私钥如下:", end="")
print(pack_private_key)
messagebox.showinfo("本地私钥", pack_private_key) # GUI界面显示
# 打印证书
def print_certificate(self):
if self.name != "":
self.certificate = get_certificate(self.public_key, self.name)
print("本地公钥证书为:")
message = ""
for key, value in self.certificate.items():
print(key, ":\n", value)
message += key + ":\n" + value + "\n"
messagebox.showinfo("本地公钥证书", message)
else:
messagebox.showinfo("提示", "请先为平台命名!")
# 接收消息
def get_message(self):
while True:
# 接收连接结果信息
message = self.client.recv(4096).decode()
print(message)
if message == "安全多方计算平台:单向授权成功!":
self.client.send("请发送平台的完整公钥证书".encode())
while True:
message = self.client.recv(4096).decode() # 公钥字符串比较长,要多一点
if message.startswith("{'public_key':"): # 等待接收平台公钥证书
cert = eval(message)
messagebox.showinfo("连接结果", "与平台连接成功!")
print("接收到的平台的公钥证书如下:")
message = ""
for key, value in cert.items():
if isinstance(value, bytes):
value = value.decode()
print(key, ":\n", value)
message += key + ":\n" + value
messagebox.showinfo("平台的公钥证书", message)
server_public_key = (
cert["public_key"]
.split("-----END PUBLIC KEY-----")[0]
.split("-----BEGIN PUBLIC KEY-----")[1]
)
server_public_key = server_public_key.replace("\n", "")
print("提取到的server_public_key:")
print(server_public_key)
global server_public_key_data
server_public_key_data = key_to_data(server_public_key)
print("对应的server_public_key_data:")
print(server_public_key_data)
messagebox.showinfo("平台的公钥信息", server_public_key_data)
self.client.send("认证成功!".encode())
self.send_public_key() # 单向认证后把自己的公钥证书发给平台实现双向认证
break
else:
print("错误的Message", message)
elif message == "安全多方计算平台:双向授权成功!":
messagebox.showinfo("平台发来的消息", "与平台的双向认证成功!")
self.client.send("请求查询方信息!".encode())
cert = self.client.recv(4096).decode()
cert = eval(cert)
message_p = "接收到一则非平台的公钥证书"
print(message_p)
# messagebox.showinfo("提示", message_p)
message = ""
for key, value in cert.items():
if isinstance(value, bytes):
value = value.decode()
print(key, ":\n", value)
message += key + ":\n" + value + "\n"
# messagebox.showinfo("数据查询方的公钥证书", message)
search_public_key = (
cert["public_key"]
.split("-----END PUBLIC KEY-----")[0]
.split("-----BEGIN PUBLIC KEY-----")[1]
)
search_public_key = search_public_key.replace("\n", "")
print("提取到的search_public_key:")
print(search_public_key)
global search_public_key_data
search_public_key_data = key_to_data(search_public_key)
print("对应的search_public_key_data:")
print(search_public_key_data)
# messagebox.showinfo("数据查询方的公钥信息", search_public_key_data)
elif message.startswith("01001010"):
message = message.split("01001010", 1)[
1
] # [0]是空字符串,已测试。只切一次是防止密文出现和头部一样的信息被误切。
# 使用私钥解密消息,获得sql明文
print("接收到的sql密文:", message)
int_message = int(message)
# Ciphertext转EncryptedNumber
Encrpt_message = CoreAlgorithm.EncryptedNumber(
self.public_key_data, int_message, exponent=0
)
dec_int_message = self.private_key_data.decrypt(Encrpt_message)
dec_message = dec_int_message.to_bytes(
(dec_int_message.bit_length() + 7) // 8, "big"
).decode()
print("解密后的消息为:", dec_message)
self.sql = dec_message.split("||")[2]
print("收到已授权方的sql:", self.sql)
self.design_window()
# --------------------------接收sql消息的窗口设计函数-------------------------
# 接受调用数据的请求
def send_accept_reply(self):
self.client.send(
(
"01001001"
+ str(self.client.getsockname())
+ "||"
+ self.name
+ "同意了你的数据申请。"
).encode()
)
self.window.destroy()
self.window = tk.Toplevel()
self.window.title("命名窗口")
self.window.geometry("300x320")
# 信息输入框
self.data_text = tk.StringVar()
tk.Label(self.window, bg="green", text="请输入要调用的信息:").place(x=50, y=100)
input_message = tk.Entry(self.window, textvariable=self.data_text)
input_message.pack()
input_message.place(x=80, y=150)
# 确认按钮及位置
bt_confirm = tk.Button(
self.window, bg="green", text="确认", width=10, command=self.send_data
)
bt_confirm.pack()
bt_confirm.place(x=125, y=220)
# 发送要调用的信息
def send_data(self):
message = self.data_text.get()
print(message, type(message))
# 加密并封装消息
print("正在封装并加密消息...")
global search_public_key_data
enc_message = str_to_encrypt(message, search_public_key_data).ciphertext()
pack_message = "01001100" + str(enc_message)
print("正在发送消息给平台...")
self.client.send(pack_message.encode())
print("发送成功!")
messagebox.showinfo("提示", "发送成功!")
self.window.destroy()
# 拒绝调用数据的请求
def send_decline_reply(self):
self.client.send(
(
"01001001" + str(self.client.getsockname()) + "||" + "想得美哦!不给(*^▽^*)"
).encode()
)
messagebox.showinfo("提示", "已拒绝!")
self.window.destroy()
# 稍后处理调用数据的请求
def wait_reply(self):
self.window.destroy()
# 接收到sql信息的专属窗口
def design_window(self):
if self.sql != "":
self.window = tk.Toplevel()
self.window.title("平台发来的消息")
self.window.geometry("550x320") # 设定窗口大小
self.recent_message = "收到已授权方的sql:" + self.sql
tk.Label(self.window, bg="green", text=self.recent_message).place(
x=50, y=100
)
# 创建接受按钮
accept_button = tk.Button(
self.window, text="接受", width=15, command=self.send_accept_reply
)
accept_button.place(x=90, y=220)
# 创建拒绝按钮
decline_button = tk.Button(
self.window, text="拒绝", width=15, command=self.send_decline_reply
)
decline_button.place(x=225, y=220)
# 创建稍后处理按钮
decline_button = tk.Button(
self.window, text="稍后处理", width=15, command=self.wait_reply
)
decline_button.place(x=360, y=220)
else:
messagebox.showinfo("提示", "暂无待处理的信息。")
# -----------------------------------------------------------------------
# 调用本地信息库
def search_data(self):
output = StringIO() # 用于保存打印信息
sys.stdout = output # 重定向sys.stdout到StringIO
print(self.name)
DataSearch.main(self.name)
sys.stdout = sys.__stdout__
long_text = output.getvalue()
print(long_text)
# 创建显示窗口
window = tk.Toplevel()
# 创建滚动文本部件
roll = scrolledtext.ScrolledText(window, width=200, height=40)
# 打印超长字符串
roll.insert(tk.END, long_text)
roll.pack()
# --------------------------主界面的按键功能绑定--------------------------------
# 界面按键
def create_widgets(self):
# 创建按钮和标签等部件并使用ttk和Style进行美化
style = Style(theme="darkly") # 指定样式主题
self.root = style.master
button1 = ttk.Button(
self.root,
text="查看我的公钥",
bootstyle="info-outline",
command=self.print_public_key,
width=30,
)
button1.place(x=104, y=310)
button2 = ttk.Button(
self.root,
text="查看我的私钥",
bootstyle="info-outline",
command=self.print_private_key,
width=30,
)
button2.place(x=104, y=348)
button3 = ttk.Button(
self.root,
text="生成我的证书",
bootstyle="info-outline",
command=self.print_certificate,
width=30,
)
button3.place(x=104, y=383)
button4 = ttk.Button(
self.root,
text="获取平台认证",
bootstyle="info-outline",
command=self.safe_connect,
width=30,
)
button4.place(x=104, y=418)
button5 = ttk.Button(
self.root,
text="处理请求信息",
bootstyle="info-outline",
command=self.design_window,
width=30,
)
button5.place(x=104, y=453)
button6 = ttk.Button(
self.root,
text="查看医疗记录",
bootstyle="info-outline",
command=self.search_data,
width=30,
)
button6.place(x=104, y=488)
# GUI界面
def run(self):
self.root.mainloop()
# ----------------------------------------------------主程序----------------------------------------------------
global server_public_key_data, search_public_key_data, photo # 使用全局变量,否则图片可能会被回收,不显示
# GUI界面
app = MyClient()
app.run()