ShadowChat is a multi-client TCP chat and file sharing system implemented in Python. The project uses a classic Client/Server architecture, where multiple clients connect to one central server through TCP sockets.
The main idea of the project is to send both control messages and real data messages over the same TCP socket. This is implemented using a logical separation called Shadow Control Channel.
- Multi-client TCP server
- Thread-based client handling
- User registration and login
- Online user management
- End-to-end encrypted chat
- Offline encrypted messages
- Unread message notifications after login
- Chat history with a specific user
- File upload to server storage
- Local file sharing after receiver acceptance
- Chunk-based binary file transfer
- Persistent storage using JSON files
- Better terminal UI using
richandprompt-toolkit - Single TCP socket per client
ShadowChat has three main parts:
- Client
- Server
- Persistent Storage
The client is responsible for user commands, terminal UI, encryption/decryption, sending chat messages, uploading files, and receiving files.
The server is responsible for accepting clients, managing users, routing messages, storing encrypted chat history, coordinating file sharing, and managing file uploads.
Persistent storage is used to save users, uploaded file metadata, and encrypted chat history.
Client <---- one TCP socket ----> Server <---- read/write ----> Persistent Storage
Inside the same TCP socket:
- CTRL messages
- DATA messages
The project does not create a separate socket for control messages.
Instead, each message has a type field in its JSON header.
Example:
{
"type": "CTRL",
"command": "LOGIN"
}or:
{
"type": "DATA",
"command": "CHAT"
}This is the Shadow Control Channel mechanism.
Every message is sent using a custom frame format:
[4-byte header length][JSON header][optional binary body]
The first 4 bytes specify the size of the JSON header. The JSON header describes the message type, command, body length, and metadata. The optional binary body is used for file chunks.
Example frame header for login:
{
"type": "CTRL",
"command": "LOGIN",
"username": "esta",
"password": "123"
}Example frame header for encrypted chat:
{
"type": "DATA",
"command": "CHAT",
"target": "dudeA",
"ciphertext": "...",
"encrypted": true
}Example frame header for file chunk:
{
"type": "DATA",
"command": "FILE_CHUNK",
"filename": "report.pdf",
"chunk_number": 1,
"chunk_size": 1024
}ShadowChat/
│
├── server.py
├── client.py
│
├── utils/
│ ├── protocol.py
│ └── crypto_utils.py
│
├── server_core/
│ ├── user_manager.py
│ ├── chat_manager.py
│ ├── file_manager.py
│ └── share_manager.py
│
├── storage/
│ ├── users.json
│ ├── chats.json
│ ├── files.json
│ └── uploaded_files/
│
├── downloads/
│
└── images/
├── main_page.png
├── register_login.png
├── send_text.png
├── wrong_passphrase.png
├── offline_text.png
├── server_upload_start.png
├── server_upload.png
├── accept_reject.png
├── share_upload.png
├── architecture.png
└── protocol_msg.png
This project uses Python 3.
Install the required packages:
pip install rich prompt-toolkit cryptographyUsed libraries:
socketthreadingjsonstructosuuidrichprompt-toolkitcryptography
First, start the server:
python3 server.pyThen open one or more new terminals and start clients:
python3 client.pyEach client connects to the server using TCP.
Default host and port:
HOST = "127.0.0.1"
PORT = 5000REGISTER <username> <password>
Example:
REGISTER esta 123
LOGIN <username> <password>
Example:
LOGIN esta 123
LOGOUT
Logs out the current user but keeps the client program open.
LIST_USERS
Shows users that are currently online.
SET_KEY <username> <secret>
Example:
SET_KEY dudeA esta_dudeA_passphrase
Both users must set the same shared secret for their conversation.
Example:
esta: SET_KEY dudeA esta_dudeA_passphrase
dudeA: SET_KEY esta esta_dudeA_passphrase
CHAT <target_user> <message>
Example:
CHAT dudeA salam doost
Before sending a chat message, the user must set an encryption key for the target user using SET_KEY.
LIST_CHAT <username>
Example:
LIST_CHAT dudeA
The server returns encrypted messages, and the client decrypts them locally if the correct key is set.
UPLOAD <filepath>
Example:
UPLOAD report.pdf
The file is uploaded to the server in chunks and stored in:
storage/uploaded_files/
LIST_FILES
Shows files uploaded to server storage.
SHARE <filepath> <target_user>
Example:
SHARE report.pdf dudeA
The file is not sent immediately. First, the receiver gets a share request. If the receiver accepts, the upload starts.
ACCEPT_FILE <filename>
Example:
ACCEPT_FILE report.pdf
REJECT_FILE <filename>
Example:
REJECT_FILE report.pdf
HELP
Shows available commands.
EXIT
Closes the client connection.
Chat messages are encrypted on the client side before being sent to the server.
The server stores only ciphertext in:
storage/chats.json
The server does not know the original plaintext message.
The encryption key is derived from:
sorted usernames + shared passphrase
This means both users must use the same shared secret for the same conversation.
Example:
esta -> SET_KEY dudeA my_secret
dudeA -> SET_KEY esta my_secret
If the wrong key is used, the client cannot decrypt the message.
If a user sends a chat message to an offline user, the message is still saved by the server.
The server stores the encrypted message and marks it as unread.
When the receiver logs in, the server sends an unread notification.
The user can then run:
LIST_CHAT <username>
to view and decrypt the saved conversation.
File upload is done in chunks.
Default chunk size:
CHUNK_SIZE = 1024The client reads a file in binary mode and sends each chunk separately.
The server writes the chunks to a file and checks whether the final received size matches the expected file size.
There are two different file-related features:
UPLOAD <filepath>
This stores the file on the server.
SHARE <filepath> <target_user>
This sends a share request first. The file is only transferred if the receiver accepts the request.
Flow:
Sender -> SHARE file.txt receiver
Server -> sends share request to receiver
Receiver -> ACCEPT_FILE file.txt
Server -> tells sender to start upload
Sender -> sends file chunks
Receiver -> saves file in downloads/
Received shared files are saved in:
downloads/
The project uses only one TCP socket between each client and the server.
Instead of creating separate sockets for control and data messages, the protocol separates messages logically.
Control messages:
CTRL
Examples:
LOGIN
LOGOUT
LIST_USERS
LIST_FILES
SHARE_REQUEST
ACCEPT_FILE
REJECT_FILE
Data messages:
DATA
Examples:
CHAT
FILE_CHUNK
SHARE_FILE_CHUNK
INCOMING_FILE_CHUNK
This design keeps one physical TCP connection but still allows the program to distinguish different message types.
The server uses one thread per connected client.
When a new client connects, the server creates a new thread:
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address),
daemon=True,
)
client_thread.start()This allows several clients to use the system at the same time.
Locks are used to protect shared data such as:
- online users
- registered users
- uploaded files
- active file transfers
- chat history
Each online user also has a send_lock to prevent multiple threads from writing to the same socket at the same time.
The server stores data in JSON files.
storage/users.json
Stores registered users.
storage/chats.json
Stores encrypted chat messages.
storage/files.json
Stores metadata for uploaded files.
storage/uploaded_files/
Stores uploaded binary files.
The system handles common errors such as:
- duplicate username
- wrong password
- user already logged in
- command without authentication
- target user not found
- missing encryption key
- wrong encryption key
- invalid file path
- incomplete file transfer
- offline file share target
- unknown command
- unexpected client disconnect
Example server response:
ERROR authentication required
Another example:
ERROR target user not found
Terminal 1:
python3 server.pyTerminal 2:
python3 client.pyClient A:
REGISTER esta 123
LOGIN esta 123
Terminal 3:
python3 client.pyClient B:
REGISTER dudeA 123
LOGIN dudeA 123
Set encryption keys:
esta: SET_KEY dudeA shared_secret
dudeA: SET_KEY esta shared_secret
Send message:
esta: CHAT dudeA hello
View chat history:
dudeA: LIST_CHAT esta
Upload file:
esta: UPLOAD report.pdf
Share local file:
esta: SHARE report.pdf dudeA
dudeA: ACCEPT_FILE report.pdf
This project was developed for a computer networks course and focuses on:
- TCP socket programming
- client/server architecture
- message protocol design
- multi-client concurrency
- logical control/data separation
- file transfer using chunks
- encrypted chat messages
- offline message storage
The project is intentionally terminal-based to focus on socket programming and protocol design rather than GUI development.
This project is for educational use.










