Yet another python Chat client

  • Tutorial
Greetings, habrauser.
Already there was an article about the chat client in Python Habré . This article made me write my bike for academic purposes, but it’s not interesting to repeat someone else’s code. We’ll pose a more interesting task: Jabber (Asymmetric RSA Encryption) + PyQt.
If interested, welcome to cat.

Of course, not only this, but for example the fact that chats in social networks will be tapped, and just increase your skill in writing programs in python.
This code was written under Debian, python 2.7, Qt 4.7, so I will describe it for it, it was not tested on other systems.

Let's get started


Decide on the message format.
1. Request key
#getkey "If you see this message, you must install the utility ..."
2. Send the key
#sendkey 234234234
3. Message
#mesg 123123123
4. Forward the last message (not implemented)
#getlastmesg
I decided that # <something> is a good choice for designating commands, in addition, all messages are encrypted and a message of the form # <anything> will be sent correctly. I think that one could do without it, I just wanted to be more beautiful.

Let's start with the simple part, namely the jabbir part.


It is interesting to write your own engine for the gillber client, but now we are moving to the result, so let's take the ready-made xmpppy module. Install it with the
sudo easy_install xmpppy command .
You can, of course, use this library right away, but I think it’s better to use our wrapper and put this functionality into a separate file, which in the future will be easier to refactor if such a need arises. For this library to work, the following is required: our jid, our password, and the callback for incoming messages.
jabber.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import xmpp,sys
#Данный фаил сожердит обертку для xmpp 
class sjabber:
	def __init__(self,xmpp_jid,xmpp_pwd):
		self.xmpp_jid = xmpp_jid
		self.xmpp_pwd = xmpp_pwd
		self.jid = xmpp.protocol.JID(xmpp_jid)
		self.client = xmpp.Client(self.jid.getDomain(),debug=[])
	def connect(self):
		con=self.client.connect()
		if not con:
			print 'could not connect!'
			sys.exit()
		print 'connected with',con
		auth = self.client.auth(self.jid.getNode(),str(self.xmpp_pwd),resource='xmpppy')
		if not auth:
			print 'could not authenticate!'
			sys.exit()
		print 'authenticated using',auth
		#Говорим серверу что мы онлайн! 
		self.client.sendInitPresence(1)
	def Process(self):
		a = self.client.Process(1)
	def send(self,to,mess):
		id = self.client.send(xmpp.protocol.Message(to,mess))
		print 'sent message with id',id
	def disconnect(self):
		self.client.sendInitPresence(1)
		self.client.disconnect()
	def userList(self):
		return self.client.getRoster().keys()
	def StepOn(self):
		try:
			self.Process()
		except:
			return 0
		return 1
	def setCB(self, CB):
		self.CB = CB
		self.client.RegisterHandler('message',self.messageCB)
	def messageCB(self,conn,mess):
		if ( mess.getBody() == None ):
			return
		self.CB(self,mess)


This code, when created, initializes the objects to connect. The connect command connects to the server. Also has a wrapper for sending messages. And in fact, it is only a decorator for the library code. The presence of a large number of ready-made libraries, in my opinion, is a big plus for python and allows you to write code in a fairly short time.

We fasten the encryption.


I decided to take RSA as the encryption algorithm, simply because I like it. In addition, it is asymmetric, i.e. each session we can generate new key pairs and distribute only the public part. Thus, instead of messages, the third party will see only a bunch of HEX instead of messages.
I made the encryption module separate for the same reasons.

rsa_decor.py
# -*- coding: utf-8 -*-
import rsa
class Crypt:
	def __init__(self):
		#Словарь в котором будут храниться известные нам ключи
		self.keys = dict()
		#Генерируем и сохраняем наши ключи
		(a,b) = self.genKey(1024)
		self.privateKey = b
		self.pubKey = a
	def hasKey(self,id):
		#Проверяем на наличие ключа для контакта
		if self.keys.has_key(id)==False:
			return False
		else:
			return True
	def decodeKey(self,key):
		#Создаем публичный ключи и загружаем переданый по сети вариант
		return rsa.PublicKey(1,1).load_pkcs1(key,format='DER')
	def saveKey(self,id,key): 
		#Сохраняем ключ
		self.keys[id]= key
	def genKey(self,size):
		#Обертка для рса
		return  rsa.newkeys(size, poolsize=8)
	def cryptMesg(self,to,mesg):
		#Шифруем сообщение
		getHex =mesg.encode('utf-8').encode('hex')
		a = rsa.encrypt(getHex, self.keys[to])
		#print len(mesg),len(a)
		return a.encode('hex')
	def decryptMesg(self,mesg):
		#Пытаемся расшифровать сообщение, иначе выдаем ошибку
		try:
			mess = rsa.decrypt(mesg.decode("hex"),self.privateKey)
		except rsa.pkcs1.DecryptionError:
			print "cant decrypt"
			return "#errorDecrypt"
		return mess.decode('hex').decode('utf-8')


Here, too, everything is simple and logical. We decorate the functions we need, and also store everything related to the keys (or rather, our private and public keys, a dictionary with the keys we know).

Let's get down to the main


This part was written quickly enough, and it was decided to sculpt the main module, the actual core of the application, which would connect the parts.

Initially, it was decided to write an interface on TK. But it turned out badly, and I remembered that python can communicate well with Qt.
Therefore, we deliver PyQt itself to the Qt Designer system, at the time of writing there was version 4.7 (unfortunately I can’t tell you how to install all of this under Win, everything is installed in Linux by the package system of your distribution) install
sudo apt-get install pyqt4-dev-tools libqt4- core libqt4-dev libqt4-gui python-qt4 qt4-designer
This set of packages should suffice.
So let's start by drawing a form.
Run Qt Designer.
Create the main_widget form.
Organize as follows, the central widget
is a vertical layer.
We will place 2 widgets in it: a horizontal layer in which there will be a place for entering a message and a button for sending, a splitter in which there will be a text browser for displaying messages and a widget sheet in which we put the contact list.
In the end, it should turn out like this.

We will not dwell on the work of QtDesigner, it is well described in the documentation (Qt has extremely good documentation)
Ready ui-file.
However, this file is not ready for us to use, it is necessary to turn it into a Python code, for this we need the pyuic4 utility.
We will use it.
pyuic4 main_window.ui -o gui.py
Now we have a file with graphics, with encryption, with jabber, it remains to combine everything together.
To combine it, we will write a class.
       def __init__(self):
                #Первым делом загрузим настройки
                self.loadConfig()
                #Создадим объект для шифрования
                self.crypt = Crypt()
                #Создадим и подключимся к жабберу
                self.jb = sjabber(self.xmpp_jid,self.xmpp_pwd)
                self.jb.connect()
                #Зададим колбек для приходящих сообщений
                self.jb.setCB(self.messageCB)
               #Создадим Qt-обработчик событий для графики
                self.app = QApplication(sys.argv)
                self.window = QMainWindow()
                self.ui = Ui_MainWindow()
                self.ui.setupUi(self.window)

Here we dwell in more detail, in Qt there is a system of signals and slots, the QApplication class is required for its processing, and since the graphics use them, we will add it. After that, we will create a window and the graphic elements themselves (we created them above), after which we will place them in our window.
               #Подключим сигналы нажатия на кнопку отправить и нажатие энтер
                self.ui.pushButton.clicked.connect(self.sendMsg)
                self.ui.lineEdit.returnPressed.connect(self.sendMsg)
                self.window.show()
                #А теперь заполним юзерлист 
                userList = self.jb.userList()
                for i in userList:
                        self.ui.listWidget.addItem(i)
                #Меняем пользователя для отправки сообщения
                self.ui.listWidget.currentRowChanged.connect(self.setToRow)
                #Выберем по умолчанию первого пользователя
                self.ui.listWidget.setCurrentRow(0)
                #Создадим рабочего который будет "дергать" обработчик жаббера
                self.workThread = WorkThread()
                self.workThread.setWorker(self.jb)
                self.workThread.start()

This implementation of the gill-client requires constant “twitching” for processing incoming messages, which also blocks the main thread, so we will create a separate class of the worker who will live in a separate thread and serve the gill-client. What is characteristic, this class is very similar to the C ++ code for Qt for working with streams.
class WorkThread(QThread):
        def __init__(self):
                QThread.__init__(self)
        def setWorker(self,jb):
                self.jb = jb
        def run(self):
                while self.jb.StepOn(): pass

Actually, this is where our application is almost ready, with the exception of the callback that processes incoming messages (well, a little different small things).
def messageCB (self, conn, mess)
       def messageCB(self,conn,mess):
                #Данный колбек проверяет регулярное выражение, после чего 
                #Либо работает с ключами, либо шифрует сообщения
                if ( mess.getBody() == None ):
                        return
                msg = mess.getBody()
                patern = re.compile('^#(getkey|sendkey|mesg|getlastmesg) ?(.*)')
                res = patern.search(msg)
                if res:
                        #Проверка
                        a = res.groups()[0]
                        if a == "getkey":
                                self.sendKey(mess.getFrom())
                                if  self.crypt.hasKey(mess.getFrom())!=False:
                                        conn.send(mess.getFrom(),"#getkey")     
                        elif a == "sendkey":
                                if res.groups()[1]!='':
                                        a = self.crypt.decodeKey(res.groups()[1].decode("hex"))
                                        self.crypt.saveKey(mess.getFrom().getStripped(),a)
                        elif a == "mesg":
                                decryptMess = self.crypt.decryptMesg(res.groups()[1])
                                if decryptMess=="#errorDecrypt":
                                        self.sendKey(mess.getFrom())
                                        self.print_("Error decrypt sendKey")
                                else:
                                        self.print_(self.to+"--> "+decryptMess)
                        elif a == "getlastmesg*":
                                print a



As a handler, I did not come up with anything new, so the message is checked with a regular expression, in case of coincidence with it, a transition is made to the reaction corresponding to the type of message.

Another horror is sending messages. The fact is that the standard RSA algorithm can encrypt strings of a certain length, depending on the size of the key, which for 1024 bytes is approximately 52 characters in Unicode, so the procedure divides the string into pieces, which it encrypts and sends. In my opinion, this is a terrible crutch, but my knowledge of the python did not allow me to make it more beautiful.

You can watch all the code on the github.

Actual result.

Constructive criticism of the code is welcome.

Also popular now: