/** ------------------------------------------------------------------------ * Enigma.java * This is an Enigma machine decryption program u * * Class: CS 107 * Lab: Englebert Humberdink, Mon. 5:00 AM * System: Eclipse 3.3.2, Java sdk 1.6, Windows XP * * @author Dale Reed * @version April 12, 2009 * * Running program gives: * * ---------------------------------------------------------------------------- */ // import Java libraries that are needed import java.util.Scanner; // used for console input // the following are needed to implement reading from the dictionary import java.io.IOException; // for IOException import java.util.ArrayList; // Used to create ArrayLists dictionary use import java.io.*; // Used for IOException, File // Declare the class public class Enigma { // Fields that can be accessed anywhere in the class go here Scanner keyboard = new Scanner( System.in); // used to read user input // Declare a dynamically allocated ArrayList of Strings for the dictionary. // The dictionary can hold any number of words. ArrayList dictionary; // Values for three rotors char[] innerRotor = {' ','G','N','U','A','H','O','V','B','I','P','W','C','J','Q','X','D','K','R','Y','E','L','S','Z','F','M','T'}; char[] middleRotor = {' ','E','J','O','T','Y','C','H','M','R','W','A','F','K','P','U','Z','D','I','N','S','X','B','G','L','Q','V'}; char[] outerRotor = {' ','B','D','F','H','J','L','N','P','R','T','V','X','Z','A','C','E','G','I','K','M','O','Q','S','U','W','Y'}; // Create index arrays corresponding to each of the above, so that a letter can be used directly // as an index and doesn't require searching. In other words, rather than having to search within // the innerRotor array to find 'A' at index position 4, we have a innerRotorIndex array that has // an entry for each ASCII character, and at position 'A' (which is 65) we will store the value 4. // This will speed up the program, since we won't need to search through the array for each letter // for each lookup, but rather can go directly there using the letter as the index. int[] innerRotorIndex = new int[128]; int[] middleRotorIndex = new int[128]; int[] outerRotorIndex = new int[128]; // Starting rotor position is chosen by the user, and modifies the starting rotor values char innerRotorStartingLetter = ' '; char middleRotorStartingLetter = ' '; char outerRotorStartingLetter = ' '; // Fields to store best case found int bestNumberOfDictionaryHits = 0; // will range from 0 to # of input words String bestDecryptedString = ""; // will store best decrypted string found char bestInnerRotorSetting = ' '; // will store rotor start letter for best solution found char bestMiddleRotorSetting = ' '; // will store rotor start letter for best solution found char bestOuterRotorSetting = ' '; // will store rotor start letter for best solution found //-------------------------------------------------------------------------- // main() - startup main loop. It is necessary to create an instance of this // class and then call a method from that instance, otherwise there may // be error messages having to do with non-static objects (e.g. keyboard) // being called from a static context (e.g. main). // The words "throws IOException" have to do with dictionary error // handling. // public static void main(String[] args) throws IOException { // create an instance of this class Enigma EnigmaInstance = new Enigma(); // call a non-static method to do everything else EnigmaInstance.mainLoop(); System.out.println("\n" + "Exiting program...\n"); } //------------------------------------------------------------------------- // mainLoop() - display identifying information and run main loop with menu // The words "throws IOException" have to do with dictionary error // handling. // void mainLoop() throws IOException { // First take care of creating and initializing the dictionary // Define the instance of the dictionary ArrayList dictionary = new ArrayList(); // Now fill the dictionary array list with words from the dictionary file readInDictionaryWords(); String cipherText = ""; // stores user Input in main loop char[] cipherTextArray; // starts with the cipherText; Eventually will store the decrypted text // Display identifying information System.out.println( "Author: Dale Reed \n" + "Class: CS 107, Spring 2009 \n" + "Program #5: Enigma \n" + "TA: Billie Joe Armstrong, T 6:00 AM \n" + "April 12, 2009\n"); // Prompt for input to be used System.out.println("Choose from the following options:"); System.out.println(" 1. Encode some text "); System.out.println(" 2. Decode using user-entered rotor starting values "); System.out.println(" 3. Decode by trying all possible rotor combinations "); System.out.println(" 4. Exit program "); System.out.print("Your choice: "); String menuChoice = keyboard.nextLine(); // handle user input for "exit" if( menuChoice.equals("4") ) { System.out.println("Exit was chosen."); // skip rest of code System.exit( 0); } // Handle menu options 1 or 2, both of which prompt the user for the starting rotor // values. Start with the code that initializes the rotor arrays that will be used // in encoding/decoding. if( (menuChoice.equals("1")) || (menuChoice.equals("2")) ) { // Encode or Decode using user-entered values // prompt for and store starting rotor values promptForStartingRotorValuesAndRotateRotors(); }//end if( (menuChoice.equals... if( menuChoice.equals("1") ) { // Selected "1" to encode some text encodeText(); // skip rest of code System.out.println("\n" + "Exiting after encoding text..."); System.exit( 0); } else if( menuChoice.equals("2") || menuChoice.equals("3")) { // Selected "2" to decode using user-entered values, or // selected "3" to decode trying all possible rotor starting positions. // Prompt for cipherText to use System.out.print("Enter the cipherText to be decoded: "); cipherText = keyboard.nextLine(); // convert to upper case and make it into a character array cipherTextArray = cipherText.toUpperCase().toCharArray(); if( menuChoice.equals("2") ) { // Decode using user-entered values // send the cipherText, which will be decoded and returned in place decodeText( cipherTextArray); // display answer System.out.println("Decoded text is: " + String.copyValueOf( cipherTextArray)); } else { // menuChoice was "3", which should try all possible starting rotor positions tryAllRotorPositions( cipherTextArray); // display best decoded text found System.out.println("\n" + "Decoded text is: " + bestDecryptedString + "\n"); // for some reason the stored rotor settings are incorrect, though the decrypted string is correct. ??? // "Using rotor settings: " + bestInnerRotorSetting + bestMiddleRotorSetting + bestOuterRotorSetting); }//end else }//end else if( menuchoice.equals... else { System.out.println("Invalid menu option chosen. Please re-run program"); // skip rest of code System.exit( 0); } }//end mainLoop() // Prompt for and store the starting rotor values. // Do some sanity check evaluation of the input values as well. public void promptForStartingRotorValuesAndRotateRotors() { System.out.print("Enter the inner, mid, and outer rotor starting letters (e.g. ABC) or press enter for default: "); String userInput = keyboard.nextLine(); if( userInput.length() == 0) { // user wants defaults of blanks to be the starting letters System.out.println("Default of blanks used for the starting letters."); innerRotorStartingLetter = ' '; middleRotorStartingLetter = ' '; outerRotorStartingLetter = ' '; } else { // there was user input to select the starting letters for each rotor // convert userInput to all upper case userInput = userInput.toUpperCase(); // extract characters for starting rotor values innerRotorStartingLetter = userInput.charAt( 0); middleRotorStartingLetter = userInput.charAt( 1); outerRotorStartingLetter = userInput.charAt( 2); // Sanity check if( (userInput.length() != 3) || (innerRotorStartingLetter != ' ' && !Character.isLetter( innerRotorStartingLetter) ) || (middleRotorStartingLetter != ' ' && !Character.isLetter( middleRotorStartingLetter) ) || (outerRotorStartingLetter != ' ' && !Character.isLetter( outerRotorStartingLetter) ) ) { // There was a problem, so exit program System.out.println("*** Invalid rotor letters. Exiting program..."); System.exit( -1); } }//end else // Rotate rotor arrays to reflect the user-selected starting letter rotateRotors( innerRotorStartingLetter, middleRotorStartingLetter, outerRotorStartingLetter); }//end promptForAndStoreStartingRotorValues() // Rotate rotors based on starting letters. Then reinitialize the arrays of rotor index values // used to speed up program execution. public void rotateRotors(char innerRotorStartingLetter, char middleRotorStartingLetter, char outerRotorStartingLetter) { // rotate rotors to the given starting letter rotateRotorToStartingCharacter( innerRotorStartingLetter, innerRotor); rotateRotorToStartingCharacter( middleRotorStartingLetter, middleRotor); rotateRotorToStartingCharacter( outerRotorStartingLetter, outerRotor); // Set rotor index values based on the rotor characters. Though not essential, // this speeds up the program since we can jump right to a letter and don't need // to search for it within each rotor. setRotorIndexValues(innerRotorIndex, innerRotor); setRotorIndexValues(middleRotorIndex, middleRotor); setRotorIndexValues(outerRotorIndex, outerRotor); }//end rotateRotors() // rotate the rotor letters to reflect the user's choice of rotor starting position. public void rotateRotorToStartingCharacter( char theLetter, char[] theRotor) { // first make a copy of the original array so we have the values to use // while we modify the original char[] theRotorCopy = new char[ theRotor.length]; for( int i=0; i 0, otherwise empty Strings are considered found in the dictionary if( wordExists( wordsArray[ i].toLowerCase()) && (wordsArray[ i].length() > 0) ) { wordFoundCount++; // For debugging: //System.out.println("Found " + wordsArray[ i]); } }//end for( int i... return wordFoundCount; }//end dictionaryLookup... // If we have a new best case scenario, store the values so we can use it to decode public void storeBestCase(String cipherText, int numberOfDictionaryHits, char innerRotorSetting, char middleRotorSetting, char outerRotorSetting) { // Store information if we have a new best case if( numberOfDictionaryHits > bestNumberOfDictionaryHits) { // we have a new best case, so store these values bestNumberOfDictionaryHits = numberOfDictionaryHits; // store best number of dictionary hits bestDecryptedString = new String( cipherText); // store best decrypted text bestInnerRotorSetting = innerRotorSetting; // store best rotor settings bestMiddleRotorSetting = middleRotorSetting; bestOuterRotorSetting = outerRotorSetting; // display values for debugging /* System.out.println("\n" + "New best case with " + numberOfDictionaryHits + " hits. " + "Rotors set to: " + innerRotorSetting + middleRotorSetting + outerRotorSetting + ". " + " PlainText: " + bestDecryptedString + "\n"); */ }//end if( numberOfDictionaryHits... }//end storeBestCase(...) // Read in the words to create the dictionary. // It throws an IOException, which is a way to gracefully handle errors // should there be a problem reading from the input. public void readInDictionaryWords() throws IOException { // Define a Scanner to read from an input file. Note that the name of // the file given in the code below MUST match the actual filename of // the dictionary file. This file should be in the same directory // as the source code for WordCross.java File dictionaryFile = new File("dictionary.txt"); // declare the file // print the directory where this program expects to find dictionary System.out.println(System.getProperty("user.dir")); // ensure file exists and is in the correct directory if( ! dictionaryFile.exists()) { System.out.println("*** Error *** \n" + "Your dictionary file has the wrong name or is " + "in the wrong directory. \n" + "Aborting program...\n\n"); System.exit( -1); // Terminate the program } Scanner inputFile = new Scanner( dictionaryFile); // while there are words in the input file, add them to the dictionary while( inputFile.hasNext()) { dictionary.add( inputFile.nextLine() ); } }//end createDictionary() // Allow looking up a word in dictionary, returning a value of true or false public boolean wordExists( String wordToLookup) { if( dictionary.contains( wordToLookup)) { return true; // words was found in dictionary } else { return false; // word was not found in dictionary } }//end wordExists }//end Class Enigma