package com.laytonsmith.PureUtilities.Common;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Objects;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.binary.Base64;
/**
* Given a public/private key pair, this class uses RSA to encrypt/decrypt data.
*/
public class RSAEncrypt {
/**
* The RSA algorithm key.
*/
private static final String ALGORITHM = "RSA";
/**
* Generates a new key, and stores the value in the RSA
*
* @param label The label that will be associated with the public key
* @return
*/
public static RSAEncrypt generateKey(String label) {
KeyPairGenerator keyGen;
try {
keyGen = KeyPairGenerator.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
RSAEncrypt enc = new RSAEncrypt(toString(key.getPrivate()), toString(key.getPublic(), label));
return enc;
}
/**
* Given a public key and a label, produces an ssh compatible rsa public key
* string.
*
* @param key
* @param label
* @return
*/
public static String toString(PublicKey key, String label) {
Objects.requireNonNull(label);
ByteArrayOutputStream pubBOS = new ByteArrayOutputStream();
try {
ObjectOutputStream publicKeyOS = new ObjectOutputStream(pubBOS);
publicKeyOS.writeObject(key);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
String publicKey = Base64.encodeBase64String(pubBOS.toByteArray());
publicKey = "ssh-rsa " + publicKey + " " + label;
return publicKey;
}
/**
* Given a private key, produces an ssh compatible rsa private key string.
*
* @param key
* @return
*/
private static String toString(PrivateKey key) {
ByteArrayOutputStream privBOS = new ByteArrayOutputStream();
ObjectOutputStream privateKeyOS;
try {
privateKeyOS = new ObjectOutputStream(privBOS);
privateKeyOS.writeObject(key);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
String privateKey = Base64.encodeBase64String(privBOS.toByteArray());
StringBuilder privBuilder = new StringBuilder();
privBuilder.append("-----BEGIN RSA PRIVATE KEY-----");
for (int i = 0; i < privateKey.length(); i++) {
if (i % 64 == 0) {
privBuilder.append(StringUtils.nl());
}
privBuilder.append(privateKey.charAt(i));
}
privBuilder.append(StringUtils.nl()).append("-----END RSA PRIVATE KEY-----").append(StringUtils.nl());
privateKey = privBuilder.toString();
return privateKey;
}
private PublicKey publicKey;
private PrivateKey privateKey;
private String label;
/**
* Creates a new RSAEncrypt object, based on the ssh compatible
* private/public key pair. Only one key needs to be provided. If so, only
* those methods for the key provided will work.
*
* @param privateKey
* @param publicKey
* @throws IllegalArgumentException If the keys are not the correct type.
* They must be ssh compatible.
*/
public RSAEncrypt(String privateKey, String publicKey) throws IllegalArgumentException {
if (privateKey != null) {
//private key processing
//replace all newlines with nothing
privateKey = privateKey.replaceAll("\r", "");
privateKey = privateKey.replaceAll("\n", "");
//Remove the BEGIN/END tags
privateKey = privateKey.replace("-----BEGIN RSA PRIVATE KEY-----", "");
privateKey = privateKey.replace("-----END RSA PRIVATE KEY-----", "");
ObjectInputStream privOIS;
try {
privOIS = new ObjectInputStream(new ByteArrayInputStream(Base64.decodeBase64(privateKey)));
this.privateKey = (PrivateKey) privOIS.readObject();
} catch (IOException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
if (publicKey != null) {
//public key processing
String[] split = publicKey.split(" ");
if (split.length != 3) {
throw new IllegalArgumentException("Invalid public key passed in.");
}
if (!"ssh-rsa".equals(split[0])) {
throw new IllegalArgumentException("Invalid public key type. Expecting ssh-rsa, but found \"" + split[0] + "\"");
}
this.label = split[2];
ObjectInputStream pubOIS;
try {
pubOIS = new ObjectInputStream(new ByteArrayInputStream(Base64.decodeBase64(split[1])));
this.publicKey = (PublicKey) pubOIS.readObject();
} catch (IOException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
}
/**
* Encrypts the data with the public key, which can be decrypted with the
* private key. This is only valid if the public key was provided.
* @param data
* @return
*/
public byte[] encryptWithPublic(byte[] data) {
Objects.requireNonNull(publicKey);
return crypt(data, publicKey, Cipher.ENCRYPT_MODE);
}
/**
* Encrypts the data with the private key, which can be decrypted with
* the public key. This is only valid if the private key was provided.
* @param data
* @return
* @throws InvalidKeyException
*/
public byte[] encryptWithPrivate(byte[] data) throws InvalidKeyException {
Objects.requireNonNull(privateKey);
return crypt(data, privateKey, Cipher.ENCRYPT_MODE);
}
/**
* Decrypts the data with the public key, which will have been encrypted
* with the private key. This is only valid if the public key was provided.
* @param data
* @return
*/
public byte[] decryptWithPublic(byte[] data) {
Objects.requireNonNull(publicKey);
return crypt(data, publicKey, Cipher.DECRYPT_MODE);
}
/**
* Decrypts the data with the private key, which will have been encrypted
* with the public key. This is only valid if the private key was provided.
* @param data
* @return
*/
public byte[] decryptWithPrivate(byte[] data){
Objects.requireNonNull(privateKey);
return crypt(data, privateKey, Cipher.DECRYPT_MODE);
}
/**
* Utility method that actually does the de/encrypting.
* @param data
* @param key
* @param cryptMode
* @return
*/
private byte[] crypt(byte [] data, Key key, int cryptMode){
byte[] cipherValue = null;
Cipher cipher;
try {
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(cryptMode, key);
cipherValue = cipher.doFinal(data);
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException ex) {
throw new RuntimeException(ex);
}
return cipherValue;
}
/**
* Returns the private key string.
*
* @return
*/
public String getPrivateKey() {
return toString(privateKey);
}
/**
* Returns the public key string.
*
* @return
*/
public String getPublicKey() {
return toString(publicKey, label);
}
/**
* Returns the label on the public key.
*
* @return
*/
public String getLabel() {
return label;
}
}