📜 ⬆️ ⬇️

Yet another python Chat client

Greetings, habrauzer.
Already there was an article about the chat client on the python on the habr. This article encouraged me to write my bike for academic purposes, but repeating someone else's code is not interesting, we set the task more interesting: Jabber (RSA Asymmetric Encryption) + PyQt.
If interested, welcome under cat.

Of course, not only that, but for example the fact that social networking chat will be tapped, and just improve your skills in writing python programs.
This code was written under Debian, python 2.7, Qt 4.7, therefore I will describe for it, on other systems it was not checked.

Let's get started


We will define the message format.
1. Key request
#getkey "If you see this message, then you need to install the utility ..."
2. Parcel key
#sendkey 234234234
3. Message
#mesg 123123123
4. Forwarding the last message (not implemented)
#getlastmesg
I decided that # <something> is a good choice for designating commands, besides, all messages are encrypted and a message like # <something> will be sent correctly. I think that it was possible to do without it, I just wanted to be more beautiful.

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


It is interesting to write your own engine for a gabber client, but now we are moving towards a result, so we’ll take a ready-made xmpppy module. Install it with a command
sudo easy_install xmpppy.
You can, of course, use this library right away, but I think it is better to use our wrapper, and put this functionality into a separate file, which in the future will be easier to refactor if the need arises. For the work of this library, the following is necessary: ​​our jid, our password and 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 creating initializes objects for connection. By the command connect connects to the server. Also has a wrapper for sending messages. And as a matter of fact it is only a decorator for 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 encryption.


As an encryption algorithm, I decided to take RSA, simply because I like it. Moreover, it is asymmetrical, i.e. we can generate new key pairs every session and distribute only the public part. Thus, instead of messages, a 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 all that is associated with the keys (or rather, our private and public keys, a dictionary with keys known to us).

Getting down to the point


This part was written fairly quickly, 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 the python is able to communicate well with Qt.
Therefore, we deliver PyQt to the Qt Designer system, at the time of writing there was version 4.7 (unfortunately I can't tell you the installation of all this under Win, everything is installed in Linux by the package system of your distribution package)
sudo apt-get install pyqt4-dev-tools libqt4-core libqt4-dev libqt4-gui python-qt4 qt4-designer
This set of packages should be enough.
Therefore, let's start by drawing a form.
Launch Qt Designer
Create the form main_widget.
Arrange as follows, central widget
- 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.
The result should be like this.

We will not stop working at QtDesigner, it is well described in the documentation (Qt has extremely good documentation)
Ready ui-file.
However, this file is not ready for use by us, it is necessary to turn it into Python code, for this we need the pyuic4 utility.
We use it.
pyuic4 main_window.ui -o gui.py
Now we have a file with graphics, with encryption, with a jabber, it remains to combine everything.
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) 

We’ll dwell here, there is a system of signals and slots in Qt, for its processing the QApplication class is required, and since the graphics use them, we add it. After that, we will create a window and the graphic elements themselves (we have 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 gabber client requires constant “twitching” to process incoming messages, which also blocks the main thread, so we will create a separate worker class that will live in a separate thread and serve the gabber client. Characteristically, this class is very similar to C ++ code for Qt for working with threads.
 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 on this, our application is almost ready, with the exception of the callback processing incoming messages (well, a little bit other 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 didn’t invent anything new, so the message is checked by a regular expression, in case of a match with this, a transition is made to the reaction corresponding to the type of message.

Another horror is sending messages. The point is that the standard RSA algorithm can encrypt strings of a certain length depending on the key size, which for about 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 python did not allow me to make it more beautiful.

You can see all the code on the githaba.

Actually the result

Constructive criticism of the code is welcome.

Source: https://habr.com/ru/post/165243/


All Articles