QQ聊天框消息加密

之前做过简单的英文文本加密,用过凯撒密码这样很简单的加密方式,我想有没有简单易学,但是加密程度更高的加密方式;
另外通过这个程序还想学习一下python的GUI开发,做出真正的界面;
如果可以与QQ等聊天界面结合起来,将内容直接输入到QQ的消息窗口中去,省去了复制粘贴的麻烦。

2021年8月我写好了第一版的程序,但是这个效果嘛,Emmmmm……一言难尽。
2022年3月的一天下午,厌学了!也是因为好久都没有写过程序了,就看了一些自己的博客,看一下还有什么坑没有填!然后就发现了我之前做的这个半成品,然后花了几个小时的时间,重写了一下,也把UI(简陋)界面简单做了一下!

murphyhoucn/EncryptedChat

博客地址:QQ聊天框消息加密

视频演示:”加密对话”EncryptedChat

需求分析

  1. 写一个聊天外挂框,实现在框内输入内容,自动加密然后通过QQ聊天框发送出去。
  2. 做一个简单的UI,其实我之前并没有学过也没用做过GUI的开发,了解一点点QT,这次做这个程序也是现学现卖了。
  3. 消息加密算法,大概率是使用对称加密了,如果是非对称加密的话估计仅靠本地是不太好实现的,得上云了!
  4. 写着玩,能用就行!

理论依据

PyQt5

PyQt5 · PyPI

Qt is set of cross-platform C++ libraries that implement high-level APIs for accessing many aspects of modern desktop and mobile systems. These include location and positioning services, multimedia, NFC and Bluetooth connectivity, a Chromium based web browser, as well as traditional UI development.

PyQt5 is a comprehensive set of Python bindings for Qt v5. It is implemented as more than 35 extension modules and enables Python to be used as an alternative application development language to C++ on all supported platforms including iOS and Android.

PyQt5 may also be embedded in C++ based applications to allow users of those applications to configure or enhance the functionality of those applications.

Base64

Base64 - 维基百科,自由的百科全书 (wikipedia.org)

Base64(基底64)是一种基于64个可打印字符来表示二进制数据的表示方法。由于一2为64的对数的值是6,所以每6个比特为一个单元,对应某个可打印字符。3个字节相当于24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。在Base64中的可打印字符包括字母A-Za-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。一些如uuencode的其他编码方法,和之后BinHex的版本使用不同的64字符集来代表6个二进制数字,但是不被称为Base64。

Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME电子邮件XML的一些复杂数据。

AES

高级加密标准 - 维基百科,自由的百科全书 (wikipedia.org)

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),又称Rijndael加密法(荷兰语发音: [ˈrɛindaːl],音似英文的“Rhine doll”),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在,高级加密标准已然成为对称密钥加密中最流行的算法之一。

该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael为名投稿高级加密标准的甄选流程。

严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中两者可以互换),因为Rijndael加密法可以支持更大范围的区块密钥长度:AES的区块长度固定为128比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度均可以是128,192或256比特。加密过程中使用的密钥是由Rijndael密钥生成方案产生。

大多数AES计算是在一个特别的有限域完成的。

AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵的“列数(Row number)”可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:

  1. AddRoundKey—矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
  2. SubBytes—透过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
  3. ShiftRows—将矩阵中的每个横列进行循环式移位。
  4. MixColumns—为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。

源代码

  • Encrpt.py
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
119
120
121
122
123
124
125
126
127
128
import sys
from PyQt5 import QtWidgets
from GUI import Ui_Form

import win32gui
import win32con
import win32clipboard as w
import time

import base64
from Crypto.Cipher import AES

'''
采用AES对称加密算法
'''

class MyPyQT_Form(QtWidgets.QWidget,Ui_Form):

def __init__(self):
super(MyPyQT_Form, self).__init__()
self.setupUi(self)

# 获取窗口所输入的信息的函数
def pushButton1_click(self):

# 获取Windows界面输入的消息
name=self.lineEdit_1.text()
key=self.lineEdit_2.text()
msg=self.lineEdit_3.text()

# 对消息进行加密
msg_encrypt=encrypt_oracle(key,msg)

self.textEditi.setPlainText("加密信息如下:\n"+\
"昵称:" + name +'\n'+\
"密钥:" + key +'\n'+\
"明文:" + msg +'\n'+\
"暗文:" + msg_encrypt +'\n')
start(name,msg_encrypt)

def pushButton2_click(self):

key=self.lineEdit_2.text()

get_msg_encrypt=getText()
# print(get_msg_encrypt)
msg_decrypt=decrypt_oralce(key,get_msg_encrypt)

self.textEdito.setPlainText("解密信息如下:\n"+\
"暗文:" + get_msg_encrypt +'\n'+\
"明文:" + msg_decrypt +'\n')


# 输出相关函数
def start(name,msg_encrypt):


# =====将消息复制到剪切板中=====

w.OpenClipboard() #打开剪切板
w.EmptyClipboard() #清空剪切板
w.SetClipboardData(win32con.CF_UNICODETEXT, msg_encrypt) #将msg_encrypt设置给剪切板
w.CloseClipboard() #关闭剪切板

handle = win32gui.FindWindow(None, name) # 根据对方昵称获取窗口句柄
win32gui.SendMessage(handle, 770, 0, 0) # 将剪切板内容填充消息
win32gui.SendMessage(handle, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0) # 按下回车发送消息


# 加密相关函数
# str不是16的倍数那就补足为16的倍数
def add_to_16(value):
while len(value) % 16 != 0:
value += '\0'
return str.encode(value) # 返回bytes

# 加密相关函数
# 加密方法
def encrypt_oracle(key0,msg):
# 秘钥
key = key0

mystr=msg
text = base64.b64encode(mystr.encode('utf-8')).decode('ascii')
# 初始化加密器
aes = AES.new(add_to_16(key), AES.MODE_ECB)
# 先进行aes加密
encrypt_aes = aes.encrypt(add_to_16(text))
# 用base64转成字符串形式
encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') # 执行加密并转码返回bytes
# print(encrypted_text) 测试打印加密数据
return encrypted_text



# 解密
def decrypt_oralce(key0,msg):
# 秘钥
key = key0
# 密文
text=msg
# 初始化加密器
aes = AES.new(add_to_16(key), AES.MODE_ECB)
# 优先逆向解密base64成bytes
base64_decrypted = base64.decodebytes(text.encode(encoding='utf-8'))

# bytes解密
decrypted_text = str(aes.decrypt(base64_decrypted),encoding='utf-8') # 执行解密密并转码返回str
decrypted_text = base64.b64decode(decrypted_text.encode('utf-8')).decode('utf-8')
# print(decrypted_text)

return decrypted_text


def getText(): # 读取剪切板
w.OpenClipboard()
d = w.GetClipboardData(win32con.CF_TEXT)
w.CloseClipboard()
return d.decode('utf-8')


if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
my_pyqt_form = MyPyQT_Form()

# 窗口的打开和关闭
my_pyqt_form.show()
sys.exit(app.exec_())
  • GUI.py
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from PyQt5 import QtCore,QtWidgets
from PyQt5.QtGui import QIcon
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

# from PyQt5.QtWidgets import QDesktopWidget,QMainWindow,QApplication

class Ui_Form(object):
def setupUi(self, Form):
# 建立Windows窗口,并设置合适的大小

Form.setObjectName("Form")
Form.resize(1300, 600)

Form.setWindowIcon(QIcon("ICON.ico"))

# QRect ( int x, int y, int width, int height )
# 构造一个以(x,y)为左上角、宽度和高度为width,height的矩形。
self.widget = QtWidgets.QWidget(Form)
self.widget.setGeometry(QtCore.QRect(130, 70, 1000, 400))
self.widget.setObjectName("widget")


self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")

# 界面标题
self.formtitle = QtWidgets.QLabel(self.widget)
self.formtitle.setObjectName("formtitle")
self.verticalLayout.addWidget(self.formtitle)

# 界面脚注
self.footer = QtWidgets.QLabel(self.widget)
self.footer.setObjectName("footer")
self.verticalLayout.addWidget(self.footer)

self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")


# int fromRow // 横坐标
# int fromColumn // 纵坐标
# int rowSpan // 横向跨越几个单元格
# int columnSpan // 纵向跨越几个单元格

# 建立三个单行文本输入框QLineEdit
self.lineEdit_1 = QtWidgets.QLineEdit(self.widget)
self.lineEdit_1.setObjectName("lineEdit_1")
self.gridLayout.addWidget(self.lineEdit_1, 0, 2, 1, 1)

self.lineEdit_2 = QtWidgets.QLineEdit(self.widget)
self.lineEdit_2.setObjectName("lineEdit_2")
self.gridLayout.addWidget(self.lineEdit_2, 1, 2, 1, 1)

self.lineEdit_3 = QtWidgets.QLineEdit(self.widget)
self.lineEdit_3.setObjectName("lineEdit_3")
self.gridLayout.addWidget(self.lineEdit_3, 2, 2, 1, 1)


# 建立三个文本标签框QLabel
self.name = QtWidgets.QLabel(self.widget)
self.name.setObjectName("name")
self.gridLayout.addWidget(self.name, 0, 0, 1, 2)

self.key = QtWidgets.QLabel(self.widget)
self.key.setObjectName("key")
self.gridLayout.addWidget(self.key, 1, 0, 1, 2)

self.message = QtWidgets.QLabel(self.widget)
self.message.setObjectName("message")
self.gridLayout.addWidget(self.message, 2, 0, 1, 2)


self.verticalLayout.addLayout(self.gridLayout)

# 建立一个用户交互按键QPushButton
self.pushButton1 = QtWidgets.QPushButton(self.widget)
self.pushButton1.setObjectName("pushButton1")
self.verticalLayout.addWidget(self.pushButton1)

# 建立一个用户交互按键QPushButton
self.pushButton2 = QtWidgets.QPushButton(self.widget)
self.pushButton2.setObjectName("pushButton2")
self.verticalLayout.addWidget(self.pushButton2)



self.retranslateUi(Form)
self.pushButton1.clicked.connect(Form.pushButton1_click)
self.pushButton2.clicked.connect(Form.pushButton2_click)
QtCore.QMetaObject.connectSlotsByName(Form)

# 尚未解决的功能:把pushButton的快捷键设置为Enter

# self.center()

# 设置两个显示文本框
self.textEditi = QtWidgets.QTextEdit()
self.textEditi.setObjectName("lineEditi")
self.gridLayout.addWidget(self.textEditi, 0, 4, 1, 1)

self.textEdito = QtWidgets.QTextEdit()
self.textEditi.setObjectName("textEdito")
self.gridLayout.addWidget(self.textEdito, 2, 4, 1, 1)


def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "聊天加密客户端")) #设置窗口标题

# 设置界面标题
self.formtitle.setText(_translate("Form", \
"<html>\
<head/>\
<body>\
<p align=\"center\">\
<span style=\" font-size:30pt; font-weight:600; font-family: Times New Roman;\">\
Encrypted Chat\
</span>\
</p>\
</body>\
</html>"))


self.footer.setText(_translate("Form", \
"<html>\
<head/>\
<body>\
<p align=\"center\">\
<span style=\" font-size:15pt; font-weight:300; font-family: Times New Roman;\">\
Designed by Murphy\
</span>\
</p>\
</body>\
</html>"))

self.name.setText(_translate("Form", "输入对方昵称"))

self.key.setText(_translate("Form", "输入加密密钥"))

self.message.setText(_translate("Form", "输入消息内容"))

self.pushButton1.setText(_translate("Form", "加密"))
self.pushButton2.setText(_translate("Form", "解密"))


# def center(self): # 定义一个函数使得窗口居中显示
# # 获取屏幕坐标系
# screen = QDesktopWidget().screenGeometry()
# # 获取窗口坐标系
# size = self.geometry()
# newLeft = (screen.width() - size.width()) / 2
# newTop = (screen.height() - size.height()) / 2
# self.move(int(newLeft),int(newTop))

结果

打包成为exe文件

稍显简陋,但是能用就行!

问题

  • UI窗口界面问题(简陋不是问题(/doge)),因为这给UI元素都是固定在一个确定的位置的,因此,在把界面放大或者缩小的时候,界面中的元素不能自适应位置,还是保持了原来的位置。
  • 加密密钥问题,我测试的数量不算特别多,英文和数字均支持,中文不支持;且要求密钥的位数不能过长,不要超过35位,否则会闪退;
  • 还是不能做到完全自动化,对方发过来的消息还是需要自己去复制一下,再点击“解密”按键才可以解密的。如果说要解决这个问题的话,我想到的只能是多线程方法,但是这个我并没有学过,一时半会不好试验我的这个想法能不能解决这个问题。

QQ聊天框消息加密
https://cosmicdusty.cc/post/Ideas/ChatEncryption/
作者
Murphy
发布于
2022年3月18日
许可协议