/** * RSA Cipher Program. Encrypts or decrypts, reads XML key file. * * TODO: Disable debugging output * * Class: CS 340, Fall 2005 * System: jdk-1.5.0.4, Windows XP * @author Michael Leonhard * @version 8 Sep 2005 */ import java.util.*; import java.io.*; import java.math.BigInteger; import javax.xml.parsers.*; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.w3c.dom.Node; public class RsaCipher { // fields private String M_inFilename = null; private String M_outFilename = null; private String M_keyFilename = null; private int M_n = -1; private int M_e = -1; private int M_d = -1; private int M_operation = -1; static int OPERATION_ENCRYPT = 0; static int OPERATION_DECRYPT = 1; /** * Constructor for objects of class RsaCipher */ public RsaCipher() { } /** * Prints debugging messages to the console */ static public void printDebug( String message ) { //System.out.println( message ); } /** * Prompt the user and return a string, returning null instead of blank string */ private String getStringFromUser(String Prompt, BufferedReader consoleReader) throws IOException { // prompt the user and read the first number System.out.print(Prompt); String typedString = consoleReader.readLine().trim(); // nothing was entered if( typedString.length() == 0 ) return null; return typedString; } /** * Gets required data from the user */ private void getDataFromUser() throws IOException { // create a console reader object BufferedReader consoleReader = new BufferedReader( new InputStreamReader( System.in ) ); // query user for required parameters, tailor prompts to desired operation if(M_operation == OPERATION_ENCRYPT) { while(M_keyFilename == null) M_keyFilename = getStringFromUser("Please enter name of public key file: ", consoleReader); while(M_inFilename == null) M_inFilename = getStringFromUser("Please enter name of plain text file to encrypt: ", consoleReader); while(M_outFilename == null) M_outFilename = getStringFromUser("Please enter name of ciphertext file to write: ", consoleReader); } else if(M_operation == OPERATION_DECRYPT) { while(M_keyFilename == null) M_keyFilename = getStringFromUser("Please enter name of private key file: ", consoleReader); while(M_inFilename == null) M_inFilename = getStringFromUser("Please enter name of ciphertext file to decrypt: ", consoleReader); while(M_outFilename == null) M_outFilename = getStringFromUser("Please enter name of plaintext file to write: ", consoleReader); } // program should never reach here else throw new Error("M_operation is invalid"); } /** * Processes the command line arguments */ private void processCommandLine( String[] argv ) throws TException { // loop through command line arguments int argc = argv.length; printDebug( "Number of parameters is " + Integer.toString(argc) ); for( int i = 0; i < argc; i++ ) { printDebug( "PARM: " + argv[i] ); if( argv[i].equals("-d") ) { // -e or -d must not have been specified before if( M_operation != -1 ) throw new TException( "Malformed Commandline: only one -e or -d is allowed" ); M_operation = OPERATION_DECRYPT; } else if( argv[i].equals("-e") ) { // -e or -d must not have been specified before if( M_operation != -1 ) throw new TException( "Malformed Commandline: only one -e or -d is allowed" ); M_operation = OPERATION_ENCRYPT; } else if( argv[i].equals("-f") ) { // -f option may not be specified twice if( M_inFilename != null ) throw new TException( "Malformed Commandline: the -f option may be specified only once" ); // there must be one more parameter if( i + 1 >= argc ) throw new TException( "Malformed Commandline: please specify a filename after the -f option" ); printDebug( "PARM: " + argv[i+1] ); // store filename M_inFilename = argv[i+1]; // one extra parameter was processed i = i + 1; } else if( argv[i].equals("-o") ) { // -o option may not be specified twice if( M_outFilename != null ) throw new TException( "Malformed Commandline: the -o option may be specified only once" ); // there must be one more parameter if( i + 1 >= argc ) throw new TException( "Malformed Commandline: please specify a filename after the -o option" ); printDebug( "PARM: " + argv[i+1] ); // store filename M_outFilename = argv[i+1]; // one extra parameter was processed i = i + 1; } else if( argv[i].equals("-k") ) { // -k option may not be specified twice if( M_keyFilename != null ) throw new TException( "Malformed Commandline: the -k option may be specified only once" ); // there must be one more parameter if( i + 1 >= argc ) throw new TException( "Malformed Commandline: please specify a filename after the -k option" ); printDebug( "PARM: " + argv[i+1] ); // store filename M_keyFilename = argv[i+1]; // one extra parameter was processed i = i + 1; } // unknown parameter else throw new TException( "Malformed Commandline: unexpected parameter: \"" + argv[i] + "\"" ); } // -e or -d must be specified if( M_operation == -1 ) throw new TException( "Malformed Commandline: -e or -d must be specified" ); } /** * Extract an integer from the supplied tag */ private int getIntTag(String tagName, Document document) throws TException { NodeList tagList = document.getElementsByTagName(tagName); if( tagList.getLength() != 1 ) throw new TException("Keyfile format error: "+tagName+" tag not found"); // the ... tag Node tagNode = tagList.item(0); if( tagNode.getNodeType() != Node.ELEMENT_NODE )throw new TException("Keyfile format error: "+tagName+" tag is wrong type"); // the items contained inside it NodeList childList = tagNode.getChildNodes(); if( childList.getLength() != 1 ) throw new TException("Keyfile format error: "+tagName+" tag does not contain exactly one item"); // the first and only item must be type text Node textNode = childList.item(0); if( textNode.getNodeType() != Node.TEXT_NODE ) throw new TException("Keyfile format error: expected text inside "+tagName+" tag"); String valueString = textNode.getNodeValue().trim(); printDebug(tagName + " is " + valueString); try { // convert string to integer int value = Integer.valueOf(valueString).intValue(); // must be positive and nonzero if( value < 1 ) throw new TException("Keyfile format error: "+tagName+" must be >0"); // done! return value; } catch(NumberFormatException e) { throw new TException( "Keyfile format error: expected integer in "+tagName+" but found \"" + valueString + "\"" ); } } /** * Read the user-specified key file into a JDOM tree */ private void readKey() throws TException, javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException { try { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = builderFactory.newDocumentBuilder(); Document document = docBuilder.parse(new File(M_keyFilename)); Element rootElement = document.getDocumentElement(); rootElement.normalize(); printDebug("Keyfile root tag is " + rootElement.getTagName()); // root tag must be rsakey if( !rootElement.getTagName().equals("rsakey") ) throw new TException("Keyfile format error: root tag must be rsakey"); // get necessary values M_n = getIntTag("nvalue", document); if(M_operation == OPERATION_ENCRYPT ) M_e = getIntTag("evalue", document); if(M_operation == OPERATION_DECRYPT ) M_d = getIntTag("dvalue", document); } catch (java.io.IOException e) { throw new TException( "Unable to read key file \""+M_keyFilename+"\".\n"+ "Technical error information: " + e.toString() ); } } /** * Reads inFile one byte at a time, writing one encrypted base10 integer to outFile per line */ private void doEncryption(FileReader inFile, FileWriter outFile) throws TException, IOException { try { BigInteger e = new BigInteger(Integer.toString(M_e)); BigInteger n = new BigInteger(Integer.toString(M_n)); // loop for each character for( int mChar = inFile.read(); mChar != -1; mChar = inFile.read() ) { BigInteger m = new BigInteger(Integer.toString(mChar)); String mPrimeString = m.modPow(e, n).toString(); outFile.write(mPrimeString + "\n"); printDebug(Integer.toString(mChar)+" -> " + mPrimeString); } } catch(ArithmeticException e){ throw new TException("Error during encryption: " + e.toString()); } catch(NumberFormatException e) { throw new TException("Error during encryption: " + e.toString()); } } /** * Reads inFile one line at a time, converts line contents to integer, * decrypts integer and writes single character to outFile */ private void doDecryption(FileReader inFile, FileWriter outFile) throws TException, IOException { BufferedReader inBR = new BufferedReader( inFile ); try { BigInteger d = new BigInteger(Integer.toString(M_d)); BigInteger n = new BigInteger(Integer.toString(M_n)); // loop for each line for( String line = inBR.readLine(); line != null; line = inBR.readLine() ) { BigInteger m = new BigInteger(line); int mPrime = m.modPow(d, n).intValue(); outFile.write(mPrime); printDebug(line+" -> " + Integer.toString(mPrime)); } } catch(ArithmeticException e){ throw new TException("Error during decryption: " + e.toString()); } catch(NumberFormatException e) { throw new TException("Error during decryption: " + e.toString()); } } /** * Opens the in and out files, calls the proper method to perform the operation, closes files */ private void doOperation() throws IOException, TException { FileReader inFile; FileWriter outFile; // open input file try { inFile = new FileReader(M_inFilename); } catch (FileNotFoundException e) { throw new TException("Unable to read file: " + e.toString()); } // open output file try { outFile = new FileWriter(M_outFilename); } catch (IOException e) { throw new TException("Unable to write to file: " + e.toString()); } // do the operation if(M_operation == OPERATION_ENCRYPT) doEncryption(inFile, outFile); else doDecryption(inFile, outFile); // close the files inFile.close(); outFile.close(); } /** * Call methods to the file and key, encrypt or decrypt, write the result */ public void doJob( String[] argv ) { // print program information System.out.println ( "CS 340 Project 1 program 2 (RsaCipher).\n" + "Michael Leonhard, 2005/09/08\n" + "Java 1.5.0.4 on Windows XP\n" ); try { processCommandLine( argv ); printDebug("M_inFilename = " + M_inFilename); printDebug("M_outFilename = " + M_outFilename); printDebug("M_keyFilename = " + M_keyFilename); if(M_operation == OPERATION_ENCRYPT) printDebug("M_operation = OPERATION_ENCRYPT"); else if(M_operation == OPERATION_DECRYPT) printDebug("M_operation = OPERATION_ENCRYPT"); else printDebug("M_operation = -1"); getDataFromUser(); printDebug("M_inFilename = " + M_inFilename); printDebug("M_outFilename = " + M_outFilename); printDebug("M_keyFilename = " + M_keyFilename); readKey(); printDebug("M_n = " + Integer.toString(M_n)); printDebug("M_e = " + Integer.toString(M_e)); printDebug("M_d = " + Integer.toString(M_d)); doOperation(); } catch( TException e ) { e.print(); return; } catch (IOException e) { System.out.println("Problem reading or writing files:"); System.out.println(e.toString()); return; } catch (javax.xml.parsers.ParserConfigurationException e) { System.out.println("Java XML Parser is not available."); System.out.println(e.toString()); return; } catch (org.xml.sax.SAXException e) { System.out.println("Unable to load keyfile as XML."); System.out.println(e.toString()); return; } } /** * Print the command line usage. */ public static void printUsage() { System.out.println( "Command Line Usage: rsacipher -d|-e [-f ] [-o outfile] [-k keyfile]" ); System.out.println( " -d decrypt the file, keyfile contains dvalue"); System.out.println( " -e encrypt the file, keyfile contains evalue"); System.out.println( " -f the file to en/decrypt, user prompted if -f not present"); System.out.println( " -k keyfile, user prompted if -k not supplied"); System.out.println( " -o file to write result, user prompted if not present"); System.out.println( "Example: rsacipher -e -f hello.txt -o cipher.txt -k pubkey.xml" ); System.out.println( "Example: rsacipher -d -f cipher.txt -o hello.txt -k privkey.xml" ); } /** * This method is called when the class is loaded from the command line */ public static void main( String[] argv ) { // show program usage information (help) if( argv.length == 0 ) { printUsage(); return; } // make an instance of the class RsaCipher instance = new RsaCipher(); // generate the keys instance.doJob(argv); } }