package rsa;

import java.math.BigInteger;
import java.math.*;
import java.util.*;
import java.io.*;

/**
 * Title: RSA - Proof Of Concept
 * Description: This class implements and wraps up the RSA algorithm to encrypt numbers.
 *              It's use is the correct implimentation to boost confidence in the
 *              understanding (or at the very least the translation!) of the Maths 
 *              involved in encyption algorithms.
 *
 *              Prior to use of BigInteger the original version/Proof of concept was simply int which 
 *              was a much less direct translation of the algorithm than below.
 *              These methods where mostly re-usable (Prime number generation, 
 *              validation, gcd(int a, b) etc) have been moved to "com.blamires.NumberSupport".
 *	      
 *              
 * Copyright: Copyright Mike Blamires (c) Jan 2008
 * Company: n/a
 * @author Mike Blamires
 * @version 1.0
 */

public class RSATest {

      //1024 should be absolute minimum for production for testing small is OK
      //just remember to keep the length of your encryption string small!
      //Remember the larger the key the longer the public key will take.
      private static int BIT_LENGTH = 1024;
      
      //'Debug' assistence
      private static boolean DEBUG = false;
      private static long start;
      private static long end;
      //end debug assistence      
      
      private BigInteger toEncode;
      private BigInteger decrypted;
      private BigInteger encrypted;
      
      //variables associated with the RSA Algorithm.
      private BigInteger E;
      private BigInteger D;

      private BigInteger P;
      private BigInteger Q;
      private BigInteger PQ; 

      //entry point. 
      public static void main(String[] args) {

          //get an alternative bit length from the command line,
	  //switch off debugging.
          try {
               BIT_LENGTH = Integer.parseInt(args[0]);
               if (args[1].toString().equals("true")) {
                    DEBUG = true;
               }
	  } catch (Exception e) { }

          System.out.print("Enter a number to encyrpt: ");

          //read in our line
          BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
          String toEncode = null;

          try {
             toEncode = br.readLine();
          } catch (java.io.IOException ioe) { }

          //start our program
          RSATest prog = new RSATest(toEncode);

      }

      //constructor, create a new instance of the class and run through key 
      //generation and a test encode/decode
      public RSATest(String enc) {

          System.out.println("Generating Key");
          generateKey();
          System.out.println("Key Generation Complete");
          toEncode = new BigInteger(enc);
          
          if (DEBUG) System.out.println("toEncode (str): " + toEncode.toString());
          
          System.out.println("Encode/Decode with the private key and public key");
          encode();
          decode();
          System.out.println("Press any key to continue.");
          
          try {
            System.in.read();
          } catch (java.io.IOException e) { }

      }


	
      //here we generate our public and private key
      public void generateKey() {

          if (DEBUG) {
              System.out.println("enter -> generateKey()");
              start = System.currentTimeMillis();
          }
          

          //we need two prime numbers, genereated with as much randomness as possible.
          //BigInteger can provide probable primes. It's PsuedoRandom but for the
          //purpose of the POC suits.
          //for production SecureRandom should be used.
          P = BigInteger.probablePrime(RSATest.BIT_LENGTH/2, new Random());
          Q = BigInteger.probablePrime(RSATest.BIT_LENGTH/2, new Random());

          if (DEBUG) System.out.println("P: " + P);
          if (DEBUG) System.out.println("Q: " + Q);

          PQ = P.multiply(Q);

          BigInteger PHI = (P.subtract(BigInteger.ONE)).multiply(Q.subtract(BigInteger.ONE));


          //discard these key values so they don't hang around in memory
          P = null;
          Q = null;

          //not going to be lower than 3.
          E = new BigInteger("3");

          //e < n such that gcd(e, phi)=1
          //loop until we find the gcd of phi which gives us E
          while (PHI.gcd(E).intValue() > 1)
              E = E.add(new BigInteger("2")); //doing one when 50% of cycles wouldn't be used is not worth it'

          if (DEBUG) System.out.println("E: " + E.toString());

          //D is the multiplicative inverse of E. modInverse takes care of this
          //d = e^-1 mod phi.
          D =  E.modInverse(PHI);
          
          if (DEBUG) {
              end = System.currentTimeMillis();
              System.out.println("Key generation took: " + (end - start) + " milliseconds");
          }
          
          if (DEBUG) System.out.println("D: " + D.toString());

          System.out.println("Public Key: (" + E.toString() + "," + PQ.toString() + ")");
          System.out.println("Private Key: (" + D.toString() + ")");

          if (DEBUG) System.out.println("exit -> generateKey()");

      }



      //encode the entered string
      public void encode() {
          encrypted = toEncode.modPow(E, PQ);
          System.out.println("encoded: " + encrypted.toString());
      }

      //Decode the entered string
      public void decode() {
          decrypted = encrypted.modPow(D, PQ);
          System.out.println("decoded: " + decrypted.toString());
      }


}


