import java.math.BigInteger ;
import java.util.* ;
import java.io.* ;
import java.sql.*;

/**
 * Class for RSA Algorithm (RSA.java).
 *
 * Generates Prime numbers and Public/Private Keys. Performs Encryption and Decryption.
 * Does an analysis of the times taken for various key sizes and message sizes.
 *
 * @author  Chue Wai Lian. Modified by Kalam Shah
 * @version 2.0.0	15th August 2003
 */
public class RSA
{
	/**
	 * Bit length of each prime number.
	 */
	int primeSize ;
        
        /**
         * Certainty of generating a prime number.
         */
        int certainty;
        
        /**
         * The time taken for an operation.
         */
        long timeDiff;
        
        /** Getter for property timeDiff.
         * @return Value of property timeDiff.
         *
         */
        public long getTimeDiff() {
            return timeDiff;
        }
        
        /** Setter for property timeDiff.
         * @param timeDiff New value of property timeDiff.
         *
         */
        public void setTimeDiff(long timeDiff) {
            this.timeDiff = timeDiff;
        }
            
	/**
	 * Two distinct large prime numbers p and q.
	 */
	BigInteger p, q ;

	/**
	 * Modulus N.
	 */
	BigInteger N ;

	/**
	 * r = ( p - 1 ) * ( q - 1 )
	 */
	BigInteger r ;

	/**
	 * Public exponent E and Private exponent D
	 */
	BigInteger E, D ;


	/**
	 * Constructor.
	 *
	 * @param	primeSize		Bit length of each prime number.
	 */
	public RSA(int primeSize, int certainty )
	{
		this.primeSize = primeSize ;
                this.certainty = certainty;
                long startTime = Calendar.getInstance().getTimeInMillis();

		// Generate two distinct large prime numbers p and q.
		generatePrimeNumbers() ;

		// Generate Public and Private Keys.
		generatePublicPrivateKeys() ;
                
                long endTime = Calendar.getInstance().getTimeInMillis();
                this.timeDiff = endTime - startTime;
	}


	/**
	 * Generate two distinct large prime numbers p and q.
	 */
	public void generatePrimeNumbers()
	{
            //System.out.println
		p = new BigInteger( primeSize, certainty, new Random() ) ;
		do
		{
			q = new BigInteger( primeSize, certainty, new Random() ) ;
		}
		while( q.compareTo( p ) == 0 ) ;
	}


	/**
	 * Generate Public and Private Keys.
	 */
	public void generatePublicPrivateKeys()
	{
		// N = p * q
		N = p.multiply( q ) ;


		// r = ( p - 1 ) * ( q - 1 )
		r = p.subtract( BigInteger.valueOf( 1 ) ) ;
		r = r.multiply( q.subtract( BigInteger.valueOf( 1 ) ) ) ;


		// Choose E, coprime to and less than r
		do
		{
			E = new BigInteger( 2 * primeSize, new Random() ) ;
		}
		while( ( E.compareTo( r ) != -1 ) || ( E.gcd( r ).compareTo( BigInteger.valueOf( 1 ) ) != 0 ) ) ;


		// Compute D, the inverse of E mod r
		D = E.modInverse( r ) ;
	}


	/**
	 * Encrypts the plaintext (Using Public Key).
	 *
	 * @param	message			String containing the plaintext message to be encrypted.
	 * @return	The ciphertext as a BigInteger array.
	 */
	public BigInteger[] encrypt( String message)
	{
                long startTime = Calendar.getInstance().getTimeInMillis();
		int i, j;
                int blockSize = this.primeSize/8; //gives no. of bytes in a block
                
                byte[] temp = new byte[blockSize] ;


		byte[] digits = message.getBytes() ;
                int ceilFactor = digits.length%blockSize==0?0:1;
		

		BigInteger[] bigdigits = new BigInteger[digits.length/blockSize + ceilFactor] ;

		for( i = 0 ; i < bigdigits.length ; i++ )
		{
                    for (j=0; j<blockSize; j++)
                    {
                        int index = i*blockSize + j;
                        if (index<digits.length) temp[j] = digits[i*blockSize+j];
                    }
                    bigdigits[i] = new BigInteger( temp ) ;
		}

		BigInteger[] encrypted = new BigInteger[bigdigits.length] ;

		for( i = 0 ; i < bigdigits.length ; i++ )
			encrypted[i] = bigdigits[i].modPow( E, N ) ;
                
                long endTime = Calendar.getInstance().getTimeInMillis();
                this.timeDiff = endTime - startTime;

		return( encrypted ) ;
	}


	/**
	 * Decrypts the ciphertext (Using Private Key).
	 *
	 * @param	encrypted		BigInteger array containing the ciphertext to be decrypted.
	 * @return	The decrypted plaintext.
	 */
	public String decrypt( BigInteger[] encrypted )
	{
                long startTime = Calendar.getInstance().getTimeInMillis();
		int i ;
		BigInteger[] decrypted = new BigInteger[encrypted.length] ;

		for( i = 0 ; i < decrypted.length ; i++ )
			decrypted[i] = encrypted[i].modPow( D, N ) ;

		String decryptedText = "";

		for( i = 0 ; i < decrypted.length ; i++ )
                {
                    decryptedText += new String(decrypted[i].toByteArray());
                }
                long endTime = Calendar.getInstance().getTimeInMillis();
                this.timeDiff = endTime - startTime;

		return(decryptedText) ;
	}


	/**
	 * Get prime number p.
	 *
	 * @return	Prime number p.
	 */
	public BigInteger getp()
	{
		return( p ) ;
	}


	/**
	 * Get prime number q.
	 *
	 * @return	Prime number q.
	 */
	public BigInteger getq()
	{
		return( q ) ;
	}


	/**
	 * Get r.
	 *
	 * @return	r.
	 */
	public BigInteger getr()
	{
		return( r ) ;
	}


	/**
	 * Get modulus N.
	 *
	 * @return	Modulus N.
	 */
	public BigInteger getN()
	{
		return( N ) ;
	}


	/**
	 * Get Public exponent E.
	 *
	 * @return	Public exponent E.
	 */
	public BigInteger getE()
	{
		return( E ) ;
	}


	/**
	 * Get Private exponent D.
	 *
	 * @return	Private exponent D.
	 */
	public BigInteger getD()
	{
		return( D ) ;
	}



	/**
	 * RSA Main program for Unit Testing.
	 */
	public static void main( String[] args ) throws IOException
	{
            int charSize = 4; //This is the size in bytes of a character in Java
            
            //set the key sizes (in bits)
            int []keySize = new int[5];
            keySize[0]=128;
            keySize[1]=256;
            keySize[2]=512;
            keySize[3]=768;
            keySize[4]=1024;
            
            //set the message sizes (in bytes)
            int []messageSize = new int[5];
            messageSize[0]=128;
            messageSize[1]=512;
            messageSize[2]=1024;
            messageSize[3]=2048;
            messageSize[4]=4096;
            
            //set different values of probability
            int []certaintyP = new int[4];
            certaintyP[0]=1;
            certaintyP[1]=3;
            certaintyP[2]=7;
            certaintyP[3]=10;
            
            //read the input and output file names
            String inFile = null;
            String outFile = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Enter the input file name to encode its contents: ");
            try
            {
                inFile = in.readLine();
            }catch(IOException ie)
            {
                System.out.println("Error reading input file name. :"+ie);
            }
            System.out.print("Enter the output file name to save the encoded message: ");
            try
            {
                outFile = in.readLine();
            }catch(IOException ie)
            {
		System.out.println("Error reading output file name. :"+ie);
            }
            
            //open input and output files
            FileReader fr = null;
            FileWriter fw = null;
            try
            {
                fr = new FileReader(inFile);
                fw = new FileWriter(outFile);
                fr.close();
            }catch (FileNotFoundException fe)
            {
                System.out.println("File Not found. "+fe);
                try
                {
                    fw.close();
                }
                catch(IOException ioe)
                {
                    System.out.println("Error closing output file:"+ioe);
                }
                System.exit(0);
            }catch (IOException ioe1)
            {
                System.out.println("Cannot make output file:"+ioe1);
            }
                
                
            fw.write("              RSA Algorithm               \n\n");
            fw.write("---------------------------------------------\n");
            fw.write("For different Key Sizes\n");
            fw.write("***********************************************\n");
            //Repeat for different key sizes
            //message length is assumed to be fixed at messageSize[1]
            //certainty is assumed constant at certaintyP[3]
            int i=0;
            String strTemp;
            String strHeader;
            while (i<5)
            {
                strHeader = "RSA Algorithm: Key Size="+keySize[i]+" bits"
                            + ", certainty="+certaintyP[3]
                            + ", messageSize="+messageSize[1]+" bytes";
                    System.out.println(strHeader);
                try
                {
                    fr = new FileReader(inFile);
                    fw.write(strHeader+"\n");
                    fw.write("-------------------------------------------------\n");

                    RSA rsa = new RSA(keySize[i], certaintyP[3]) ;

                    strTemp = "Generated prime numbers p and q\n"
                            + "p: [" + rsa.getp().toString( 16 ).toUpperCase() + "]\n"
                            + "q: [" + rsa.getq().toString( 16 ).toUpperCase() + "]\n"
                            + "Time taken to generate them:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The public key is the pair (N, E) which will be published.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "E: [" + rsa.getE().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The private key is the pair (N, D) which will be kept private.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "D: [" + rsa.getD().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    //read input text from file
                    char[] cbuff = new char[messageSize[1]/charSize];
                    fr.read(cbuff, 0, messageSize[1]/charSize);
                    String plaintext = new String(cbuff);
                    //plaintext = "my name is kalam";
                    System.out.println("plaintext=["+plaintext+"]");
                    fw.write("plaintext=["+plaintext+"]\n");

                    // Encrypt Message
                    BigInteger[] ciphertext = rsa.encrypt(plaintext) ;
                    strTemp = "Ciphertext: [";
                    for( int j = 0 ; j < ciphertext.length ; j++ )
                    {
                        strTemp += ciphertext[j].toString( 16 ).toUpperCase();
                        if( j != ciphertext.length - 1 ) strTemp += " ";
                    }
                    strTemp += "]\n";
                    strTemp += "Time taken to encrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
                    strTemp = "Recovered plaintext: [" + recoveredPlaintext + "]\n"
                            + "Time taken to decrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp) ;
                    fw.write(strTemp + "\n\n\n");
                    fr.close();
                }catch(Exception e)
                {
                    System.out.println("Exception while executing the 1st while loop:"+e);
                    System.exit(0);
                }
                i++;
            }
            
            
            fw.write("\n\n\n\n\n---------------------------------------------\n");
            fw.write("For different message Sizes-----KeySize="+keySize[3]+"\n");
            fw.write("***********************************************\n");
            //Repeat for different message sizes
            //key size is fixed at keySize[3]
            //certainty is assumed constant at certaintyP[3]
            i=0;
            while (i<5)
            {
                strHeader = "RSA Algorithm: Key Size="+keySize[3]+" bits"
                            + ", certainty="+certaintyP[3]
                            + ", messageSize="+messageSize[i]+" bytes";
                    System.out.println(strHeader);
                try
                {
                    fr = new FileReader(inFile);
                    fw.write(strHeader+"\n");
                    fw.write("-------------------------------------------------\n");

                    RSA rsa = new RSA(keySize[3], certaintyP[3]) ;

                    strTemp = "Generated prime numbers p and q\n"
                            + "p: [" + rsa.getp().toString( 16 ).toUpperCase() + "]\n"
                            + "q: [" + rsa.getq().toString( 16 ).toUpperCase() + "]\n"
                            + "Time taken to generate them:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The public key is the pair (N, E) which will be published.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "E: [" + rsa.getE().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The private key is the pair (N, D) which will be kept private.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "D: [" + rsa.getD().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    //read input text from file
                    char[] cbuff = new char[messageSize[i]/charSize];
                    fr.read(cbuff, 0, messageSize[i]/charSize);
                    String plaintext = new String(cbuff);
                    //plaintext = "my name is kalam";
                    System.out.println("plaintext=["+plaintext+"]");
                    fw.write("plaintext=["+plaintext+"]\n");

                    // Encrypt Message
                    BigInteger[] ciphertext = rsa.encrypt(plaintext) ;
                    strTemp = "Ciphertext: [";
                    for( int j = 0 ; j < ciphertext.length ; j++ )
                    {
                        strTemp += ciphertext[j].toString( 16 ).toUpperCase();
                        if( j != ciphertext.length - 1 ) strTemp += " ";
                    }
                    strTemp += "]\n";
                    strTemp += "Time taken to encrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
                    strTemp = "Recovered plaintext: [" + recoveredPlaintext + "]\n"
                            + "Time taken to decrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp) ;
                    fw.write(strTemp + "\n\n\n");
                    fr.close();
                }catch(Exception e)
                {
                    System.out.println("Exception while executing the 1st while loop:"+e);
                    System.exit(0);
                }
                i++;
            }
            
            fw.write("\n\n\n\n\n---------------------------------------------\n");
            fw.write("For different message Sizes-----KeySize="+keySize[4]+"\n");
            fw.write("***********************************************\n");
            //Repeat for different message sizes
            //key size is fixed at keySize[4]
            //certainty is assumed constant at certaintyP[3]
            i=0;
            while (i<5)
            {
                strHeader = "RSA Algorithm: Key Size="+keySize[4]+" bits"
                            + ", certainty="+certaintyP[3]
                            + ", messageSize="+messageSize[i]+" bytes";
                    System.out.println(strHeader);
                try
                {
                    fr = new FileReader(inFile);
                    fw.write(strHeader+"\n");
                    fw.write("-------------------------------------------------\n");

                    RSA rsa = new RSA(keySize[4], certaintyP[3]) ;

                    strTemp = "Generated prime numbers p and q\n"
                            + "p: [" + rsa.getp().toString( 16 ).toUpperCase() + "]\n"
                            + "q: [" + rsa.getq().toString( 16 ).toUpperCase() + "]\n"
                            + "Time taken to generate them:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The public key is the pair (N, E) which will be published.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "E: [" + rsa.getE().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The private key is the pair (N, D) which will be kept private.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "D: [" + rsa.getD().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    //read input text from file
                    char[] cbuff = new char[messageSize[i]/charSize];
                    fr.read(cbuff, 0, messageSize[i]/charSize);
                    String plaintext = new String(cbuff);
                    //plaintext = "my name is kalam";
                    System.out.println("plaintext=["+plaintext+"]");
                    fw.write("plaintext=["+plaintext+"]\n");

                    // Encrypt Message
                    BigInteger[] ciphertext = rsa.encrypt(plaintext) ;
                    strTemp = "Ciphertext: [";
                    for( int j = 0 ; j < ciphertext.length ; j++ )
                    {
                        strTemp += ciphertext[j].toString( 16 ).toUpperCase();
                        if( j != ciphertext.length - 1 ) strTemp += " ";
                    }
                    strTemp += "]\n";
                    strTemp += "Time taken to encrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
                    strTemp = "Recovered plaintext: [" + recoveredPlaintext + "]\n"
                            + "Time taken to decrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp) ;
                    fw.write(strTemp + "\n\n\n");
                    fr.close();
                }catch(Exception e)
                {
                    System.out.println("Exception while executing the 1st while loop:"+e);
                    System.exit(0);
                }
                i++;
            }
            
            fw.write("\n\n\n\n\n---------------------------------------------\n");
            fw.write("For different Probabilities\n");
            fw.write("***********************************************\n");
            //Repeat for different probabilities
            //key size is fixed at keySize[3]
            //messageSize is fixed at messageSize[2]
            i=0;
            while (i<4)
            {
                strHeader = "RSA Algorithm: Key Size="+keySize[3]+" bits"
                            + ", certainty="+certaintyP[i]
                            + ", messageSize="+messageSize[2]+" bytes";
                    System.out.println(strHeader);
                try
                {
                    fr = new FileReader(inFile);
                    fw.write(strHeader+"\n");
                    fw.write("-------------------------------------------------\n");

                    RSA rsa = new RSA(keySize[3], certaintyP[i]) ;

                    strTemp = "Generated prime numbers p and q\n"
                            + "p: [" + rsa.getp().toString( 16 ).toUpperCase() + "]\n"
                            + "q: [" + rsa.getq().toString( 16 ).toUpperCase() + "]\n"
                            + "Time taken to generate them:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The public key is the pair (N, E) which will be published.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "E: [" + rsa.getE().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    strTemp = "The private key is the pair (N, D) which will be kept private.\n"
                            + "N: [" + rsa.getN().toString( 16 ).toUpperCase() + "]\n"
                            + "D: [" + rsa.getD().toString( 16 ).toUpperCase() + "]\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    //read input text from file
                    char[] cbuff = new char[messageSize[2]/charSize];
                    fr.read(cbuff, 0, messageSize[2]/charSize);
                    String plaintext = new String(cbuff);
                    //plaintext = "my name is kalam";
                    System.out.println("plaintext=["+plaintext+"]");
                    fw.write("plaintext=["+plaintext+"]\n");

                    // Encrypt Message
                    BigInteger[] ciphertext = rsa.encrypt(plaintext) ;
                    strTemp = "Ciphertext: [";
                    for( int j = 0 ; j < ciphertext.length ; j++ )
                    {
                        strTemp += ciphertext[j].toString( 16 ).toUpperCase();
                        if( j != ciphertext.length - 1 ) strTemp += " ";
                    }
                    strTemp += "]\n";
                    strTemp += "Time taken to encrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp);
                    fw.write(strTemp);

                    String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
                    strTemp = "Recovered plaintext: [" + recoveredPlaintext + "]\n"
                            + "Time taken to decrypt:"+rsa.getTimeDiff()+" ms\n\n";
                    System.out.println(strTemp) ;
                    fw.write(strTemp + "\n\n\n");
                    fr.close();
                }catch(Exception e)
                {
                    System.out.println("Exception while executing the 1st while loop:"+e);
                    System.exit(0);
                }
                i++;
            }
            
            System.out.println("You can view the results in "+inFile);
            
            //close the output files
            try
            {
                    fw.close();
            }
            catch(IOException ioe)
            {
                    System.out.println("Error closing file."+ioe);
            }
        }
}

