Summary
This project involves the design and development of a small suite of networked C programs
to support secure message encryption and decryption using a one-time pad-style
approach. The goal is to simulate a secure communication environment where client
applications interact with encryption and decryption services via socket-based inter-
process communication (IPC).
The system consists of five components:
• Two server applications (one for encryption, one for decryption)
• Two corresponding client applications
• A standalone utility for key generation
All components are designed for command-line usage, integrating standard Unix features
such as I/O redirection, background job control, and multi-processing. The implementation
enforces proper use of Unix socket APIs and includes a build automation script for
compilation.
Specifications:
enc_server
This program is the encryption server and will run in the background as a daemon.
• Its function is to perform the actual encoding, as described above in the Wikipedia
quote.
• This program will listen on a particular port/socket, assigned when it is first ran (see
syntax below).
• Upon execution, enc_server must output an error if it cannot be run due to a
network error, such as the ports being unavailable.
• When a connection is made, enc_server must call accept() to generate the socket
used for actual communication, and then use a separate process to handle the rest
of the servicing for this client connection (see below), which will occur on the newly
accepted socket.
• This child process of enc_server must first check to make sure it is communicating
with enc_client (see enc_client, below).
• After verifying that the connection to enc_server is coming from enc_client, then this
child receives plaintext and a key from enc_client via the connected socket.
• The enc_server child will then write back the ciphertext to the enc_client process
that it is connected to via the same connected socket.
• Note that the key passed in must be at least as big as the plaintext.
The enc_server must support up to five concurrent socket connections running at the
same time; this is different than the number of client connection requests that could
queue up on your listening socket (which is specified in the second parameter of
the listen call). Again, only in the child server process will the actual encryption take place,
and the ciphertext be written back: the original server daemon process continues listening
for new connections, not encrypting data.
The encryption server must be capable of handling five concurrent encryption requests
at the same time. You may implement this in one of two ways:
• Dynamically create a new child process using fork() each time a new client
connection is accepted.
• Alternatively, pre-create a pool of five worker processes when the server starts,
which will handle incoming client requests as they arrive.
Either method is acceptable, as long as the final system supports up to five simultaneous
client connections and can perform five encryptions in parallel without blocking or
queuing additional connections unnecessarily. Efficient handling of multiple clients is a key
part of this requirement.
enc_server listening_port
The encryption server (enc_server) must accept a listening port number as a command-
line argument. This port determines which port the server listens on for incoming
connections. The program should be designed to accept any valid port number, and it will
typically be launched to run in the background using a command like the following:
bash
CopyEdit
$ enc_server 57171 &
(Note: 57171 is just an example — your implementation must work with any available port
provided at runtime.)
$ enc_server 57171 &
In all error situations, this program must output errors to stderr as appropriate but should
not crash or otherwise exit, unless the errors happen when the program is starting up (i.e.
are part of the networking start up protocols like bind). Once running, enc_server should
recognize any bad input it receives, report an error to stderr, and continue to run. Generally
speaking, though, this server shouldn’t receive bad input, since that should be discovered
and handled in the client first. All error text must be output to stderr.
This program, and the other 3 network programs, should use localhost as the target IP
address/host. This makes them use the actual computer they all share as the target for the
networking connections.
enc_client
This program connects to enc_server, and asks it to perform a one-time pad style
encryption as detailed above. By itself, enc_client doesn’t do the encryption
- enc_server does. The syntax of enc_client is as follows:
enc_client plaintext key port
In this syntax, plaintext is the name of a file in the current directory that contains the
plaintext you wish to encrypt. Similarly, key contains the encryption key you wish to use to
encrypt the text. Finally, port is the port that enc_client should attempt to connect
to enc_server on. When enc_client receives the ciphertext back from enc_server, it should
output it to stdout. Thus, enc_client can be launched in any of the following methods, and
should send its output appropriately:
$ enc_client myplaintext mykey 57171
$ enc_client myplaintext mykey 57171 > myciphertext
$ enc_client myplaintext mykey 57171 > myciphertext &
If enc_client receives key or plaintext files with any bad characters in them, or the key file is
shorter than the plaintext, then it should terminate, send appropriate error text to stderr,
and set the exit value to 1.
enc_client should not be able to connect to dec_server, even if it tries to connect on the
correct port - the programs need to reject each other. If this happens, enc_client should
report the rejection to stderr and then terminate itself. In more detail: if enc_client cannot
connect to the enc_server server, for any reason (including that it has accidentally tried to
connect to the dec_server server), it should report this error to stderr with the attempted
port, and set the exit value to 2. Otherwise, upon successfully running and
terminating, enc_client should set the exit value to 0.
Again, any and all error text must be output to stderr (not into the plaintext or ciphertext
files).
dec_server
This program performs exactly like enc_server, in syntax and usage. In this case,
however, dec_server will decrypt ciphertext it is given, using the passed-in ciphertext and
key. Thus, it returns plaintext again to dec_client.
dec_client
Similarly, this program will connect to dec_server and will ask it to decrypt ciphertext using
a passed-in ciphertext and key, and otherwise performs exactly like enc_client, and must
be runnable in the same three ways. dec_client should NOT be able to connect
to enc_server, even if it tries to connect on the correct port - you’ll need to have the
programs reject each other, as described in enc_client.
keygen
This program creates a key file of specified length. The characters in the file generated will
be any of the 27 allowed characters, generated using the standard Unix randomization
methods
Note: Do not create spaces every five characters.
Note: you do not have to do any fancy random number generation/ cryptographically not
necessary for random number generation. rand() is just fine. The last character keygen
outputs should be a newline. Any error text must be output to stderr.
You will implement a standalone utility called keygen. This program must accept a single
command-line argument, keylength, which specifies the desired length of the key in
characters.
The program should generate a random key consisting of the 27 allowed characters (A–Z
and space), and output the result to stdout. The output must end with a newline
character, so a key of 256 characters will result in a file with 257 characters in total.
Example usage:
bash
$ keygen 256 > mykey
In this example, the key is saved to a file named mykey.
Compilation Script
Code must be compiled to create 5 executable programs. The programs must be
named enc_server, enc_client, dec_server, dec_client and keygen.
If you have only 5 C files, each with the same name as the executable program it will
produce, you can use the following shell script as your compileall script
#!/bin/bash
gcc --std=gnu99 -o enc_server enc_server.c
gcc --std=gnu99 -o enc_client enc_client.c
gcc --std=gnu99 -o dec_server dec_server.c
gcc --std=gnu99 -o dec_client dec_client.c
gcc --std=gnu99 -o keygen keygen.c
If the names of your C files are different and/or you have additional C files, then please
provide a bash shell script called compileall that creates 5 executable programs with the
names specified above. These 5 programs must be created in the same directory
as compileall.
Alternatively, you may You are also allowed to use a Makefile instead of a compileall script.
However, running the Makefile must create the 5 executable files with the names specified
above and these files must be created in the same directory as your Makefile.
Getting Started :
Begin by implementing the keygen program first, as it is straightforward and a good
introduction to the project. Afterward, use the provided sample network programs (client.c
and server.c) as references or starting points to develop enc_client and enc_server.
Once your encryption client and server are working correctly, you can duplicate and adapt
those programs to build dec_client and dec_server for decryption functionality.
Reference:
http://en.wikipedia.org/wiki/One-time_pad
Example from Wikipedia article.
Suppose Alice wishes to send the message “HELLO” to Bob. Assume two pads of paper
containing identical random sequences of letters were somehow previously produced and
securely issued to both. Alice chooses the appropriate unused page from the pad. The way
to do this is normally arranged for in advance, as for instance “use the 12th sheet on 1
May”, or “use the next available sheet for the next message”.
The material on the selected sheet is the key for this message. Each letter from the pad will
be combined in a predetermined way with one letter of the message. (It is common, but not
required, to assign each letter a numerical value, e.g., “A” is 0, “B” is 1, and so on.)
In this example, the technique is to combine the key and the message using modular
addition. The numerical values of corresponding message and key letters are added
together, modulo 26. So, if key material begins with “XMCKL” and the message is “HELLO”,
then the coding would be done as follows:
H E L L O message
7 (H) 4 (E) 11 (L) 11 (L) 14 (O) message
+ 23 (X) 12 (M) 2 (C) 10 (K) 11 (L) key
= 30 16 13 21 25 message + key
= 4 (E) 16 (Q) 13 (N) 21 (V) 25 (Z) (message + key) mod 26
E Q N V Z → ciphertext
If a number is larger than 25, then the remainder after subtraction of 26 is taken in modular
arithmetic fashion. This simply means that if the computations “go past” Z, the sequence
starts again at A.
The ciphertext to be sent to Bob is thus “EQNVZ”. Bob uses the matching key page and the
same process, but in reverse, to obtain the plaintext. Here the key is subtracted from the
ciphertext, again using modular arithmetic:
E Q N V Z ciphertext
4 (E) 16 (Q) 13 (N) 21 (V) 25 (Z) ciphertext
- 23 (X) 12 (M) 2 (C) 10 (K) 11 (L) key
= -19 4 11 11 14 ciphertext – key
= 7 (H) 4 (E) 11 (L) 11 (L) 14 (O) ciphertext – key (mod 26)
H E L L O → message
Similar to the above, if a number is negative, then 26 is added to make the number zero or
higher.
Thus Bob recovers Alice’s plaintext, the message “HELLO”. Both Alice and Bob destroy the
key sheet immediately after use, thus preventing reuse and an attack against the cipher.
Example :
Example of usage of code from the command line: