/*
Pankaj Gulhane
03329007
it653: assignment no 2
*/

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.
 */
public class RSA
{
    int primeSize ;     //  Bit length of each prime number.
    int msgSize;
    int probFactor;
    long time2Gen1, time2Gen2;
    long time2Encrypt, time2Decrypt;
    long startTime, endTime;


    BigInteger p, q ;	// Two distinct large prime numbers p and q
    BigInteger N ;      // Modulus N.
    BigInteger r ;      //  r = ( p - 1 ) * ( q - 1 )
    BigInteger E, D ;   // Public exponent E and Private exponent D

    /*** Constructor. */
    public RSA( int primeSize , int fProbSize)
    {
	this.primeSize = primeSize ;
	this.probFactor = fProbSize;

	generatePrimeNumbers() ;        // Generate two distinct large prime numbers p and q.
	generatePublicPrivateKeys() ;   // Generate Public and Private Keys.
    }


    /**
     * Generate two distinct large prime numbers p and q.
     */
    public void generatePrimeNumbers()
    {
	long start=getCurrentTime();
	startTime= start;
	p = new BigInteger( primeSize, 10, new Random() ) ;
	long end=getCurrentTime();
	time2Gen1=end - start;
	
	start=end;

	do
	    {
		q = new BigInteger( primeSize, probFactor, new Random() ) ;
	    }
	while( q.compareTo( p ) == 0 ) ;

	end=getCurrentTime();
	time2Gen2= end - start;
    }

    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()
    {
	N = p.multiply( q ) ;     // N = p * 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 ) ) ;

	D = E.modInverse( r ) ;// Compute D, the inverse of E mod 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 )
    {
	int i ;
	byte[] temp = new byte[1] ;
	byte[] digits = message.getBytes() ;

	msgSize= message.length();
	long start=getCurrentTime();

	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] ;

	for( i = 0 ; i < bigdigits.length ; i++ )
	    encrypted[i] = bigdigits[i].modPow( E, N ) ;

	long end=getCurrentTime();
	time2Encrypt= end- start;
	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=getCurrentTime();
	BigInteger[] decrypted = new BigInteger[encrypted.length] ;

	for( i = 0 ; i < decrypted.length ; i++ )
	    decrypted[i] = encrypted[i].modPow( D, N ) ;

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

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


	long end=getCurrentTime();
	time2Decrypt= end-start;
	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 ) ;
    }

    /**
     * RSA Main program for Unit Testing.
     */
    public static void main( String[] args ) throws IOException
    {
	int[] keyArray= new int[5];
	keyArray[0]=128;
	keyArray[1]=256;
	keyArray[2]=512;
	keyArray[3]=768;
	keyArray[4]=1024;

	int[] proArray= new int[3];
	proArray[0]=10;
	proArray[1]=8;
	proArray[2]=6;

	// Get message (plaintext) from user
	//	System.out.println( "Taking new input  (plaintext):" ) ;
	String plaintext = ( new BufferedReader( new InputStreamReader( System.in ) ) ).readLine() ;
	System.out.println(" Key Size\tmsgSize\tprobFactor\ttime2Gen1\ttime2Gen2\ttime2Encrypt\ttime2Decrypt");
	

	for(int i=0; i<5; ++i)
	    {
		for( int j=0; j<3; ++j )
		    {
			// Generate Public and Private Keys
			RSA rsa = new RSA( keyArray[i],  proArray[j]) ;

			// Encrypt Message
			BigInteger[] ciphertext = rsa.encrypt( plaintext ) ;
			// Decrypt Message
			String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
	
			System.out.println(rsa.primeSize+"\t\t"+rsa.msgSize+"\t\t"+rsa.probFactor+"\t\t"+rsa.time2Gen1+"\t\t"+rsa.time2Gen2+"\t\t"+rsa.time2Encrypt+"\t\t"+rsa.time2Decrypt );
		    }
	    }
    }
}
