package railo.runtime.crypt; import java.security.Key; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; import railo.commons.lang.StringUtil; import railo.runtime.coder.Coder; import railo.runtime.exp.PageException; import railo.runtime.op.Caster; /** * */ public class Cryptor { public final static String DEFAULT_CHARSET = "UTF-8"; public final static String DEFAULT_ENCODING = "UU"; public final static int DEFAULT_ITERATIONS = 1000; // minimum recommended per NIST private final static SecureRandom secureRandom = new SecureRandom(); /** * @param input - the clear-text input to be encrypted, or the encrypted input to be decrypted * @param key - the encryption key * @param algorithm - algorithm in JCE scheme * @param ivOrSalt - Initialization Vector for algorithms with Feedback Mode that is not ECB, or Salt for Password Based Encryption algorithms * @param iterations - number of Iterations for Password Based Encryption algorithms (recommended minimum value is 1000) * @param doDecrypt - the Operation Type, pass false for Encrypt or true for Decrypt * @return * @throws PageException */ static byte[] crypt( byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations, boolean doDecrypt ) throws PageException { byte[] result = null; Key secretKey = null; AlgorithmParameterSpec params = null; String algo = algorithm; boolean isFBM = false, isPBE = StringUtil.startsWithIgnoreCase( algo, "PBE" ); int ivsLen = 0, algoDelimPos = algorithm.indexOf( '/' ); if ( algoDelimPos > -1 ) { algo = algorithm.substring( 0, algoDelimPos ); isFBM = !StringUtil.startsWithIgnoreCase( algorithm.substring( algoDelimPos + 1 ), "ECB" ); } try { Cipher cipher = Cipher.getInstance( algorithm ); if ( ivOrSalt == null ) { if ( isPBE || isFBM ) { ivsLen = cipher.getBlockSize(); ivOrSalt = new byte[ ivsLen ]; if ( doDecrypt ) System.arraycopy( input, 0, ivOrSalt, 0, ivsLen ); else secureRandom.nextBytes( ivOrSalt ); } } if ( isPBE ) { secretKey = SecretKeyFactory.getInstance( algorithm ).generateSecret( new PBEKeySpec( key.toCharArray() ) ); params = new PBEParameterSpec( ivOrSalt, iterations > 0 ? iterations : DEFAULT_ITERATIONS ); // set Salt and Iterations for PasswordBasedEncryption } else { secretKey = new SecretKeySpec( Coder.decode( Coder.ENCODING_BASE64, key ), algo ); if ( isFBM ) params = new IvParameterSpec( ivOrSalt ); // set Initialization Vector for non-ECB Feedback Mode } if ( doDecrypt ) { cipher.init( Cipher.DECRYPT_MODE, secretKey, params ); result = cipher.doFinal( input, ivsLen, input.length - ivsLen ); } else { cipher.init( Cipher.ENCRYPT_MODE, secretKey, params ); result = new byte[ ivsLen + cipher.getOutputSize( input.length ) ]; if ( ivsLen > 0 ) System.arraycopy( ivOrSalt, 0, result, 0, ivsLen ); cipher.doFinal( input, 0, input.length, result, ivsLen ); } return result; } catch ( Throwable t ) { throw Caster.toPageException( t ); } } /** * an encrypt method that takes a byte-array for input and returns an encrypted byte-array */ public static byte[] encrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException { return crypt( input, key, algorithm, ivOrSalt, iterations, false ); } /** * an encrypt method that takes a clear-text String for input and returns an encrypted, encoded, String */ public static String encrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException { try { if ( charset == null ) charset = DEFAULT_CHARSET; if ( encoding == null ) encoding = DEFAULT_ENCODING; byte[] baInput = input.getBytes( charset ); byte[] encrypted = encrypt( baInput, key, algorithm, ivOrSalt, iterations ); return Coder.encode( encoding, encrypted ); } catch ( Throwable t ) { throw Caster.toPageException( t ); } } /** * a decrypt method that takes an encrypted byte-array for input and returns an unencrypted byte-array */ public static byte[] decrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException { return crypt( input, key, algorithm, ivOrSalt, iterations, true ); } /** * a decrypt method that takes an encrypted, encoded, String for input and returns a clear-text String */ public static String decrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException { try { if ( charset == null ) charset = DEFAULT_CHARSET; if ( encoding == null ) encoding = DEFAULT_ENCODING; byte[] baInput = Coder.decode( encoding, input ); byte[] decrypted = decrypt( baInput, key, algorithm, ivOrSalt, iterations ); return new String( decrypted, charset ); } catch ( Throwable t ) { throw Caster.toPageException( t ); } } }