// Program to implement RSA public key - private key encryption

/* The program has to compare the timing for running for different key sizes,
 * different message lengths etc.
 * The different events which we have to store in the log file are
 * Key Size
 * The certainty in the prime no. required
 * Time to generate first prime no.
 * First prime no.
 * Time to generate second prime no.
 * Second Prime no.
 * Time to compute N
 * The value of N
 * Time to compute E
 * The value of E
 * Time to compute D
 * The value of D
 * The message length
 * The message
 * Time to encrypt the message
 * Time to decrypt the message
 * The decrypted message

 The parameter that should be passed with different values are
	Key size
	Certainty in the prime no.
	The message length( since this program will be called externally
			    by some other program, so only the message
			    length will be passed. The message of that
			    length will be read from a file.)
 */
	
	
	

import java.math.BigInteger ;
import java.util.* ;
import java.io.* ;
import java.sql.*;

// This class has the functions to implement writing to a file.
class WriteToFile{
	String 		szFilename;
	BufferedWriter	br;
	File		outputFile;
	
	WriteToFile(String filename) throws IOException
	{
		szFilename = new String(filename);
		outputFile = new File(szFilename);
		if(!outputFile.exists())
		{
			System.out.println("File does not exists.");
		}
		else
		{
			br = new BufferedWriter(new FileWriter(outputFile));
		}
	}

	

	//this method writes the string to the file
	void write(String line) throws IOException
	{
		br.write(line);
	}

	void close() throws IOException
	{
		br.close();
	}
	

}



// This class has the functions to implement writing to a file.
class ReadFromFile{
	String 		szFilename;
	BufferedReader	br;
	File		inputFile;
	
	ReadFromFile(String filename) throws IOException
	{
		szFilename = new String(filename);
		inputFile = new File(szFilename);
		if(!inputFile.exists())
		{
			System.out.println("File does not exists.");
		}
		else
		{
			br = new BufferedReader(new FileReader(inputFile));
		}
	}
	

	//this method writes the string to the file
	String read() throws IOException
	{
		return br.readLine();
	}

	String read(int len) throws IOException
	{
		int	i;
		String strtemp;
		String strResult = new String("");
		strtemp = br.readLine();
		while ( strtemp != null && len > 0)
		{
			i = strtemp.length();
			strResult += strtemp;
			strResult += new String("\n");
			
			
			len = len - i;
			strtemp = br.readLine();

		}
		return strResult;
			
	 }

	void close() throws IOException
	{
		br.close();
	}
	
}


/**
 * Class for RSA Algorithm (RSA.java).
 *
 * Generates Prime numbers and Public/Private Keys. Performs Encryption and
 * Decryption.
 *
 */
public class RSA
{
	/**
	* The name of the file into which the output is to be stored
	*/
	String szFilename;        
	
	/** 
	 * The certainty factor for the P, Q to be prime
	 */
	int certaintyFactor;

	/**
	 * Message Length
	 */
	int messageLength;

	/**
	 * The message string - since the message here is immaterial, it
	 * will be read from a file - the name of the file is taken as 
	 * a parameter.
	 */
	 String messageFilename;

	/**
	 * Time to generate the prime nos., computing N, Generating E, computing
	 * D, encryption and decryption
	 */
	 long timeToGeneratePQ;
	 long timeToComputeN;
	 long timeToGenerateE;
	 long timeToComputeD;
	 long timeToEncrypt;
	 long timeToDecrypt;
	
                                                            
        /**
         * Bit length of each prime number.
         */
        int primeSize ;

        /**
         * 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, int messLen, String logFile, String messageFile)
        {
		szFilename = new String(logFile);
		messageFilename = new String(messageFile);
		
                
		this.primeSize = primeSize ;
		this.certaintyFactor = certainty;
		this.messageLength = messLen;

                // Generate two distinct large prime numbers p and q.
                generatePrimeNumbers() ;

                // Generate Public and Private Keys.
                generatePublicPrivateKeys() ;
        }
                                                                     



        /**
         * Generate two distinct large prime numbers p and q.
         */
        public void generatePrimeNumbers()
        {
            long start=getCurrentTime();
                p = new BigInteger( primeSize, 100, new Random() ) ;
                do
                {
                        q = new BigInteger( primeSize, 10, new Random() ) ;
                }
                while( q.compareTo( p ) == 0 ) ;
            long end=getCurrentTime();
	     timeToGeneratePQ = (new Timestamp(end-start)).getNanos(); //added


        }
        public long getCurrentTime()
        {
        	java.util.Date date=Calendar.getInstance().getTime();
	        Timestamp a=new Timestamp(date.getTime());
		long currentTime=Calendar.getInstance().getTimeInMillis();

                return currentTime;
        }



        /**
         * Generate Public and Private Keys.
         */
        public void generatePublicPrivateKeys()
        {
                long start, end;                                                    
                // N = p * q
            	start=getCurrentTime();
                N = p.multiply( q ) ;
            	end=getCurrentTime();
	     	timeToComputeN= (new Timestamp(end-start)).getNanos(); //added
		


                // 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
		start = getCurrentTime();
                do
                {
                        E = new BigInteger( 2 * primeSize, new Random() ) ;
                }
                while( ( E.compareTo( r ) != -1 ) || ( E.gcd( r ).compareTo(BigInteger.valueOf( 1 ) ) != 0 ) ) ;
		end = getCurrentTime();
	     	timeToGenerateE = (new Timestamp(end-start)).getNanos(); //added


                // Compute D, the inverse of E mod r
		start = getCurrentTime();
                D = E.modInverse( r ) ;
		end = getCurrentTime();
	     	timeToComputeD = (new Timestamp(end-start)).getNanos(); //added
        }
                        

        /**
         * 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 )
        {
                int i ;
		long start, end;
                byte[] temp = new byte[1] ;


                byte[] digits = message.getBytes() ;

                BigInteger[] bigdigits = new BigInteger[digits.length] ;

                for( i = 0 ; i < bigdigits.length ; i++ )
                {
                        temp[0] = digits[i] ;
                        bigdigits[i] = new BigInteger( temp ) ;
                }

                BigInteger[] encrypted = new BigInteger[bigdigits.length] ;

		start = getCurrentTime();
                for( i = 0 ; i < bigdigits.length ; i++ )
                        encrypted[i] = bigdigits[i].modPow( E, N ) ;
		end = getCurrentTime();
	     	timeToEncrypt = (new Timestamp(end-start)).getNanos(); //added


                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 )
        {
                int i ;
		long start, end;


                BigInteger[] decrypted = new BigInteger[encrypted.length] ;

		start = getCurrentTime();
                for( i = 0 ; i < decrypted.length ; i++ )
                        decrypted[i] = encrypted[i].modPow( D, N ) ;
		end = getCurrentTime();
	     	timeToDecrypt = (new Timestamp(end-start)).getNanos(); //added

                char[] charArray = new char[decrypted.length] ;

                for( i = 0 ; i < charArray.length ; i++ )
                        charArray[i] = (char) ( decrypted[i].intValue() ) ;


                return( new String( charArray ) ) ;
        }


        /**
         * 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 ) ;
        }

	public BigInteger getTimeToGeneratePQ()
	{
		return BigInteger.valueOf(timeToGeneratePQ/1000);
	}

	public BigInteger getTimeToComputeN()
	{
		return BigInteger.valueOf(timeToComputeN/1000);
	}

	public BigInteger getTimeToGenerateE()
	{
		return BigInteger.valueOf(timeToGenerateE/1000);
	}

	public BigInteger getTimeToComputeD()
	{
		return BigInteger.valueOf(timeToComputeD/1000);
	}
	public BigInteger getTimeToEncrypt()
	{
		return BigInteger.valueOf(timeToEncrypt/1000);
	}
	public BigInteger getTimeToDecrypt()
	{
		return BigInteger.valueOf(timeToDecrypt/1000);
	}
        /**
         * RSA Main program for Unit Testing.
         */
        public static void main( String[] args ) throws IOException
        {
		ReadFromFile rdFile;
		WriteToFile wrtFile;
                if( args.length != 5 )
                {
                        System.out.println( "Syntax: java RSA PrimeSize Certainty MessageLen logFileName MessageFile" ) ;
			return ;
                }

		

                // Get bit length of each prime number
                int primeSize	  = Integer.parseInt( args[0] );
		int certaintySize = Integer.parseInt( args[1] );
		int messageSize   = Integer.parseInt( args[2] );
	



                // Generate Public and Private Keys

                RSA rsa = new RSA( primeSize, certaintySize, messageSize, args[3], args[4] ) ;
		
		wrtFile = new WriteToFile(args[3]);
		rdFile = new ReadFromFile(args[4]);

		// Write KeySize, Certainty, MessageSize
		wrtFile.write(primeSize+"\t");
		wrtFile.write(certaintySize+"\t");
		wrtFile.write(messageSize+"\t");

		

		String plaintext = rdFile.read(messageSize);

                // Encrypt Message
                BigInteger[] ciphertext = rsa.encrypt( plaintext ) ;

                for( int i = 0 ; i < ciphertext.length ; i++ )
                {

                        if( i != ciphertext.length - 1 );
                }

                String recoveredPlaintext = rsa.decrypt( ciphertext ) ;


		// Now write all the time taken values by the program in
		// the file.

		wrtFile.write(rsa.getTimeToGeneratePQ().toString()+"\t\t");
		wrtFile.write(rsa.getTimeToComputeN().toString()+"\t");
		wrtFile.write(rsa.getTimeToGenerateE().toString()+"\t");
		wrtFile.write(rsa.getTimeToComputeD().toString()+"\t");
		wrtFile.write(rsa.getTimeToEncrypt().toString()+"\t");
		wrtFile.write(rsa.getTimeToDecrypt().toString()+"\n");
		
                                                           

		wrtFile.close();
		rdFile.close();

        }
}






