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}