|
|
|
@ -0,0 +1,450 @@
|
|
|
|
|
package RSA;
|
|
|
|
|
|
|
|
|
|
import java.math.BigInteger;
|
|
|
|
|
import java.util.Base64;
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author Alexander Kimmig
|
|
|
|
|
*/
|
|
|
|
|
public class RSA {
|
|
|
|
|
/**
|
|
|
|
|
* first prime number
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger p;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* second prime number
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger q;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* modulus p*q
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger N;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* euler phi-function
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger phi;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* exponent for encryption
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger e;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* exponent for decryption
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger d;
|
|
|
|
|
|
|
|
|
|
private static final BigInteger B0 = new BigInteger("0");
|
|
|
|
|
private static final BigInteger B1 = new BigInteger("1");
|
|
|
|
|
private static final BigInteger B2 = new BigInteger("2");
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* default constructor
|
|
|
|
|
*/
|
|
|
|
|
public RSA() {
|
|
|
|
|
this.p = null;
|
|
|
|
|
this.q = null;
|
|
|
|
|
this.N = null;
|
|
|
|
|
this.phi = null;
|
|
|
|
|
this.e = null;
|
|
|
|
|
this.d = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* constructor with setting prime numbers manually
|
|
|
|
|
* @param p first prime number
|
|
|
|
|
* @param q second prime number
|
|
|
|
|
* @param e exponent (coprime to phi)
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
public RSA(String p, String q, String e) throws Exception {
|
|
|
|
|
this.p = new BigInteger(p);
|
|
|
|
|
this.q = new BigInteger(q);
|
|
|
|
|
this.N = this.p.multiply(this.q);
|
|
|
|
|
this.phi = this.p.subtract(B1).multiply(this.q.subtract(B1));
|
|
|
|
|
this.e = new BigInteger(e);
|
|
|
|
|
this.d = this.findd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* auto-generate a public-private keypair with 1024 bits
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
public void generatePair() throws Exception { this.generatePair(1024); }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* auto-generate a public-private keypair
|
|
|
|
|
* @param bits bitlength for prime numbers
|
|
|
|
|
* @param ebits bitlength for encryption exponent
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
public void generatePair(int bits) throws Exception {
|
|
|
|
|
Random rand = new Random();
|
|
|
|
|
int lengthp = (bits*(90+rand.nextInt(20))/200);
|
|
|
|
|
int lengthq = bits - lengthp;
|
|
|
|
|
|
|
|
|
|
this.p = new BigInteger(RSA.generatePrime(lengthp));
|
|
|
|
|
|
|
|
|
|
while (this.N == null || this.N.bitLength() != bits) {
|
|
|
|
|
if (this.N != null && this.N.bitLength() < bits) lengthq++;
|
|
|
|
|
else if (this.N != null && this.N.bitLength() > bits) lengthq--;
|
|
|
|
|
|
|
|
|
|
this.q = new BigInteger(RSA.generatePrime(lengthq));
|
|
|
|
|
this.N = this.p.multiply(this.q);
|
|
|
|
|
}
|
|
|
|
|
this.phi = this.p.subtract(B1).multiply(this.q.subtract(B1));
|
|
|
|
|
this.e = new BigInteger("65537");
|
|
|
|
|
this.d = this.findd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* manually set private key
|
|
|
|
|
* @param d exponent for decryption
|
|
|
|
|
* @param N RSA modulus
|
|
|
|
|
*/
|
|
|
|
|
public void setPrivate(String d, String N) {
|
|
|
|
|
this.d = new BigInteger(d);
|
|
|
|
|
this.N = new BigInteger(N);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* manually set public key
|
|
|
|
|
* @param d exponent for encryption
|
|
|
|
|
* @param N RSA modulus
|
|
|
|
|
*/
|
|
|
|
|
public void setPublic(String e, String N) {
|
|
|
|
|
this.e = new BigInteger(e);
|
|
|
|
|
this.N = new BigInteger(N);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* helper-class for return-values for extended euclidean algorithm
|
|
|
|
|
* @author Alexander Kimmig
|
|
|
|
|
*/
|
|
|
|
|
private static class eeareturn {
|
|
|
|
|
BigInteger s;
|
|
|
|
|
BigInteger t;
|
|
|
|
|
|
|
|
|
|
public eeareturn(BigInteger s, BigInteger t) {
|
|
|
|
|
this.s = s;
|
|
|
|
|
this.t = t;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* executes the extended euclidean algorithm recursively
|
|
|
|
|
* @param a first number
|
|
|
|
|
* @param b second number
|
|
|
|
|
* @return eeareturn with s and t
|
|
|
|
|
*/
|
|
|
|
|
private static eeareturn eea(BigInteger a, BigInteger b) {
|
|
|
|
|
if (b.compareTo(a) > 0) {
|
|
|
|
|
eeareturn tmp = eea(b, a);
|
|
|
|
|
return new eeareturn(tmp.t, tmp.s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BigInteger q = a.divide(b);
|
|
|
|
|
BigInteger r = a.mod(b);
|
|
|
|
|
|
|
|
|
|
if (r.compareTo(B0) == 0) {
|
|
|
|
|
// System.out.println(a + ", " + b + ", " + q + ", " + r + ", 0, 1");
|
|
|
|
|
return new eeareturn(B0,B1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eeareturn alt = eea(b, r);
|
|
|
|
|
|
|
|
|
|
// System.out.println(a + ", " + b + ", " + q + ", " + r + ", " + alt.t + ", " + (alt.s-q*alt.t));
|
|
|
|
|
return new eeareturn(alt.t, alt.s.subtract(q.multiply(alt.t)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* calculates the inverse d to e mod phi
|
|
|
|
|
* @return exponent for decryption for given encryption-exponent
|
|
|
|
|
* @throws Exception if phi not available
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger findd() throws Exception {
|
|
|
|
|
if (this.phi == null) {
|
|
|
|
|
throw new Exception("you don't have the prime numbers!");
|
|
|
|
|
}
|
|
|
|
|
return (RSA.eea(this.e,this.phi).s.add(this.phi)).mod(this.phi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* calculates the inverse c to q mod p
|
|
|
|
|
* @return coefficient
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
private BigInteger findcoefficient() throws Exception {
|
|
|
|
|
if (this.p == null) {
|
|
|
|
|
throw new Exception("you don't have the prime numbers!");
|
|
|
|
|
}
|
|
|
|
|
return (RSA.eea(this.q,this.p).s.add(this.p)).mod(this.p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* calculating the power to a given modulus
|
|
|
|
|
* @param b base
|
|
|
|
|
* @param e exponent
|
|
|
|
|
* @param mod modulus
|
|
|
|
|
* @return the result
|
|
|
|
|
*/
|
|
|
|
|
private static BigInteger modexp(BigInteger b, BigInteger e, BigInteger mod) {
|
|
|
|
|
BigInteger ret = B1;
|
|
|
|
|
|
|
|
|
|
while (e.compareTo(B0)>0) {
|
|
|
|
|
if (e.mod(B2).compareTo(B1) == 0) {
|
|
|
|
|
ret = ret.multiply(b).mod(mod);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b = b.multiply(b).mod(mod);
|
|
|
|
|
|
|
|
|
|
e = e.divide(B2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* encrypt a number
|
|
|
|
|
* @param m number within a String (automatically convert to BigInteger)
|
|
|
|
|
* @return the encypted number
|
|
|
|
|
* @throws Exception if no public key available
|
|
|
|
|
*/
|
|
|
|
|
public String encrypt(String m) throws Exception {
|
|
|
|
|
if (this.e == null || this.N == null) {
|
|
|
|
|
throw new Exception("you don't have a valid public key!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RSA.modexp(new BigInteger(m), this.e, this.N).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* decrypt a number
|
|
|
|
|
* @param m number within a String (automatically convert to BigInteger)
|
|
|
|
|
* @return the decrypted number
|
|
|
|
|
* @throws Exception if no private key available
|
|
|
|
|
*/
|
|
|
|
|
public String decrypt(String m) throws Exception {
|
|
|
|
|
if (this.d == null || this.N == null) {
|
|
|
|
|
throw new Exception("you don't have a valid private key!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RSA.modexp(new BigInteger(m), this.d, this.N).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* helper-function for printing public and private key
|
|
|
|
|
*/
|
|
|
|
|
public String toString() {
|
|
|
|
|
String ret = "";
|
|
|
|
|
|
|
|
|
|
if (this.e != null && this.N !=null) {
|
|
|
|
|
ret += "public key ("+this.e.toString()+","+this.N.toString()+")";
|
|
|
|
|
}
|
|
|
|
|
if (this.d != null && this.N !=null) {
|
|
|
|
|
if (ret != "") ret += "\n";
|
|
|
|
|
ret += "private key ("+this.d.toString()+","+this.N.toString()+")";
|
|
|
|
|
}
|
|
|
|
|
if (ret == "") {
|
|
|
|
|
ret += "no key available!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Miller-Rabin-Test for finding large prime numbers
|
|
|
|
|
* @param n number to test for prime
|
|
|
|
|
* @return true if probably prime
|
|
|
|
|
*/
|
|
|
|
|
private static boolean isProbablyPrime(BigInteger n) {
|
|
|
|
|
int j = 0;
|
|
|
|
|
BigInteger d = n.subtract(B1);
|
|
|
|
|
|
|
|
|
|
if (n.compareTo(new BigInteger("5")) < 0) return false;
|
|
|
|
|
|
|
|
|
|
while(d.mod(B2).compareTo(B0) == 0) {
|
|
|
|
|
j++;
|
|
|
|
|
d = d.divide(B2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int[] _a = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71};
|
|
|
|
|
|
|
|
|
|
for(int i=0; i<_a.length; i++) {
|
|
|
|
|
BigInteger a = new BigInteger(""+_a[i]);
|
|
|
|
|
if (a.compareTo(n.subtract(B2)) > 0) break;
|
|
|
|
|
|
|
|
|
|
boolean isPrime = false;
|
|
|
|
|
|
|
|
|
|
// System.out.println(a+"^"+d+"%"+n+"="+RSA.modexp(a, d, n));
|
|
|
|
|
if (RSA.modexp(a, d, n).compareTo(B1) == 0) isPrime = true;
|
|
|
|
|
|
|
|
|
|
if (isPrime) continue;
|
|
|
|
|
|
|
|
|
|
for(int r = 0; r<j; r++) {
|
|
|
|
|
BigInteger exp = d.multiply(B2.pow(r));
|
|
|
|
|
// System.out.println(a+"^"+exp+"%"+n+"="+RSA.modexp(a, exp, n));
|
|
|
|
|
if (RSA.modexp(a, exp, n).compareTo(n.subtract(B1)) == 0) {
|
|
|
|
|
isPrime = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isPrime) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isPrime) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* find a prime number
|
|
|
|
|
* @param bits length of the prime number
|
|
|
|
|
* @return prime number
|
|
|
|
|
*/
|
|
|
|
|
public static String generatePrime(int bits) {
|
|
|
|
|
Random rand = new Random();
|
|
|
|
|
BigInteger prime = new BigInteger(bits, rand);
|
|
|
|
|
|
|
|
|
|
// System.out.println(isProbablyPrime(new BigInteger("17")));
|
|
|
|
|
|
|
|
|
|
// System.out.println("trying " + prime);
|
|
|
|
|
while(!isProbablyPrime(prime)) {
|
|
|
|
|
prime = new BigInteger(bits, rand);
|
|
|
|
|
// System.out.println("trying " + prime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return prime.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* generates the content for the public key file
|
|
|
|
|
* @return String
|
|
|
|
|
*/
|
|
|
|
|
public String toPublicFileString() {
|
|
|
|
|
String ret = "";
|
|
|
|
|
|
|
|
|
|
// add sequence
|
|
|
|
|
{
|
|
|
|
|
String seqdata = "";
|
|
|
|
|
|
|
|
|
|
// add RSA identifier
|
|
|
|
|
seqdata += asn1field("06", "2A864886F70D010101");
|
|
|
|
|
|
|
|
|
|
// add NULL
|
|
|
|
|
seqdata += "0500";
|
|
|
|
|
|
|
|
|
|
ret += asn1field("30", seqdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add bit string
|
|
|
|
|
{
|
|
|
|
|
String bsdata = "";
|
|
|
|
|
// add sequence with modulus and exponent
|
|
|
|
|
{
|
|
|
|
|
String seqdata = "";
|
|
|
|
|
// add modulus
|
|
|
|
|
seqdata += asn1field("02", "00" + this.N.toString(16));
|
|
|
|
|
|
|
|
|
|
// add exponent
|
|
|
|
|
seqdata += asn1field("02", this.e.toString(16));
|
|
|
|
|
|
|
|
|
|
bsdata += asn1field("30", seqdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret += asn1field("03", "00" + bsdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "-----BEGIN PUBLIC KEY-----\n" + Base64.getEncoder().encodeToString(hex2byte(asn1field("30", ret))).replaceAll("(.{64})", "$1\n") + "\n-----END PUBLIC KEY-----";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* generates the content for the private key file
|
|
|
|
|
* @return String
|
|
|
|
|
*/
|
|
|
|
|
public String toPrivateFileString() throws Exception {
|
|
|
|
|
String ret = "";
|
|
|
|
|
|
|
|
|
|
// add 0
|
|
|
|
|
ret += asn1field("02", "00");
|
|
|
|
|
|
|
|
|
|
// add N
|
|
|
|
|
ret += asn1field("02", "00" + this.N.toString(16));
|
|
|
|
|
|
|
|
|
|
// add e
|
|
|
|
|
ret += asn1field("02", this.e.toString(16));
|
|
|
|
|
|
|
|
|
|
// add d
|
|
|
|
|
ret += asn1field("02", "00" + this.d.toString(16));
|
|
|
|
|
|
|
|
|
|
// add p
|
|
|
|
|
ret += asn1field("02", "00" + this.p.toString(16));
|
|
|
|
|
|
|
|
|
|
// add q
|
|
|
|
|
ret += asn1field("02", "00" + this.q.toString(16));
|
|
|
|
|
|
|
|
|
|
// add d%(p-1)
|
|
|
|
|
ret += asn1field("02", "00" + this.d.mod(this.p.subtract(new BigInteger("1"))).toString(16));
|
|
|
|
|
|
|
|
|
|
// add d%(q-1)
|
|
|
|
|
ret += asn1field("02", "00" + this.d.mod(this.q.subtract(new BigInteger("1"))).toString(16));
|
|
|
|
|
|
|
|
|
|
// add coefficient
|
|
|
|
|
ret += asn1field("02", this.findcoefficient().toString(16));
|
|
|
|
|
|
|
|
|
|
return "-----BEGIN RSA PRIVATE KEY-----\n" + Base64.getEncoder().encodeToString(hex2byte(asn1field("30", ret))).replaceAll("(.{64})", "$1\n") + "\n-----END RSA PRIVATE KEY-----";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* generating a ASN 1 entry
|
|
|
|
|
* @param type Type of the ASN 1 field
|
|
|
|
|
* @param content Content of the ASN 1 field
|
|
|
|
|
* @return the encoded string (hex-number as string)
|
|
|
|
|
*/
|
|
|
|
|
private static String asn1field(String type, String content) {
|
|
|
|
|
String ret = type;
|
|
|
|
|
content = fill(content);
|
|
|
|
|
int length = content.length();
|
|
|
|
|
|
|
|
|
|
if (length < 128) {
|
|
|
|
|
ret += fill(Integer.toHexString(length/2)).toUpperCase();
|
|
|
|
|
} else {
|
|
|
|
|
String bytelength = fill(Integer.toHexString(length/2));
|
|
|
|
|
int bytes = bytelength.length()/2;
|
|
|
|
|
ret += "8" + bytes + bytelength;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret += content.toUpperCase();
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* helper-function for filling up a hex-string with a leading zero
|
|
|
|
|
* @param s hex-string
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
private static String fill(String s) {
|
|
|
|
|
if (s.length() % 2 == 0) return s;
|
|
|
|
|
return "0" + s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* convert a hex-string to a byte-array
|
|
|
|
|
* @param hex
|
|
|
|
|
* @return byte[]
|
|
|
|
|
*/
|
|
|
|
|
private static byte[] hex2byte(String hex) {
|
|
|
|
|
byte[] b = new byte[hex.length()/2];
|
|
|
|
|
|
|
|
|
|
for (int i=0; i<hex.length(); i+=2) {
|
|
|
|
|
b[i/2] = (byte) Integer.parseInt("" + hex.charAt(i)+hex.charAt(i+1), 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
}
|