
// RSA.java
// Andrew Davison, ad@coe.psu.ac.th, April 2023

/* RSA involves a public key and a private key. 
   The public key is represented by the integers n and e, 
   and the private key by the integer d. The message is m.

   The basic principle is to find three very large 
   positive integers e, d, and n, such that with modular exponentiation 
   for all integers m (with 0 <= m < n):
          (m^e)^d  ==  m (mod n)

   and that knowing e and n, it can be extremely difficult to find d. 

   https://en.wikipedia.org/wiki/RSA_(cryptosystem)

   Usage:
     > compile RSA.java
     > run RSA <message>
   e.g.
     > run RSA "Hide in plain sight."
*/

import javax.swing.*;
import java.math.BigInteger;
import java.security.SecureRandom;


public final class RSA
{

  public static void main(String[] args)
  {
    if (args.length != 1) {
      System.out.println("Usage: run RSA <message>");
      return;
    }
    System.out.println("Message: \"" + args[0] + "\"");

    BigInteger[] keys = genKeys(1024);
       // [modulus (n), public key (e), private key (d)]

    String cipher = encode(args[0], keys[1], keys[0]);
    System.out.println("\nEncrypted message : \"" + cipher + "\"");

    String decipher = decode(cipher, keys[2], keys[0]);
    System.out.println("\nDecrypted message : \"" + decipher + "\"");
  } /* main */



  public static BigInteger[] genKeys(int numBits)
  /* Generate a public and private key pair (e,d), and modulus (n)
         https://en.wikipedia.org/wiki/RSA_(cryptosystem) 
     See the "key generation" section.
  */
  {
    SecureRandom rand = new SecureRandom();
    BigInteger p = new BigInteger(numBits/2, 100, rand);
    BigInteger q = new BigInteger(numBits/2, 100, rand);
        // guaranteed to be 'probably' prime

    BigInteger modulus = p.multiply(q);        // n

    BigInteger m = (p.subtract(BigInteger.ONE)).
            multiply(q.subtract(BigInteger.ONE));
             // (p-1)*(q-1); Euler totient function

    BigInteger pubKey = new BigInteger("3");   // e; simple starting choice
    while (m.gcd(pubKey).intValue() > 1)
      pubKey = pubKey.add(new BigInteger("2"));
    // increase pubkey until gcd(pubkey, m) == 1, coprime

    BigInteger priKey = pubKey.modInverse(m);  // d
           // modular mmultiplicative inverse

    return new BigInteger[] {modulus, pubKey, priKey};
                         // { n, e, d}
  } /* genKeys */



  public static String encode(String msg,
                 BigInteger pubKey, BigInteger modulus)  // e and n
  {  
    BigInteger msgVal = new BigInteger(msg.getBytes());
    BigInteger msgMP = msgVal.modPow(pubKey, modulus);
                         // (msgVal ^ pubkey) mod modulus
    return msgMP.toString();
  }  // end of encode()



  public static String decode(String msg,
             BigInteger priKey, BigInteger modulus)  // d and n
  {  
    BigInteger msgVal = new BigInteger(msg);
    BigInteger msgMP = msgVal.modPow(priKey, modulus);
                         // (msgVal ^ prikey) mod modulus
    String decrypt = new String( msgMP.toByteArray());
    return decrypt;
  } // end of decode()


}  // end of RSA class
