Make sure that the directories keys and keys/server_keys are present in the directory with the python scripts. The keys/server_keys directory should contain the keys of the servers and keyserver.
Run fastchatdb_schema.py, then run keyserver.py, then load_balancer.py. Follow this by running mutiple_servers.py 5 times with 5 different command line arguments - 5001, 5002, 5003, 5004, 5005. Once this is done, all servers and background systems are up. Follow this by running client.py. Over there, enter your username. We allow only unique usernames in our system and hence if the username has been seen before the system detects you as an existing user and askes you to enter your password. Otherwise the system identifies you as a new user and requests you to create a password.
Once in the system, you are given a menu. Type out the digit corresponding to what you want to do. Respond to prompts based on your understanding.
If you're offline, the system will wait till you come back online to deliver messages to you. These messages will be recieved but not displayed when you login. The system will tell you the number of messages + images that arived while you were away. Messages that you choose to view will be displayed directly on the terminal. Images on the other hand will be downloaded into the images/ directory where your python scripts are running.
The terminal will tell you who sent the message.
Throughout the project, we have used selectors to manage the sockets. When we perform selectors.select
, we can input a list of sockets and retrieve a list of sockets that have buffered, incoming data. This can be done in a while True
loop to continuously check for sockets which are receiving data.
A try-except block protects servers from crashing under any errors if a client crashes.
The system ensures smooth flow of messages between clients. When a new client registers, they need to contact the load balancer and get the server number to which they have to connect. The client is connected to this server for the whole session.
We intend to make sure that the client is not able to connect with any other server, without having taken permission from the load balancer. For this, an token is generated by the load balancer and is sent to the connecting client as well as to the corresponding server. The client when connecting to the server, must send their username and this token in the first message to the server. The server ensures that the client has indeed been sent by the load balancer.
When a username is identified as a new user by the connected server, the user is asked to enter a password. This password is encrypted with PKCS1_OAEP using the server's public key, and is then sent to the server. The server, upon reception of the encrypted password, decrypts the encrypted password using its private key to retrieve the password entered by the user. The server then generates a random salt and computes a salted hash of the password. The hash function we have used is bcrypt. The server then stores the salt and the salted hash in the USERS table of the fastchatdb database. At the same time, the user generates an RSA key pair. It stores them in its local storage and registers its public key to the keyserver. The keyserver stores the username of the user along with its corresponding public key in the KEYSERVER table of the fastchatdb database. The user public key cannot be overwritten.
When a username is identified as an existing user by the connected server, the user is asked to enter their password. The server first creates an AES key and IV. We use AES under the CBC mode of operation. The AES key and IV are then encrypted with PKCS1_OAEP using the client's public key, and is then sent to the client. The client, upon reception of the encrypted key and IV, decrypts them using their private key to retrieve the AES key and IV sent by the server. The client then encrypts the password using AES under CBC mode of operation with the retrieved key and IV, and then encrypts the AES-encrypted password with PKCS1_OAEP using the server's public key and sends it to the serer. The server, upon encryption of the doubly encrypted password, decrypts it using its private key first, followed by the AES key and IV to retrieve the password sent by the user. It then retrieves the salt corresponding to the user from the database and computes a salted hash of the retrieved password. The server then verifies if this salted hash matches the salted hash stored in the database. Upon a successful match, the user is authenticated, else the user is informed that they have entered an incorrect password. A user is allowed to enter an incorrect password only thrice before the server disconnects from the client.
Whenever a system requires the public key of another system, it first checks in its key cache for possession of the required public key. If said public key is cached in the key cache, it is then used by the system. If said public key is absent from the key cache, then the system requests the key server to send the receiving system's public key. The key server, on reception of such a request, retrieves the required public key from the database and returns back the required public key to the requesting system. The keyserver also digitally signs the public key being sent with its private signing key. The requesting system can verify the authenticity of the public key received from the key server by verifying the keyserver's digital signature using the keyserver's public key (which comes pre-installed with the application).
Imagine a situation where a client, say, Alice requests the keyserver to send, say, Bob's public key. What if a malicious actor Mallory instead intercepts the data and returns her own public key to Alice spoofing as the keyserver. Then any messages sent by Alice to bob can be intercepted by Mallory and decrypted using his public key. We have solved this problem by making the keyserver digitally sign every public key it sends to a client. The private signing key resides solely with the keyserver and the public verification keys comes pre-installed with client. The client can verify the authenticity of the public key received from the keyserver by verifying the digital signature on the received public key.
Imagine a scenario where the server forwards a message from a client Alice to another client Bob. We do not want the server to be able to read Alice's message to Bob. So we encrypt the message using public key cryptography, specifically the PKCS1_OAEP standard. Each client generates a public-key/private-key pair during registration and registers its public key to the keyserver. The private key is kept only to the client. Whenever Alice wants to send a message to Bob, she encrypts her message with Bob's public key, which could only be decrypted using Bob's private key which only Bob possesses.
Imagine that the server were storing the raw passwords in its database. In the unfortunate event that the database being used by the servers gets leaked, the passwords of all the users would be compromised. To prevent against this, the server stores a hash of the password instead of the raw password itself. The server could hash the password received by a user during logging in and check if it matches the stored hash to verify the authenticity of the user.
Imagine an attacker having a large pre-computed database of the pre-images of hash results. If the database is leaked, the attacker can use this database to invert the hash and potentially retrieve the password of a user. Such a database is called a rainbow table. To prevent against this, we generate a random value called the salt and store the salted hash of the password. Using a salt necessitates the attacker to create a separate rainbow table for each distinct salt, thereby making it very computationally infeasible to retrieve the password.
We use the bcrypt hash function to compute the salted hashes. Unlike the SHA family of hash function which were designed to be computationally fast, the bcrypt hash function is intentionally designed to be a slow hash function. This makes creating rainbow tables against it infeasible. Moreover, the number of iterations needed to control the hash result of bcrypt can be increased, thereby making it such that the difficulty scales with improvements in hardware technology.
Imagine a situation where Alice logins in and sends her encrypted password to a server. What if a malicious entity Mallory intercepts her packets and then sends those back to the server at a later time? These packets would still be correctly decrypted to Alice's password at the server-side and Mallory would be authenticated as Alice. This kind of an attack is called a replay attack. We prevent replay attacks during logging in by adding a second layer of encryption. The server generates a fresh AES key and IV and sends it to the client using PKCS1_OAEP encryption. The client uses the given AES key and IV under CBC mode of encryption to encrypt their password and sends that to the server. The server then decrypts the encrypted password and continues on with verifying the equality of the salted hashes. As the AES key/IV generated by the server is different each time, the doubly encrypted password sent to the server differs each time and Mallory can no longer succeed by performing a replay attack.
The servers mantain a shared database where they mantain user data. This includes username which is the unique identifier, password hash, salt and the current server number to which that user is connected. The current server number gets updated at the start of each session of the user.
At this point connection is complete and the user is prompted with a menu to decide what they want to do.
The menu:
Enter Command No.:
- RECEIVE MESSAGES
- RECEIVE IMAGES
- SEND MESSAGE
- SEND IMAGE
- SEND GROUP MESSAGE
- SEND GROUP IMAGE
- CREATE GROUP
- MANAGE MY GROUPS
- QUIT
Let us first discuss the situation without groups:
When a user(sender) selects send message, they are prompted to enter username of receiver(this is unique) followed by message contents. We have implemented RSA end-to-end encryption using the PKCS1_OAEP standard, so the message is encrypted using the public key of the receiver. If the user does not have the required public key in local storage, it prompts the keyserver which sends it the public key. The keyserver digitally signs the public key being sent and the client verifies the digital signature before proceeding. The public key is then stored in local storage and the sender need not ping the keyserver everytime it wishes to chat with the same receiver. The message is then sent to the server with all the data regarding the type, purpose, sender, receiver, encrypted content etc.
In the case of images, a similar thing takes place. The client prompts the user to enter the path of the image and then retrieves the image from that location. In this case however ,we have to encrypt not only the image content, but also the image name. Both get encrypted and sent over the server. The path remains private and is not sent over, the image name field only contains the title and the extension of the image file.
The server recieves the message from the client. It observes that this is a one to one message and detects the name of the receiver. It then opens the database and checks which server number that user was connected to when that user last came online. If this server is that server, it views all clients connected to it and sends the message to the suitable user. If the client was last connected to some other server. it sends the whole message to that server to handle. When a message comes to a server through the server-server channel it assumes that the client for whom the message is meant is connected to that server. If the client is not found connected to the server which is given in the database, we assume that this is an offline client.
For offline clients, the message is stored in the message handling table. Currently it is stored for infinite time. For online clients, the message is sent to the appropriate client by its connected server.
When a client recieves a message from a server, it just stores it in the python script until the user chooses to read. Remember: if the user is online but just chooses to not read received messages then the system will delete the messages when the user exits the python script. Message storage at the server only takes place in case of offline users. Message decryption will also not take place until the user tries to read the message.
When the user chooses to read messages received, the system decrypts the message using the locally stored private key and displays it. This is along with the sender's name.
The client may choose to receive images in which case any received images will be downloaded and the sender's name will be printed onto the terminal.
When an existing client logs in and connects to the server, the server sends all of the stored messages that were meant for this client and stored because the client was offline. These messages are recieved by the client and it print out "You have n unread messages". The user may then choose to read these messages/images as usual.
Lets take a look at what is extra in the case of groups:
When a new group is to created, the user enters the group name and the members they wish to add to the group. These members must be entered as strings within quotes, comma separated. In case of one user, only enter "username", (the comma at the end is important)
The message for group creation is then sent to the server who creates that group in the database. The creator of the group is marked as the admin. Note, if the group name exists the client will not be allowed to create this group.
The admin at this point creates a group RSA key. The public key for this is sent to the keyserver to deal with as a regular key. We allow for same name of groups and users hence the keyserver table also stores a type to differentiate between the two.
The private key needs to get to all the group members but must not be accessible by anyone else. These primary keys are encrypted using the public key of each of the users and is sent over with 'class'='group invite'. These messages are received as regular messages by all the members, but when they read them, the group private key is downloaded into their local storage and they are alerted that they have joined a new group.
The user enters the group name and what they wish to do, i.e add/remove members. Then, these members must be entered as strings within quotes, comma separated. In case of one user, only enter "username", (the comma at the end is important)
The message for group updation is sent to the server. If the user who is requesting an updation is not the admin for the relevant group, then the request is declined and a suitable error message is sent back. If they are an admin, the group members list is updated in the GROUPS table for that group as intended by the user.
From here, adding and removing members follow different processes.
The admin retrieves the public and private keys for the group from local storage. This key is sent to the new users as group invites. The key is encrypted using the receivers public key. The new members receive the group private key as well as the notification of having been added to a group.
When we remove some members from the group, it is imprtant that they no longer have access to group messages. i.e they shouldn't be able to decrypt messages anymore. For this purpose, a new public-private key pair is generated by the admin. This new group key is sent to the keyserver to update it there and the new private keys are sent to all the remaining members of the group, encrypted using their individual public keys. The members receive this message as a group invite. When they read this message, the private key is overwritten in their local storage and so is the public key(To be implemented)
When a client sends a group message, the message arrives at the server with the group name, 'class'='group message' and message content, encrypted with the group private key. If the client is not part of the group, the server sends back an error saying that the client is not part of the group. The server handles this by breaking that group message into messages to individual receivers(all group members) and forwards it ahead as a regular 1 to 1 message. All group members, hence receive the message. Group message storage happens in the same table as one to one messages. The decryption for group messages happens using the group private key.
The directory structure of keys is as follows:
keys | |---cached_keys | | | |---[username] | | | |---[public keys of others] | |---my_keys | | | |---[username] | | | |---group_keys | | | | | |--[public/private keys of groups] | | | |---personal_keys | | | |---[public/private keys of user] | |---server_cached_keys | | | |---[public keys of users] | |---server_keys | |---KEYSERVER_PRIVKEY.pem | |---KEYSERVER_PUBKEY.pem | |---SERVERS_PRIVKEY.pem | |---SERVERS_PUBKEY.pem
The cached_keys directory stores all the cached public keys of other users.
The my_keys directory stores the user's public and private keys in the personal_keys subdirectory, and the user's group keys in the group_keys subdirectory.
The server_cached_keys directory stores all the cached public keys of the users at the server side.
The server_key directory stores all the pre-installed server/keyserver keys.
The directory structure of logs is as follows:
logs | |---clients_logs | | | |---[logs of clients] | |---servers_logs | |---keyserver.log | |---load_balancer.log | |---server5001.log | |---server5002.log | |---server5003.log | |---server5004.log | |---server5005.log
The clients_logs directory stores all the logs of the clients.
The servers_logs directory stores all the logs of the server, the keyserver, and the load balancer.
The directory structure of images is as follows:
images | |---[username] | |---[image download by user]
The images directory consists the usernames as its subdirectories, each of which stores the images downloaded by the corresponding user.