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.

Instructions

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.