CS 1501
Data Structures and Algorithms
Programming Project 4
Purpose
The purpose of this programming assignment is to implement a primitive
digital signature system using the RSA cryptosystem. You will use either Victor Shoup's NTL library for C++ or the
BigInteger type for Java for your implementation.
1. For this assignment you will need to use either the NTL Library for C++ or the BigInteger class for Java.
a. If you are using NTL, you will need to download and install it, either onto your PC, onto your Unixs account, or onto a Zip disk. This is not a trivial procedure, so definitely get started at it right away. Carefully read the information shown on the NTL web site, and also read over the installation hints. If you have difficulty getting the library installed, see your TA for help. Once you have installed the library, read over the details of the ZZ class in files ZZ.h and ZZ.cpp. Some more NTL help is available through the TAs. See the following links for information: http://www2.cs.pitt.edu/~tomas/cs1501/index.html and http://www.cs.pitt.edu/~flying/CS_1501/Recitation4.htm .
b. If you are using BigInteger, you need to thoroughly familiarize yourself with the class and its methods.
2. Play around with the RSA demo at http://www.cs.pitt.edu/~kirk/cs1501/notes/rsademo/index.html to familiarize yourself with and make sure you understand the RSA cryptosystem. We will adopt the notation used in this demo for the rest of these instructions (note: the notation used in the text is slightly different).
3. Write a program to implement the key generation part of the RSA system.
· Pick P and Q to be random primes of some specified length using the function RandomPrime in ZZ for C++, or the appropriate BigInteger constructor for Java.
·
Pick E to be a random prime between 1 and PHI=(P-1)(Q-1),
exclusive, such that GCD(E, PHI)=1 (E must not divide PHI
evenly). E should be similar in (bit)
length to P and Q, but does not have to be the same length.
·
Solve the following equation
(E)(D) mod
PHI = 1
for D such that 1 < D < PHI.
We discussed in lecture how to do this using the XGCD function, and that function is available in NTL. However, both NTL and the BigInteger class in Java have methods to calculate the "inverse mod" that can also give us the correct answer. To put it into mathematical terms, since
(E)(D) mod
PHI = 1
we can solve for D and say that
D = E-1
mod PHI
This only works if E and PHI are relatively prime, which we know them to be. In NTL, the function used for this purpose is
inline void
InvMod(ZZ& x, const ZZ& a, const ZZ& n)
and it would be called by passing D into x (the return value), E into a and PHI into n.
In BigInteger the method used for this purpose is
public
BigInteger modInverse(BigInteger m)
and it would be called from object E, passing PHI as the argument, with the return value assigned to D.
When you execute this program, it should generate new public and private keys for your RSA cryptosystem, where P, Q and E as defined above are all 524-bit integers. Also necessary for both encryption and decryption is the value (P)(Q) = N (which should be ~1048 bits). The values E and N should be saved to a file called "pubkey.txt" and the values D and N should be saved to a file "privkey.txt".
4. Write a second program that uses your RSA algorithm for a primitive
"digital signature." As discussed in
lecture, a digital signature is a way for the receiver to make sure a message
has not been forged or tampered with (note: prevention of forging also requires
third party authentication, but we will not be concerned with that here). Recall that it is (usually) too
time-consuming for the sender to "decrypt" the entire message and for
the receiver to then "encrypt" it, otherwise that technique would
work well. Instead the sender will
merely append a "signature" on the end of the message, such that any
tampering with the message will be evident.
The basic idea is that the sender processes
the message together with a date/time stamp to form the
"signature". The sender then
encrypts the signature using his/her private key and appends it to the end of
the message. Thus, the message itself
is unencrypted plaintext (however, the sender could encrypt the entire signed
message with the receiver's public key if he/she wanted it to be private). The receiver then decrypts the signature
using the sender's public key, processes the message in the same way the sender
did, and verifies that the signatures "match". The basis for the success of this technique
is the ability to process the message in a way that is fast yet that can detect
(almost) any alteration to the message.
Production environments use sophisticated "hash" algorithms
such as Rivest's MD5 algorithm.
However, in this assignment we will keep it much simpler, using a very
simple (and less secure) hashing algorithm.
Details:
Your goal is for the receiver of a message to
be able to discover any tampering with the message sent by the sender, and,
assuming that the sender's private key has not be compromised, to verify that
the sender is indeed the one who sent the message.
Sender:
The sender of the message will create the message
itself in plaintext. This can be any
text message stored in any file on the sender's computer. Before "sending" the message, the
sender will do the following:
1)
Store his/her initials
and the date and time that the message was "sent" in a temporary array
of char (C++) or array of byte (Java) in the following format:: IIYYYYMMDDHHMMSS. This
translates to two characters for the initials, 4 digits for the year, 2 digits
for the month, 2 digits for the day, 2 digits for the hour (24 hour clock), 2 digits
for the minute and 2 digits for the second.
No blanks should separate the items, so the array should be exactly 16
characters in length.
2)
Process the plaintext
file in the following way:
a)
Create a random very
long integer (VLI for short) of length 128 bits using the RandomLen function in
ZZ or the appropriate constructor for BigInteger. Call this the modulus.
Store a string representation of this integer in another temporary
string using the BytesFromZZ function.
The array should be exactly 16 characters in length.
b)
Create a VLI
corresponding to the file by using Horner's method on the characters in the
file modulo modulus. Use as the
multiplier 128 (the size of the regular ASCII set). For example, the string "ABCD" would be converted in
the following fashion (note that this is pseudocode only, in reality a loop
would be used):
VLI answer = 0;
answer = (answer * 128 + ASCII('A')) % modulus;
answer = (answer * 128 + ASCII('B')) % modulus;
answer = (answer * 128 + ASCII('C')) % modulus;
answer = (answer * 128 + ASCII('D')) % modulus;
c)
The final answer should
now be a VLI of up to 128 bits (it could be smaller due to the mod). Store a string representation of this VLI in
another temporary array using the BytesFromZZ function in C++ or the toByteArray
method in Java, and then pad the array on the left with null characters if
necessary so that the final array is exactly 16 characters in length.
3)
Append the three arrays
into one signature array by copying them index by index. See handout ZZtoZZ.cpp
or BigtoBig.java for help on how to do this.
4)
You now have in your signature
array the digital signature string ready to be "decrypted". Using the function ZZFromBytes in C++ or the
sign/magnitude constructor for BigInteger in Java, convert this array into a
single VLI. Next "decrypt"
the VLI using the private key that you generated in Part 1 of the assignment
using the RSA algorithm. To make verification
easier, prepend this value to your plaintext file (i.e. put it in the front of
the file, separated by a blank). Note that the "decrypted" VLI can be
written to your file using the << operator for ZZ or the toString()
method for BigInteger, which produces a long string of digits. Your message is
now ready to "send". However,
for the purposes of this assignment you will simply leave it in the file.
Receiver
Upon "receiving" the message the
receiver first reads the VLI value, then "encrypts" it using the
public key generated in Part 1 of the assignment using the RSA algorithm. The resulting VLI is then converted back to
an array of characters (or bytes) using the BytesFromZZ function or
toByteArray() method, with the result being the signature string that you will
now use to check to see if the message has been tampered with. If the signature string itself is gibberish,
the signature was tampered with and the file can be rejected. If the signature can be parsed, continue as
specified below.
1)
First isolate and save
as a VLI the modulus value. Process the
plaintext in the exact manner as in 2b) above, using the modulus value. Isolate and save as a VLI the hash part of
the signature. Compare the value you
just calculated with that from the signature.
If they match, the message is authentic. If they do not match, the message has been tampered with, and you
should reject it.
2)
See ZZtoZZ.cpp
or BigtoBig.java for more help on this topic.
Main Program for Second Part
Have a menu-driven loop that allows the user
to "send" or "receive" files. "Send" will simply prompt for an input filename and an
output filename and process the file as specified above. "Receive" will also prompt for a
filename (which had previously been output from a "Send") and process
it as above, outputting whether or not the file has been tampered with.
To allow for easier grading by the TA,
"capture" an interactive session in which you test your program. If you are using Windows, this may involve
cutting and pasting text from the console to a file. If you are using Unixs, you can run a script. In this file you should first show your key
generation by running your first program.
Then print out a short test file, "Send" it, and print out the
signed file. Then edit the signed file,
changing a few characters and print it out again. Finally, "receive"
it (and through the receiver indicate that it has been corrupted). Whichever system you use, call your
interactive session file "demo.txt".
Notes:
·
The signature that you
"decrypt" will be fairly long – exactly 48 characters. This will convert into a fairly large VLI –
384 bits. However, since your N value
is over 1000 bits long, you have no worry that this VLI will exceed N.
·
Be sure
"demo.txt" is submitted as specified above along with all other
pertinent source and data files. If
not, you may lose credit!
·
As with previous
assignments, make sure you submit the appropriate .exe or .jar file so that the
TA can execute your program without compiling it.
·
Don't forget to complete
and submit your Assignment Information Sheet.
·
If you want to try some
extra credit, try coming up with a better signing algorithm. However, you must justify that your
algorithm is superior to the one in the assignment.