The environment that

  • Operating system: MAC
  • Programming language: Python3
  • Dependencies on Python libraries: Tkinter, Pymysql, SSL, Socket, etc. (most are built in Python)
  • Mysql database should be established first: FileTransfer, create table user, containing three fields: ID, USERNAME, password

Generate a certificate

PROJECT_NAME="jiang zheng fool Project"
# Generate the openssl configuration files.
cat > ca_cert.conf << EOF [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] O = $PROJECT_NAME Certificate Authority EOF
cat > server_cert.conf << EOF [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] O = $PROJECT_NAME CN = SERVER EOF
cat > client_cert.conf << EOF [ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] O = $PROJECT_NAME Device Certificate CN = SERVER EOF
mkdir ca
mkdir server
mkdir client
mkdir certDER
# private key generation
openssl genrsa -out ca.key 1024
openssl genrsa -out server.key 1024
openssl genrsa -out client.key 1024
# cert requestsopenssl req -out ca.req -key ca.key -new -config ./ca_cert.conf openssl req -out server.req -key server.key -new -config  ./server_cert.conf openssl req -out client.req -key client.key -new -config ./client_cert.conf# generate the actual certs.
openssl x509 -req -in ca.req -out ca.crt -sha1 -days 5000 -signkey ca.key
openssl x509 -req -in server.req -out server.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
openssl x509 -req -in client.req -out client.crt -sha1 -CAcreateserial -days 5000 -CA ca.crt -CAkey ca.key
mv ca.crt ca.key ca/
mv server.crt server.key server/
mv client.crt client.key client/

Copy the code

The SSL protocol provides the following services:

1) Authenticate users and servers to ensure that data is sent to the correct client and server;

2) Encrypt data to prevent data theft;

3) Maintain the integrity of data and ensure that data is not changed during transmission.

The certificate file is generated as follows:

Open ca.crt and you can see the result as shown in the figure below:

An overview of the

The project consists of two small projects, including a group chat function and a file transfer system. The group chat function mainly realizes the function of multi-group chat, cache message and so on. The file transfer system includes uploading and downloading files.

Group chat function

Program started

- client.py Indicates the client1- client2.py Specifies the client2- server.py Specifies the serverCopy the code

rendering

Folder Introduction

  • Cer — This folder holds the CA root certificate and server and client certificates (generated using OpenSSL)
    • CA — root certificate and key
    • Server — Server key, signature certificate, and signed certificate
    • Client — Client key, signature certificate, and signed certificate

The file is introduced

MultiPersonChat

  • Client. py Client 1
  • Client2.py client2
  • Server. Py server

Complete the function

  • Online reminder, offline reminder
  • The SSL link
  • Password encryption
  • Users cannot log in to the system repeatedly
  • The server needs to cache part of the recent historical messages so that they can be pushed to the client when it just goes online
  • Visual interface
  • Multi-user chat

Client. Py client classes

The method name Specific function
send_login_info Send the user name and password to the server for authentication and return the verification result
send_register_info Send the user name and password to the server and return the registration result
recv_data The client receives data from the server
close Closes the socket connecting the client to the server

Client. Py LoginPanel class

The method name Specific function
set_panel_position Set the position and size of the login screen in the screen
config_for_reg_panel Set other Settings for the login screen
set_title Place the interface title
set_form Place the login form
set_btn Place the registration and login buttons
show Call the instance method for the overall layout of the login interface
close The interface is closed
get_input Gets the user name and password entered by the user
login_func Functionality encapsulated into the login button in the login screen
reg_func Encapsulate it into the registration button on the login interface to jump from the login interface to the registration interface

server.py

The method name function
encrypt_psw The MD5 algorithm is used to encrypt the user password
check_user Check whether the user name and password entered during the login are correct
add_user User name to be registered Check whether there are duplicate user names
update_online_list Update the list of online clients
online_notice Send a notification to all online clients that a new client is online
offline_notice Send user offline notification to all online users
handle_login Processing a login request
handle_reg Handle registration requests from clients
handle_msg Broadcast the content to be sent by the client
handle The main framework in which the server runs

The key code is the following, which is responsible for the distribution of requests:

def handle(new_socket, addr) :
    """ The server runs on the main framework :param new_socket: the client socket of this connection: Param ADDR: the IP address and port of this connection client """
    try:
        while True:
            req_type = new_socket.recv(1).decode("utf-8")  # Get the request type
            print(req_type)
            if req_type:  # If not true, the client is disconnected
                if req_type == "1":  # Login request
                    print("Start processing login request")
                    handle_login(new_socket)
                elif req_type == "2":  # Registration request
                    print("Start processing registration requests")
                    handle_reg(new_socket)
                elif req_type == "3":  # Send a message
                    print("Start processing send message request")
                    handle_msg(new_socket)
            else:
                break
    except Exception as ret:
        print(str(addr) + "Connection abnormal, ready to disconnect:" + str(ret))
    finally:
        try:
            # The operation performed after the client is disconnected
            new_socket.close()
            online_socket.remove(new_socket)
            offline_notice(new_socket)
            socket2user.pop(new_socket)
            time.sleep(4)
            update_online_list()
        except Exception as ret:
            print(str(addr) + "Connection closing exception")


Copy the code

Cache Function

Each chat content and user is stored in an array through the structure, which is then distributed to the user when he logs in

Secure file transfer function

Boot method

  • Start the server:
python server_ssl.py   
python server_no_ssl.py
Copy the code
  • Start the client:
python main.py

Copy the code

Folder Description

  • Cer — This folder holds the CA root certificate and server and client certificates (generated using OpenSSL)
    • CA — root certificate and key
    • Server — Server key, signature certificate, and signed certificate
    • Client — Client key, signature certificate, and signed certificate
  • ClientCache – This directory holds download list data that requests updates from the server
  • ClientDownload – ClientDownload path
  • ServerRec — Server upload path

Document describing

  • Main. py Client startup file
  • Client_login. py Client login page
  • Client_mian. Py Main interface of the client
  • View. py Main interface view of the client
  • Client_socket_no_ssl. py The client does not encrypt communication objects
  • Client_socket_ssl. py The client encrypts the communication object
  • Server_no_ssl. py The server does not encrypt the communication code
  • Server_ssl. py The server encrypts the communication code without encrypting it
  • Result. TXT records the download list of the server
  • Serverlog. TXT Logs of the server

The flow chart

The server process is as follows:

The client flowchart is as follows:

Custom transport protocol

In each exchange between the server and the client, a custom header is added. The client actively requests data from the server and the server passively returns data, so the data header of the two will be different. This system uses Python struct structure to realize the transmission of binary header stream.

The client header is as follows :(1024 bytes)

  • Command includes Download, Upload, Update, Login, and Register
  • In the download command, filename indicates the filename to be downloaded. In the Upload mode, filename indicates the local path to Upload the file.
  • Filesize is the size of the file
  • Time is the time of the data request
  • User and password are user names and passwords that are validated each time data is requested, simulating Cookie mode.
header = {
            'Command': 'Download'.'fileName': filename,
            'fileSize': ' '.'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
            'user': self.username,
            'password': self.password,
        }
Copy the code

Struct in Python is used as follows

header_hex = bytes(json.dumps(header).encode('utf-8'))
fhead = struct.pack('1024s', header_hex)
self.ssock.send(fhead)
Copy the code

The server header is as follows :(128 bytes)

Since the server is passively replying to the client, not much is needed in the header, so 128 bytes are used.

  • Feedback Indicates the instruction to be responded to
  • Stat indicates the status of the response (such as registration, login, and so on)
  • Filesize is the size of the file
  • User is the current User
header = {
                'Feedback': 'Login'.'stat': 'Success'.'fileSize': os.stat(listResult).st_size,
                'user': username
             }
Copy the code

The key code

Responsible for distributing requests:

    def conn_thread(self,connection) :
        while True:
            try:
                connection.settimeout(60)
                fileinfo_size = struct.calcsize('1024s')
                buf = connection.recv(fileinfo_size)
                if buf:  If you do not add this if, the first file transfer will be automatically moved to the next sentence
                    header_json = str(struct.unpack('1024s', buf)[0], encoding='utf-8').strip('\00')
                    #print(header_json)
                    header = json.loads(header_json)
                    Command = header['Command']

                    if Command == 'Upload':
                        fileName = header['fileName']
                        fileSize = header['fileSize']
                        time = header['time']
                        user = header['user']
                        filenewname = os.path.join('ServerRec/', fileName)
                        print('Upload: file new name is %s, filesize is %s' % (filenewname, fileSize))
                        recvd_size = 0  # Define the size of the received file
                        file = open(filenewname, 'wb')
                        print('start receiving... ')
                        while not recvd_size == fileSize:
                            if fileSize - recvd_size > 1024:
                                rdata = connection.recv(1024)
                                recvd_size += len(rdata)
                            else:
                                rdata = connection.recv(fileSize - recvd_size)
                                recvd_size = fileSize
                            file.write(rdata)
                        file.close()
                        print('receive done')

                        fileSize = float(fileSize)
                        if fileSize<1024.0:
                            fileSize = "%s bytes"% (int(fileSize))
                        elif fileSize/1024.0< =1024.0:
                            fileSize = "%.2f Kb"%(fileSize/1024.0)
                        elif fileSize/1024.0/1024.0< =1024.0:
                            fileSize = "%.2f Mb"%(fileSize/1024.0/1024.0)
                        else:
                            fileSize = "%.2f Gb"%(fileSize/1024.0/1024.0/1024.0)

                        uploadmsg = '{" name ":" % s ", "uploading" : "% s", "upload" : "% s", "size" : "% s"} \ n'%\
                                    (fileName,user,time,fileSize)
                        with open('result.txt'.'a', encoding='utf-8') as list:
                            list.write(uploadmsg)

                        uploadlog = '\n%s upload a file "%s" at %s' % \
                                        (user, fileName, time)
                        with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                            list.write(uploadlog)
                        #connection.close()

                    elif Command == 'Login':
                        # Query table data
                        username = header['user']
                        password = header['password']
                        time = header['time']
                        sql = "select * from user where username = '%s' and password = '%s'"%(username,password)
                        cursor.execute(sql)
                        data = cursor.fetchone()
                        if data:
                            listResult = './result.txt'
                            # Define file header information, including file name and file size
                            header = {
                                'Feedback': 'Login'.'stat': 'Success'.'fileSize': os.stat(listResult).st_size,
                                'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)

                            fo = open(listResult, 'rb')
                            while True:
                                filedata = fo.read(1024)
                                if not filedata:
                                    break
                                connection.send(filedata)
                            fo.close()
                            print('%s login successfully')

                            loginlog = '\n%s try to login at "%s" , Stat: Success ' % \
                                        (username, time)
                            with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                                list.write(loginlog)
                            #connection.close()
                        else:
                            header = {
                                'Feedback': 'Login'.'stat': 'Fail'.'fileSize': ' '.'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)
                            loginlog = '\n%s try to login at "%s" , Stat: Fail ' % \
                                       (username, time)
                            with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                                list.write(loginlog)

                    elif Command == 'Download':
                        # Query table data
                        username = header['user']
                        password = header['password']
                        time = header['time']
                        sql = "select * from user where username = '%s' and password = '%s'" % (username, password)
                        cursor.execute(sql)
                        data = cursor.fetchone()
                        filename = header['fileName']
                        if data:

                            filepath = os.path.join('./ServerREc/', filename)
                            # Define file header information, including file name and file size
                            header = {
                                'Feedback': 'Download'.'stat': 'Success'.'fileSize': os.stat(filepath).st_size,
                                'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)

                            fo = open(filepath, 'rb')
                            while True:
                                filedata = fo.read(1024)
                                if not filedata:
                                    break
                                connection.send(filedata)
                            fo.close()
                            print('send file over... ')
                            downloadlog = '\n%s download a file "%s" at %s' % \
                                          (username, filename, time)
                            with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                                list.write(downloadlog)
                            # connection.close()
                        else:
                            header = {
                                'Feedback': 'Download'.'stat': 'LoginFail'.'fileSize': ' '.'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)

                    elif Command == 'Update':
                        # Query table data
                        username = header['user']
                        password = header['password']
                        sql = "select * from user where username = '%s' and password = '%s'" % (username, password)
                        cursor.execute(sql)
                        data = cursor.fetchone()
                        if data:
                            listResult = './result.txt'
                            # Define file header information, including file name and file size
                            header = {
                                'Feedback': 'Update'.'stat': 'Success'.'fileSize': os.stat(listResult).st_size,
                                'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)

                            fo = open(listResult, 'rb')
                            while True:
                                filedata = fo.read(1024)
                                if not filedata:
                                    break
                                connection.send(filedata)
                            fo.close()
                            #print('send list over... ')
                            # connection.close()
                        else:
                            header = {
                                'Feedback': 'Login'.'stat': 'Fail'.'fileSize': ' '.'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)

                    elif Command == 'Register':
                        # Query table data
                        username = header['user']
                        password = header['password']
                        time = header['time']
                        sql = "select * from user where username = '%s'" % (username)
                        cursor.execute(sql)
                        data = cursor.fetchone()
                        if data:
                            # Define file header information, including file name and file size
                            header = {
                                'Feedback': 'Register'.'stat': 'Exist'.'fileSize': ' '.'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)
                            loginlog = '\n%s try to register at "%s" , Stat: Fail ' % \
                                       (username, time)
                            with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                                list.write(loginlog)
                        else:
                            sql = "insert into user values ('','%s','%s')"%(username,password)
                            cursor.execute(sql)
                            db.commit()
                            # Define file header information, including file name and file size
                            header = {
                                'Feedback': 'Register'.'stat': 'Success'.'fileSize': ' '.'user': username
                            }
                            header_hex = bytes(json.dumps(header).encode('utf-8'))
                            fhead = struct.pack('128s', header_hex)
                            connection.send(fhead)
                            loginlog = '\n%s try to register at "%s" , Stat: Success ' % \
                                       (username, time)
                            with open('Serverlog.txt'.'a', encoding='utf-8') as list:
                                list.write(loginlog)

            except socket.timeout:
                connection.close()
                break
            except ConnectionResetError:
                connection.close()
                break

Copy the code

rendering

The renderings of the file list are as follows:

The renderings of the uploaded files are as follows:

Have a problem

  • No GUI: Use the Python GUI Programming (Tkinter) toolkit
  • The openssl command does not generate a certificate
  • hostname match error:context.wrap_socket(self.sock, server_hostname='SERVER', server_side=False)Server_hostname must be the same as CN in server.conf

Experimental harvest and experience

This experiment let me learn a lot of things, let me have a deeper understanding of SSL encryption transmission and TCP protocol. In the past, I have seen the KNOWLEDGE of TCP protocol in books. This time, I verified the three times grip of TCP, ACK number and so on by capturing packets by myself, which made me more interested in the knowledge of computer network. In addition, in this experiment, I designed the protocol by myself, applied for SSL certificate by myself, implemented GUI and so on, which I had never tried before. After completing the experiment, I had a great sense of achievement and deepened my understanding of related fields. The main problems encountered in this experiment include GUI design, SSL certificate application, etc. Because beforehand is not very solution, so the development is more difficult to deal with, later after a lot of access to information, browsing the blog, learning other people’s code, finally solved the difficulties one by one, successfully realized all the functions. This experiment has exercised my ability to think and solve problems, and also increased my own experience, which is very fulfilling.