001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.crypt; 020 021import java.security.Key; 022import java.security.SecureRandom; 023import java.security.spec.AlgorithmParameterSpec; 024 025import javax.crypto.Cipher; 026import javax.crypto.SecretKeyFactory; 027import javax.crypto.spec.IvParameterSpec; 028import javax.crypto.spec.PBEKeySpec; 029import javax.crypto.spec.PBEParameterSpec; 030import javax.crypto.spec.SecretKeySpec; 031 032import lucee.commons.lang.StringUtil; 033import lucee.runtime.coder.Coder; 034import lucee.runtime.exp.PageException; 035import lucee.runtime.op.Caster; 036 037/** 038 * 039 */ 040public class Cryptor { 041 042 public final static String DEFAULT_CHARSET = "UTF-8"; 043 public final static String DEFAULT_ENCODING = "UU"; 044 public final static int DEFAULT_ITERATIONS = 1000; // minimum recommended per NIST 045 046 private final static SecureRandom secureRandom = new SecureRandom(); 047 048 049 /** 050 * @param input - the clear-text input to be encrypted, or the encrypted input to be decrypted 051 * @param key - the encryption key 052 * @param algorithm - algorithm in JCE scheme 053 * @param ivOrSalt - Initialization Vector for algorithms with Feedback Mode that is not ECB, or Salt for Password Based Encryption algorithms 054 * @param iterations - number of Iterations for Password Based Encryption algorithms (recommended minimum value is 1000) 055 * @param doDecrypt - the Operation Type, pass false for Encrypt or true for Decrypt 056 * @return 057 * @throws PageException 058 */ 059 static byte[] crypt( byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations, boolean doDecrypt ) throws PageException { 060 061 byte[] result = null; 062 Key secretKey = null; 063 AlgorithmParameterSpec params = null; 064 065 String algo = algorithm; 066 boolean isFBM = false, isPBE = StringUtil.startsWithIgnoreCase( algo, "PBE" ); 067 int ivsLen = 0, algoDelimPos = algorithm.indexOf( '/' ); 068 069 if ( algoDelimPos > -1 ) { 070 071 algo = algorithm.substring( 0, algoDelimPos ); 072 isFBM = !StringUtil.startsWithIgnoreCase( algorithm.substring( algoDelimPos + 1 ), "ECB" ); 073 } 074 075 try { 076 077 Cipher cipher = Cipher.getInstance( algorithm ); 078 079 if ( ivOrSalt == null ) { 080 081 if ( isPBE || isFBM ) { 082 083 ivsLen = cipher.getBlockSize(); 084 ivOrSalt = new byte[ ivsLen ]; 085 086 if ( doDecrypt ) 087 System.arraycopy( input, 0, ivOrSalt, 0, ivsLen ); 088 else 089 secureRandom.nextBytes( ivOrSalt ); 090 } 091 } 092 093 if ( isPBE ) { 094 095 secretKey = SecretKeyFactory.getInstance( algorithm ).generateSecret( new PBEKeySpec( key.toCharArray() ) ); 096 params = new PBEParameterSpec( ivOrSalt, iterations > 0 ? iterations : DEFAULT_ITERATIONS ); // set Salt and Iterations for PasswordBasedEncryption 097 } 098 else { 099 100 secretKey = new SecretKeySpec( Coder.decode( Coder.ENCODING_BASE64, key ), algo ); 101 if ( isFBM ) 102 params = new IvParameterSpec( ivOrSalt ); // set Initialization Vector for non-ECB Feedback Mode 103 } 104 105 if ( doDecrypt ) { 106 107 cipher.init( Cipher.DECRYPT_MODE, secretKey, params ); 108 109 result = cipher.doFinal( input, ivsLen, input.length - ivsLen ); 110 } 111 else { 112 113 cipher.init( Cipher.ENCRYPT_MODE, secretKey, params ); 114 115 result = new byte[ ivsLen + cipher.getOutputSize( input.length ) ]; 116 117 if ( ivsLen > 0 ) 118 System.arraycopy( ivOrSalt, 0, result, 0, ivsLen ); 119 120 cipher.doFinal( input, 0, input.length, result, ivsLen ); 121 } 122 123 return result; 124 } 125 catch (Throwable t ) { 126 throw Caster.toPageException( t ); 127 } 128 } 129 130 131 /** 132 * an encrypt method that takes a byte-array for input and returns an encrypted byte-array 133 */ 134 public static byte[] encrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException { 135 136 return crypt( input, key, algorithm, ivOrSalt, iterations, false ); 137 } 138 139 140 /** 141 * an encrypt method that takes a clear-text String for input and returns an encrypted, encoded, String 142 */ 143 public static String encrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException { 144 145 try { 146 147 if ( charset == null ) charset = DEFAULT_CHARSET; 148 if ( encoding == null ) encoding = DEFAULT_ENCODING; 149 150 byte[] baInput = input.getBytes( charset ); 151 byte[] encrypted = encrypt( baInput, key, algorithm, ivOrSalt, iterations ); 152 153 return Coder.encode( encoding, encrypted ); 154 } 155 catch (Throwable t ) { 156 throw Caster.toPageException( t ); 157 } 158 } 159 160 161 /** 162 * a decrypt method that takes an encrypted byte-array for input and returns an unencrypted byte-array 163 */ 164 public static byte[] decrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException { 165 166 return crypt( input, key, algorithm, ivOrSalt, iterations, true ); 167 } 168 169 170 /** 171 * a decrypt method that takes an encrypted, encoded, String for input and returns a clear-text String 172 */ 173 public static String decrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException { 174 175 try { 176 177 if ( charset == null ) charset = DEFAULT_CHARSET; 178 if ( encoding == null ) encoding = DEFAULT_ENCODING; 179 180 byte[] baInput = Coder.decode( encoding, input ); 181 byte[] decrypted = decrypt( baInput, key, algorithm, ivOrSalt, iterations ); 182 183 return new String( decrypted, charset ); 184 } 185 catch (Throwable t ) { 186 throw Caster.toPageException( t ); 187 } 188 } 189}