package com.austinv11.collectiveframework.utils.security; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; /** * This class simplifies and attempts to merge all java cipher/encryption apis */ public final class Encryption { private static final boolean useSunApi = isSunPackageAvailable();//Cached value for finding a sun package /** * Encrypts a value * @param string The string to encrypt * @param algorithm The algorithm, mode and padding to use * @param key The key * @param initializationVector The initialization vector * @return The value, use {@link Encryption#getValue(SecretValue)} to get the encrypted value and * {@link Encryption#getMeta(SecretValue)} to get the length of the encryption * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException * @throws ShortBufferException * @throws IllegalBlockSizeException * @throws BadPaddingException */ public static SecretValue<String, byte[], Integer> encrypt(String string, String algorithm, byte[] key, byte[] initializationVector) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException { byte[] data = string.getBytes(); SecretKeySpec keySpec; if (algorithm.contains("/")) keySpec = new SecretKeySpec(key, algorithm.split("/")[0]); else keySpec = new SecretKeySpec(key, algorithm); IvParameterSpec ivSpec = new IvParameterSpec(initializationVector); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted= new byte[cipher.getOutputSize(data.length)]; int encryptionLength = cipher.update(data, 0, data.length, encrypted, 0); encryptionLength += cipher.doFinal(encrypted, encryptionLength); return newSecretValue(new String(encrypted), key, encryptionLength); } /** * Decrypts a value * @param string The encrypted string * @param encryptionLength The length of the encryption * @param algorithm The algorithm, mode and padding to use * @param key The key * @param initializationVector The initialization vector * @return The value, use {@link Encryption#getValue(SecretValue)} to get the decrypted value and * {@link Encryption#getMeta(SecretValue)} to get the length of the decryption * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException * @throws ShortBufferException * @throws IllegalBlockSizeException * @throws BadPaddingException */ public static SecretValue<String, byte[], Integer> decrypt(String string, int encryptionLength, String algorithm, byte[] key, byte[] initializationVector) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException { byte[] data = string.getBytes(); SecretKeySpec keySpec; if (algorithm.contains("/")) keySpec = new SecretKeySpec(key, algorithm.split("/")[0]); else keySpec = new SecretKeySpec(key, algorithm); IvParameterSpec ivSpec = new IvParameterSpec(initializationVector); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] decrypted = new byte[cipher.getOutputSize(encryptionLength)]; int decryptionLength = cipher.update(data, 0, encryptionLength, decrypted, 0); decryptionLength += cipher.doFinal(decrypted, decryptionLength); return newSecretValue(new String(decrypted), key, decryptionLength); } /** * Creates a new secret value * @param value The value * @param key The key * @param meta Any metadata * @param isCallerSensitive Set this to true to limit callers (if supported on the JDK) * @param approvedCaller The only class allowed to retrieve info from it * @return The secret value */ public static <V, K, M> SecretValue<V, K, M> newSecretValue(V value, K key, M meta, boolean isCallerSensitive, String approvedCaller) { return new SecretValue<V, K, M>(value, key, meta, isCallerSensitive, approvedCaller); } /** * Creates a new secret value with the approved caller as the caller of this method * @param value The value * @param key The key * @param meta Any metadata * @param isCallerSensitive Set this to true to limit callers (if supported on the JDK) * @return The secret value */ @CallerSensitive public static <V, K, M> SecretValue<V, K, M> newSecretValue(V value, K key, M meta, boolean isCallerSensitive) { if (useSunApi && isCallerSensitive) return newSecretValue(value, key, meta, isCallerSensitive, Reflection.getCallerClass().getName()); else return newSecretValue(value, key, meta); } /** * Creates a new secret value * @param value The value * @param key The key * @param meta Any metadata * @return The secret value */ public static <V, K, M> SecretValue<V, K, M> newSecretValue(V value, K key, M meta) { return new SecretValue<V, K, M>(value, key, meta); } /** * Gets the value of the SecretValue * @param value The SecretValue * @return The value */ @CallerSensitive public static <V, K, M> V getValue(SecretValue<V, K, M> value) { if (useSunApi && value.isCallerSensitive) { if (!Reflection.getCallerClass().getName().equals(value.approvedCaller)) throw new SecurityException("Unapproved caller!"); } return value.value; } /** * Gets the key of the SecretValue * @param value The SecretValue * @return The key */ @CallerSensitive public static <V, K, M> K getKey(SecretValue<V, K, M> value) { if (useSunApi && value.isCallerSensitive) { if (!Reflection.getCallerClass().getName().equals(value.approvedCaller)) throw new SecurityException("Unapproved caller!"); } return value.key; } /** * Gets the metadata of the SecretValue * @param value The SecretValue * @return The metadata */ @CallerSensitive public static <V, K, M> M getMeta(SecretValue<V, K, M> value) { if (useSunApi && value.isCallerSensitive) { if (!Reflection.getCallerClass().getName().equals(value.approvedCaller)) throw new SecurityException("Unapproved caller!"); } return value.meta; } /** * Default encoding for base64 encryption */ private static final String DEFAULT_ENCODING = "UTF-8"; /** * Performs a base 64 encoding on a string * @param string The string to encode * @param encoding The encoding of the string * @return Encoded string * @throws UnsupportedOperationException Thrown if the sun package isn't found (i.e. when running with OpenJDK) * @throws UnsupportedEncodingException */ public static String base64Encode(String string, String encoding) throws UnsupportedOperationException, UnsupportedEncodingException { if (!useSunApi) throw new UnsupportedOperationException("Sun Package not found!"); BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(string.getBytes(encoding)); } /** * Performs a base 64 encoding on a string * @param string The string to encode * @return Encoded string * @throws UnsupportedOperationException Thrown if the sun package isn't found (i.e. when running with OpenJDK) * @throws UnsupportedEncodingException */ public static String base64Encode(String string) throws UnsupportedOperationException, UnsupportedEncodingException { return base64Encode(string, DEFAULT_ENCODING); } /** * Performs a base 64 decoding on a string * @param string The string to encode * @param encoding The encoding of the string * @return Decoded string * @throws UnsupportedOperationException Thrown if the sun package isn't found (i.e. when running with OpenJDK) * @throws IOException */ public static String base64Decode(String string, String encoding) throws UnsupportedOperationException, IOException { if (!useSunApi) throw new UnsupportedOperationException("Sun Package not found!"); BASE64Decoder decoder = new BASE64Decoder(); return new String(decoder.decodeBuffer(string), encoding); } /** * Performs a base 64 decoding on a string * @param string The string to encode * @return Decoded string * @throws UnsupportedOperationException Thrown if the sun package isn't found (i.e. when running with OpenJDK) * @throws IOException */ public static String base64Decode(String string) throws UnsupportedOperationException, IOException { return base64Decode(string, DEFAULT_ENCODING); } //Don't wanna forget about our linux fans on OpenJDK private static boolean isSunPackageAvailable() { try { Class.forName("sun.misc.BASE64Encoder"); return true; } catch (ClassNotFoundException e) { return false; } } /** * Eases the management of algorithms, modes, and padding * See http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher for a description * of each. * Use this in the place of a string for encryption types. */ public static final class EncryptionType { //Algorithms (minus PBE) public static final String AES = "AES"; public static final String AES_WRAP = "AESWrap"; public static final String ARCFOUR = "ARCFOUR"; public static final String BLOWFISH = "Blowfish"; public static final String CCM = "CCM"; public static final String DES = "DES"; public static final String DE_SEDE = "DESede"; public static final String DE_SEDE_WRAP = "DESedeWrap"; public static final String ECIES = "ECIES"; public static final String GCM = "GCM"; //Skipped PBE public static final String RC2 = "RC2"; public static final String RC4 = "RC4"; public static final String RC5 = "RC5"; public static final String RSA = "RSA"; //Modes (does not include number, i.e. CFBx where x is any number) public static final String NONE = "NONE"; public static final String CBC = "CBC"; public static final String CFB = "CFB"; public static final String CTR = "CTR"; public static final String CTS = "CTS"; public static final String ECB = "ECB"; public static final String OFB = "OFB"; public static final String PCBC = "PCBC"; //Paddings (minus OAEPWith<digest>And<mgf>Padding) public static final String NO_PADDING = "NoPadding"; public static final String ISO10126_PADDING = "ISO10126Padding"; public static final String OAEP_PADDING= "OAEPPadding"; public static final String PKCS1_PADDING = "PKCS1Padding"; public static final String PKCS5_PADDING = "PKCS5Padding"; public static final String SSL3_PADDING = "SSL3Padding"; private final String type; public EncryptionType(String algorithm) { type = algorithm; } public EncryptionType(String algorithm, String mode, String padding) { type = algorithm+"/"+mode+"/"+padding; } @Override public String toString() { return type; } } }