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.