Client-server application that allows a client process to send encrypted data to a server process.
The server decrypts the received data and writes it to a file. Communication between client and server takes place via sockets.
The client application takes the following input parameters:
- the name of a file (text or binary; for testing, a text file is recommended)
- a 64-bit key
K(representable as anunsigned long long int) - an integer
pthat defines the desired degree of parallelism - the IP address and port number of the server
The client reads the input file, extracts its contents as a byte sequence F, and determines its length L. It splits the content into n blocks Bi of 64 bits each:
F = B1B2 … Bn
If the last block Bn is shorter than 64 bits, it is padded with '\0's.
Each block Bi is encrypted by performing a bitwise XOR with the key K, resulting in an encrypted block Ci = Bi XOR K.
The complete encrypted message C is the concatenation of all blocks:
C = C1C2…Cn
The client sends this encrypted message to the server, along with the original file length L and the key K.
Message format:
[C1C2…Cn, L, K]
After sending the data, the client waits for an acknowledgment message from the server before terminating.
- Uses up to
pthreads to perform parallel encryption - Handles server-side connection closures
- Ignores interruptions from signals such as
SIGINT,SIGALRM,SIGUSR1,SIGUSR2, andSIGTERMduring encryption and sending - Avoids race conditions when using shared data structures between threads
The server application accepts the following input parameters:
- an integer
pfor the desired parallelism level during decryption - a string
sto use as a prefix for output filenames - an integer
lrepresenting the maximum number of concurrent connections that can be handled by the server
The server listens on a specific port and accepts connection requests from multiple client processes.
Once a client connects, the server receives an encrypted message containing:
- the ciphertext
C - the original length
L - the encryption key
K
The ciphertext C is split into n 64-bit blocks. Using up to p threads (where p ≤ n), the server decrypts each block via XOR:
Bi = Ci XOR K
Decrypted blocks are stored in a shared buffer. Once the full message is reconstructed, it is saved to a file using the prefix s for the filename.
The server sends an acknowledgment to the client after decryption is complete, and before writing to disk. It then closes the connection.
- Threads performing decryption or file I/O are immune to signal interruptions (
SIGINT,SIGALRM,SIGUSR1,SIGUSR2,SIGTERM) - Decrypted data is stored in a dynamically allocated list of blocks
- Synchronization with semaphores ensures the absence of race conditions during shared buffer access
-
Both client and server accept parameters via command-line arguments or environment variables
-
Both components include error handling for:
- Invalid input
- System call failures
- I/O errors
Plaintext input:
"il mio nome e’ legenda"
Binary representation:
01001001 01101100 00100000 01101101 01101001 01101111
00100000 01101110 01101111 01101101 01100101 00100000
01100101 00100111 00100000 01101100 01100101 01100111
01100101 01101110 01100100 01100001
Length:
L = 22 bytes = 176 bits
Key:
K = "emiliano"
Binary representation:
01100101 01101101 01101001 01101100 01101001 01100001 01101110 01101111
Block division:
B1 = 01001001 01101100 00100000 01101101 01101001 01101111 00100000 01101110
B2 = 01101111 01101101 01100101 00100000 01100101 00100111 00100000 01101100
B3 = 01100101 01100111 01100101 01101110 01100100 01100001 00000000 00000000
Encrypted blocks (XOR with key):
C1 = B1 XOR K =
01001001 01101100 00100000 01101101 01101001 01101111 00100000 01101110
XOR
01100101 01101101 01101001 01101100 01101001 01100001 01101110 01101111
=
00101100 00000001 01001001 00000001 00000000 00001110 01001110 00000001
This project is an example of low-level system programming with multi-threading and network socket communication, focusing on concurrency, synchronization, and signal safety.