/* RSA.java             nov 2000
 *
 * Erwan Lemonnier & Eric Nordenstam
 *
 * program to encrypt and decrypt files using RSA, with 1024 bits keys.
 * Uses a personal implementation for some operations on big numbers
 * (see Math.java). 
 ***********************************************************************/

import java.math.BigInteger;
import java.util.Random;

/**
 * Program to encrypt and decrypt files using the RSA algorithm.
 * This program accepts arguments from the command line and can be run in 3 
 * ways:
 * <UL>
 * <LI>
 * <b><code>java RSA -mk [username] </code></b>: create a pair of public and private key,
 * and a modulus for this <code>username</code>, and stores them in 3 files in
 * the local directory.  
 * </LI>
 * <LI>
 * <b><code>java RSA -e [username] [message_file] [encoded_file] </code></b>: uses the
 * private key of <code>username</code> to crypt the file <code>message_file</code>.
 * The crypted message is stored in the file <code>encoded_file</code>.
 * </LI>
 * <LI>
 * <b><code>java RSA -d [username] [encoded_file] [message_file] </code></b>: uses the
 * public key of <code>username</code> to decrypt the file <code>encoded_file</code>.
 * The resulting clear file is stored in <code>encoded_file</code>.
 * </LI>
 * </UL>
 * RSA uses the functions implemented in class <code>Math</code>.
 * The keys are large prime of 1024 bits.
 * <p>
 * @author E.Lemonnier & E.Nordenstam
 */

public class RSA {
	
	public static final int SIZE_KEY = 512;
	private static final int CERTAINTY = 3;	//2 enough for keys of 512 bits
	
	private static final BigInteger ONE = new BigInteger("1");
	private static final BigInteger TWO = new BigInteger("2");

	/**
	 * Crypt/Decrypt a file, or generate keys for a user.
	 * See on top of this page for a detailed description.
	 *
	 * @param see the description above.
	 */
	public static void main(String[] arg) {
		
		if (arg.length < 2) {
			showHelp();
		}
		
		if (arg[0].equals("-mk")) {
			if (arg.length>1) {
				makeKeys(arg[1]);
				System.exit(0);
			}
		}
		
		if (arg[0].equals("-e") || arg[0].equals("-d")) {
			if (arg.length<4) {
				showHelp();
				System.exit(0);
			}
			
			try {
				KeyLoader kl = new KeyLoader(arg[1]);
				DataLoader dl = new DataLoader(arg[2], arg[3]);
				if (arg[0].equals("-e"))
					encryptData(kl,dl);
				else
					decryptData(kl,dl);
			} catch(Exception e) {
				System.out.println("Error: "+e.getMessage());
			}
		} else
			showHelp();
	}
		
		
		
	private static void showHelp()	{
		System.out.println("RSA implemented by E. Lemonnier & E. Nordenstam - Nov 2000");
		System.out.println("usage: java RSA -mk <user_name>");
		System.out.println("       java RSA -e <user_name> <message_file> <cypher_file>");
		System.out.println("       java RSA -d <user_name> <cypher_file> <message_file>");
		System.out.println("options:");
		System.out.println("  -mk : create private and public key for user <username>");
		System.out.println("        as well as public modulus.");
		System.out.println("  -e : encrypt <message_file> with <user_name>'s public key");
		System.out.println("       and store the result in <result_file>");
		System.out.println("  -d : decrypt <message_file> with <user_name>'s private key");
		System.out.println("       and store the result in <result_file>");
		System.exit(0);
	}
	
	//create public & private keys, and modulus
	private static void makeKeys(String username) {
		System.out.println("# generating keys for user "+username);
		BigInteger[] b = keys();
		
		System.out.println("# saving keys to files");
		KeyLoader kl = new KeyLoader(username);
		kl.savePrivateKey(b[0]);
		kl.savePublicKey(b[1]);
		kl.saveModulus(b[2]);
		
		System.out.println("# Properties:");
		System.out.println("\tsize of modulus: "+b[2].bitLength()+" bits");
		System.out.println("\tsize of (private) encryption key : "+b[0].bitLength()+" bits");
		System.out.println("\tsize of (public) decryption key :  "+b[1].bitLength()+" bits");
	}
	
	//encrypt a file
	private static void encryptData(KeyLoader kl, DataLoader dl) {
		BigInteger e = kl.loadPrivateKey();
		BigInteger n = kl.loadModulus();
		BigInteger m;
		
		try {	
			while(true) {
				m = dl.readClearBlock();
				m = Math.modPow(m,e,n);
				dl.writeEncryptedBlock(m);
				System.out.print(".");
			}
		} catch (Exception ex) {
			dl.close();
		}		
	}
	
	//decrypt a file
	private static void decryptData(KeyLoader kl, DataLoader dl) {
		BigInteger d = kl.loadPublicKey();
		BigInteger n = kl.loadModulus();
		BigInteger m;

		try {	
			while(true) {
				m = dl.readEncryptedBlock();
				m = Math.modPow(m,d,n);
				dl.writeClearBlock(m);
				System.out.print(".");
			}
		} catch (Exception ex) {
			dl.close();
		}	
	}
	
	/**
	 * Encrypt a message with RSA. In fact compute <code> message ^ key mod modulus</code>.
	 * <p>
	 * @param message the message to encrypt.
	 * @param key the key used to crypt the message.
	 * @param modulus key modulus.
	 * @return the encoded message.
	 */
	public static BigInteger encrypt(BigInteger message, BigInteger key, BigInteger modulus) {
		return Math.modPow(message,key,modulus);
	}

	/**
	 * Decrypt a message with RSA. In fact compute <code> cypher ^ key mod modulus</code>.
	 * <p>
	 * @param cypher the cypher to decrypt.
	 * @param key the key used to crypt the message.
	 * @param modulus key modulus.
	 * @return the decrypted message.
	 */
	public static BigInteger decrypt(BigInteger cypher, BigInteger key, BigInteger modulus) {
		return Math.modPow(cypher,key,modulus);
	}
	
	
	/**
	 * Generate a pair of private and public keys, and a modulus. The result
	 * is an array of BigIntegers of this form:
	 * <code>[private_key, public_key, modulus]</code>.
	 * <p>
	 * @return the 2 keys and the modulus.
	 */
	public static BigInteger[] keys() {
		BigInteger[] result = new BigInteger[3];
		
		/*
		 * COMPUTE P, Q, N, F(N)=(P-1)(Q-1)
		 */		
		Random rand = new Random();
		BigInteger n = ONE;
		BigInteger p = ONE;		//the 2 large prime used to
		BigInteger q = ONE;		//generate n and f(n)
		
		//to be sure n is bigger than 2^SIZE_BLOCK,
		//ie bigger than any message to encrypt
		while (n.bitLength() < 2*SIZE_KEY-8 || n.bitLength()>=2*SIZE_KEY) {
			System.out.println("\tCompute p");
			p = Math.makePrime(SIZE_KEY-1, CERTAINTY);		//-1 to avoid n >= 1024 bits
			System.out.println("\tCompute q");
			q = Math.makePrime(SIZE_KEY, CERTAINTY);
			System.out.println("\tCompute n=p.q");
			n = p.multiply(q);
		}
		
		System.out.println("\tCompute f(n)=(p-1).(q-1)");
		BigInteger fn = (p.subtract(ONE)).multiply(q.subtract(ONE));
			
		/*
		 * COMPUTE PRIVATE & PUBLIC KEY
		 */
		//make sure that d is not << sqrt(n)
		System.out.println("\tCompute public key d");
		int l = n.bitLength()-4;
		
		BigInteger d = new BigInteger(l, new Random());
		while (d.bitLength() < l) {
			d = new BigInteger(l, new Random());
		}
		
		//adjust d to let it have an inverse
		while (Math.gcd(d,fn).compareTo(ONE)!=0) {
			d=d.add(ONE);
		}
		
		System.out.println("\tCompute private key e");
		BigInteger e = Math.modInverse(d, fn);
		
		result[0] = e;
		result[1] = d;
		result[2] = n;
		
		return result;
	}
}