/** * RSA Key Generator. Stored private and public keys in XML format. * * TODO: Disable debugging output * TODO: Write tests * TODO: Determine algorithm failures (in finding e, d, and div by zero) * * Class: CS 340, Fall 2005 * System: jdk-1.5.0.4, Windows XP * @author Michael Leonhard * @version 5 Sep 2005 */ import java.util.*; import java.io.*; public class KeyGen { // fields private String M_pubkeyFilename = null; private String M_privkeyFilename = null; private int M_p = -1, M_q = -1; private int M_n, M_phi, M_e, M_d; /** * Constructor for objects of class KeyGen */ public KeyGen() { } /** * Test the number for primeness, return true or false */ boolean isPrime( int n ) { // prime numbers are all positive and greater than zero if( n <= 0 ) return false; // even numbers are not prime if( n % 2 == 0 ) return false; // non-even numbers below 8 are all prime: 1 3 5 7 if( n < 8 ) return true; // search for possible factors for( int j = 3; j < n / 2; j += 2 ) { // is j a factor? if( n % j == 0 ) { printDebug( "isPrime/1 " + Integer.toString(n) + " is divisible by " + Integer.toString(j) ); return false; } } printDebug( "isPrime/1 " + Integer.toString(n) + " is prime!" ); return true; } /** * Return the smallest prime number that is larger than n */ int findPrime( int n ) { printDebug( "findPrime/1 checking for prime number larger than " + Integer.toString(n) ); // if n is even then make it odd if( n % 2 == 0 ) n = n + 1; // loop to find a prime number while( !isPrime(n) ) { printDebug( "findPrime/1 " + Integer.toString(n) + " is not prime" ); n = n + 2; } printDebug( "findPrime/1 returning " + Integer.toString(n) ); return n; } /** * Prints debugging messages to the console */ static public void printDebug( String message ) { //System.out.println( message ); } /** * Configures default values that were not specified by the user */ private void useNeededDefaults() { if( M_pubkeyFilename == null ) // use default filenames { M_pubkeyFilename = "pubkey.xml"; M_privkeyFilename = "privkey.xml"; // inform user System.out.println( "Filenames not specified. Using defaults: pubkey.xml privkey.xml" ); } } /** * Try one time to get the second prime number from the user */ public int getSecondPrimeFromUser( BufferedReader consoleReader ) throws TException,IOException { int q = -1; // prompt the user and read the first number System.out.print( "Please enter prime number q:" ); String qString = consoleReader.readLine().trim(); // nothing was entered if( qString.length() == 0 ) return -1; try // convert parameter to integer { q = Integer.valueOf( qString ).intValue(); } catch( NumberFormatException e ) { throw new TException( "You must type a positive integer that is prime." ); } // integer must be prime if( !isPrime( q ) ) throw new TException( Integer.toString(q) + " is not prime. You must type a prime number." ); // must not be 1 if( q == 1 ) throw new TException( "The number may not be 1." ); return q; } /** * Try one time to get the prime numbers from the user */ private void getPrimeNumbersFromUser( BufferedReader consoleReader ) throws TException,IOException { int p = -1, q = -1; // prompt the user and read the first number System.out.print( "Please enter prime number p:" ); String pString = consoleReader.readLine().trim(); // nothing was entered if( pString.length() == 0 ) return; try // convert parameter to integer { p = Integer.valueOf( pString ).intValue(); } catch( NumberFormatException e ) { throw new TException( "You must type a positive integer that is prime." ); } // integer must be prime if( !isPrime( p ) ) throw new TException( Integer.toString(p) + " is not prime. You must type a prime number." ); // must not be 1 if( p == 1 ) throw new TException( "The number may not be 1." ); // need second prime number, loop until user enters it correctly while( q == -1 ) { try { q = getSecondPrimeFromUser(consoleReader); } catch( TException e ) { e.print(); } } // the numbers must not be equal if( p == q ) throw new TException( "You may not enter the same number twice." ); // the numbers must have a product larger than 127 if(p * q < 128 ) throw new TException( "The product of the numbers must be greater than 127." ); // save the values M_p = p; M_q = q; } /** * Gets required data from the user */ private void getDataFromUser() throws IOException { // create a console reader object BufferedReader consoleReader = new BufferedReader( new InputStreamReader( System.in ) ); // need prime numbers, loop until the user enters them correctly while( M_p == -1 ) { try { getPrimeNumbersFromUser(consoleReader); } catch( TException e ) { e.print(); } } } /** * 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("-o") ) { // -o option may not be specified twice if( M_pubkeyFilename != null ) throw new TException( "Malformed Commandline: the -o option may be specified only once" ); // there must be two more parameters if( i + 2 >= argc ) throw new TException( "Malformed Commandline: please specify two filenames after the -o option" ); printDebug( "PARMS: " + argv[i+1] + " " + argv[i+2] ); // the filenames must not be equal if( argv[i+1].equals(argv[i+2]) ) throw new TException( "Malformed Commandline: the pubkey and privkey filenames may not be the same." ); // store filenames M_pubkeyFilename = argv[i+1]; M_privkeyFilename = argv[i+2]; // two extra parameters processed i = i + 2; } else if( argv[i].equals("-c") ) { // initialize the pseudo random number generator Random randomizer = new Random(); // choose p to be a random prime number in [16,116) M_p = findPrime(randomizer.nextInt(100) + 16); // choose q to be a random prime number in [16,116) that is not p M_q = M_p; while( M_q == M_p ) M_q = findPrime(randomizer.nextInt(100) + 16); } else // look for primes { // primes may not be specified twice if( M_p != -1 ) throw new TException( "Malformed Commandline: unexpected parameter: \"" + argv[i] + "\"" ); // convert parameter to integer try { M_p = Integer.valueOf( argv[i] ).intValue(); } catch( NumberFormatException e ) { throw new TException( "Malformed Commandline: expected integer but found \"" + argv[i] + "\"" ); } // integer must be prime if( !isPrime( M_p ) ) throw new TException( "Malformed Commandline: expected prime number but found " + Integer.toString(M_p) ); // must not be 1 if( M_p == 1 ) throw new TException( "Malformed Commandline: prime number must not be 1" ); // there must one more parameter if( i + 1 >= argc ) throw new TException( "Malformed Commandline: missing second prime number" ); printDebug( "PARM: " + argv[i+1] ); try // convert next parameter to integer { M_q = Integer.valueOf( argv[i+1] ).intValue(); } catch( NumberFormatException e ) { throw new TException( "Malformed Commandline: expected integer but found \"" + argv[i+1] + "\"" ); } // integer must be prime if( !isPrime( M_q ) ) throw new TException( "Malformed Commandline: expected prime number but found " + Integer.toString(M_q) ); // must not be 1 if( M_q == 1 ) throw new TException( "Malformed Commandline: prime number must not be 1" ); // the numbers must not be equal if( M_p == M_q ) throw new TException( "Malformed Commandline: the prime numbers may not be the same" ); // the numbers must have a product larger than 127 if( M_p * M_q < 128 ) throw new TException( "Malformed Commandline: the product of the primes must be >127" ); // one extra parameter processed i = i + 1; } } } /** * Test the a and b for a common denominator larger than 1 */ boolean areRelativelyPrime( int a, int b ) { String text = Integer.toString(a)+" and "+Integer.toString(b); printDebug( "areRelativelyPrime/2 testing " + text); while( b != 0 ) { int t = b; b = a % b; a = t; } if( a == 1 ) printDebug( "areRelativelyPrime/2 " + text + " are relatively prime" ); else printDebug( "areRelativelyPrime/2 " + text + " are not relatively prime" ); return a == 1; } /** * Generate n, phi, etc. */ private void generateKeys() throws TException { printDebug( "p is " + Integer.toString(M_p) ); printDebug( "q is " + Integer.toString(M_q) ); M_n = M_p * M_q; printDebug( "n is " + Integer.toString(M_n) ); M_phi = (M_p - 1) * (M_q - 1); printDebug( "phi is " + Integer.toString(M_phi) ); // find largest suitable e int e = M_n - 1; while( e == M_phi || !areRelativelyPrime(e, M_phi) ) { // TODO: Try to find out if this can ever happen if( e == 1 ) throw new TException( "Unable to find valid value for e" ); e--; } M_e = e; printDebug( "e is " + Integer.toString(M_e) ); // search (inefficiently) for d int d = 3; while( (M_e*d) % M_phi != 1 ) { // TODO: Try to find out if this can ever happen if( d == M_n || d == M_phi ) throw new TException( "Unable to find valid value for d" ); d++; } M_d = d; printDebug( "d is " + Integer.toString(M_d) ); } /** * Write the private and public key files in XML format */ private void writeKeys() throws IOException { System.out.println( "Writing public key to file: " + M_pubkeyFilename ); FileWriter pubKeyFile = new FileWriter( M_pubkeyFilename ); pubKeyFile.write( "\r\n" + "\t"+Integer.toString(M_e)+"\r\n" + "\t"+Integer.toString(M_n)+"\r\n" + "\r\n"); pubKeyFile.close(); System.out.println( "Writing private key to file: " + M_privkeyFilename ); FileWriter privKeyFile = new FileWriter( M_privkeyFilename ); privKeyFile.write( "\r\n" + "\t"+Integer.toString(M_d)+"\r\n" + "\t"+Integer.toString(M_n)+"\r\n" + "\r\n"); privKeyFile.close(); printDebug( "done." ); } /** * Obtain (p,q) and generate the keys */ public void doJob( String[] argv ) { // print program information System.out.println ( "CS 340 Project 1 program 1 (KeyGen).\n" + "Michael Leonhard, 2005/09/05\n" + "Java 1.5.0.4 on Windows XP\n" ); try { processCommandLine( argv ); useNeededDefaults(); getDataFromUser(); generateKeys(); writeKeys(); } catch( TException e ) { printUsage(); System.out.println(); e.print(); return; } catch (IOException e) { System.out.println("Problem writing files:"); System.out.println(e.toString()); return; } } /** * Print the command line usage. */ public static void printUsage() { System.out.println( "Command Line Usage: keygen [-o ] [

| -c]" ); System.out.println( " is the name of file to write public key" ); System.out.println( " is the name of file to write private key" ); System.out.println( " -c requests that prime numbers be generated automatically" ); System.out.println( "

and are unique prime numbers used to generate the keys" ); System.out.println( " Note: p * q must be larger than 127" ); System.out.println( "Example command line: keygen -o pubkey.xml privkey.xml -c" ); } /** * This method is called when the class is loaded from the command line */ public static void main( String[] argv ) { // make an instance of the class KeyGen instance = new KeyGen(); // generate the keys instance.doJob(argv); } }