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.
 *
 * @author  Chue Wai Lian
 * @version
 *
 * 1.0.0	11 Apr 2001
 * <br>		1st release.
 */
public class RSA
{
	/**
	 * 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 )
	{
		this.primeSize = primeSize ;

		// 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()
	{
            for(int cert=8;cert<=16;cert+=2)
	    {
                long start=getCurrentTime();
		p = new BigInteger( primeSize, cert, new Random() ) ;


		do
		{
			q = new BigInteger( primeSize, cert, new Random() ) ;
		}
		while( q.compareTo( p ) == 0 ) ;
           
	    	long end=getCurrentTime();
	    	long diff=end-start;
            	System.out.println("Total elapsed time with certainity : "+cert+" = " + diff);
            
            }         
	}
        
	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 * q
		long start=getCurrentTime();
		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 ) ;
		long end=getCurrentTime();
		long diff=end-start;
		System.out.println("PUB-PRIV Time: "+ diff);
	}


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

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


		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 ;


		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() ) ;


		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
	{
		// I modified here
		// Initialize variables like key sizes,text
		
		int a[]=new int[5];
		int primeSize;
		long start,end,diff;	
		String plaintext[] = new String[2];
		
		a[0]=128; a[1]=256; a[2]=512; a[3]=768; a[4]=1024;
		plaintext[0]=new String("1234567890abcdefghijklmnopqrstuvwxyz");
		plaintext[1]=new String("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz");
		
		// Run the loop for two different texts
		
		for(int text=0;text<2;text++)
		{
		  // Run the loop for two different key sizes 
		  for(int ps=0;ps<=4;ps++)
		  {
		    // Generate Public and Private Keys	
	 	
	  	    primeSize=a[ps];
		    System.out.println( "\n\t\tKey Size: [" + primeSize + "]" ) ;
		    System.out.println( "" ) ;
		    RSA rsa = new RSA(primeSize);
		 
		    // Encrypt Message
		    start=rsa.getCurrentTime();
		    BigInteger[] ciphertext = rsa.encrypt(plaintext[text]);
		    end=rsa.getCurrentTime();
		    diff=end-start;
		    System.out.println("Encryption : " + diff);
		
		    // Decrypt Message
		    start=rsa.getCurrentTime();
		    String recoveredPlaintext = rsa.decrypt( ciphertext ) ;
		    end=rsa.getCurrentTime();
		    diff=end-start;
		    System.out.println("Decryption : " + diff);
		    System.out.println( "Recovered plaintext: [" + recoveredPlaintext + "]" ) ;

		  }

	        }
	}
}

