Coverage Report - org.apache.shiro.crypto.JcaCipherService
 
Classes in this File Line Coverage Branch Coverage Complexity
JcaCipherService
72%
121/167
50%
36/72
3
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.crypto;
 20  
 
 21  
 import org.apache.shiro.util.ByteSource;
 22  
 import org.apache.shiro.util.StringUtils;
 23  
 import org.slf4j.Logger;
 24  
 import org.slf4j.LoggerFactory;
 25  
 
 26  
 import javax.crypto.CipherInputStream;
 27  
 import javax.crypto.spec.IvParameterSpec;
 28  
 import javax.crypto.spec.SecretKeySpec;
 29  
 import java.io.IOException;
 30  
 import java.io.InputStream;
 31  
 import java.io.OutputStream;
 32  
 import java.security.Key;
 33  
 import java.security.SecureRandom;
 34  
 import java.security.spec.AlgorithmParameterSpec;
 35  
 
 36  
 /**
 37  
  * Abstract {@code CipherService} implementation utilizing Java's JCA APIs.
 38  
  * <h2>Auto-generated Initialization Vectors</h2>
 39  
  * Shiro does something by default for all of its {@code CipherService} implementations that the JCA
 40  
  * {@link javax.crypto.Cipher Cipher} does not do:  by default,
 41  
  * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly
 42  
  * generated and prepended to encrypted data before returning from the {@code encrypt} methods.  That is, the returned
 43  
  * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual
 44  
  * encrypted data byte array.  The {@code decrypt} methods in turn know to read this prepended initialization vector
 45  
  * before decrypting the real data that follows.
 46  
  * <p/>
 47  
  * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted
 48  
  * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>.
 49  
  * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources
 50  
  * that are the same or similar.
 51  
  * <p/>
 52  
  * You can turn off this behavior by setting the
 53  
  * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it
 54  
  * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing
 55  
  * a critical security feature.
 56  
  * <h3>Initialization Vector Size</h3>
 57  
  * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to
 58  
  * {@code 128} bits, a fairly common size.  Initialization vector sizes are very algorithm specific however, so subclass
 59  
  * implementations will often override this value in their constructor if necessary.
 60  
  * <p/>
 61  
  * Also note that {@code initializationVectorSize} values are specified in the number of
 62  
  * bits (not bytes!) to match common references in most cryptography documentation.  In practice though, initialization
 63  
  * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple
 64  
  * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the
 65  
  * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this).
 66  
  *
 67  
  * @since 1.0
 68  
  */
 69  
 public abstract class JcaCipherService implements CipherService {
 70  
 
 71  
     /**
 72  
      * Internal private log instance.
 73  
      */
 74  1
     private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class);
 75  
 
 76  
     /**
 77  
      * Default key size (in bits) for generated keys.
 78  
      */
 79  
     private static final int DEFAULT_KEY_SIZE = 128;
 80  
 
 81  
     /**
 82  
      * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations
 83  
      */
 84  
     private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
 85  
 
 86  
     private static final int BITS_PER_BYTE = 8;
 87  
 
 88  
     /**
 89  
      * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance.
 90  
      */
 91  
     private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
 92  
 
 93  
     /**
 94  
      * The name of the cipher algorithm to use for all encryption, decryption, and key operations
 95  
      */
 96  
     private String algorithmName;
 97  
 
 98  
     /**
 99  
      * The size in bits (not bytes) of generated cipher keys
 100  
      */
 101  
     private int keySize;
 102  
 
 103  
     /**
 104  
      * The size of the internal buffer (in bytes) used to transfer data from one stream to another during stream operations
 105  
      */
 106  
     private int streamingBufferSize;
 107  
 
 108  
     private boolean generateInitializationVectors;
 109  
     private int initializationVectorSize;
 110  
 
 111  
 
 112  
     private SecureRandom secureRandom;
 113  
 
 114  
     /**
 115  
      * Creates a new {@code JcaCipherService} instance which will use the specified cipher {@code algorithmName}
 116  
      * for all encryption, decryption, and key operations.  Also, the following defaults are set:
 117  
      * <ul>
 118  
      * <li>{@link #setKeySize keySize} = 128 bits</li>
 119  
      * <li>{@link #setInitializationVectorSize(int) initializationVectorSize} = 128 bits</li>
 120  
      * <li>{@link #setStreamingBufferSize(int) streamingBufferSize} = 512 bytes</li>
 121  
      * </ul>
 122  
      *
 123  
      * @param algorithmName the name of the cipher algorithm to use for all encryption, decryption, and key operations
 124  
      */
 125  7
     protected JcaCipherService(String algorithmName) {
 126  7
         if (!StringUtils.hasText(algorithmName)) {
 127  0
             throw new IllegalArgumentException("algorithmName argument cannot be null or empty.");
 128  
         }
 129  7
         this.algorithmName = algorithmName;
 130  7
         this.keySize = DEFAULT_KEY_SIZE;
 131  7
         this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice)
 132  7
         this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
 133  7
         this.generateInitializationVectors = true;
 134  7
     }
 135  
 
 136  
     /**
 137  
      * Returns the cipher algorithm name that will be used for all encryption, decryption, and key operations (for
 138  
      * example, 'AES', 'Blowfish', 'RSA', 'DSA', 'TripleDES', etc).
 139  
      *
 140  
      * @return the cipher algorithm name that will be used for all encryption, decryption, and key operations
 141  
      */
 142  
     public String getAlgorithmName() {
 143  26
         return algorithmName;
 144  
     }
 145  
 
 146  
     /**
 147  
      * Returns the size in bits (not bytes) of generated cipher keys.
 148  
      *
 149  
      * @return the size in bits (not bytes) of generated cipher keys.
 150  
      */
 151  
     public int getKeySize() {
 152  6
         return keySize;
 153  
     }
 154  
 
 155  
     /**
 156  
      * Sets the size in bits (not bytes) of generated cipher keys.
 157  
      *
 158  
      * @param keySize the size in bits (not bytes) of generated cipher keys.
 159  
      */
 160  
     public void setKeySize(int keySize) {
 161  0
         this.keySize = keySize;
 162  0
     }
 163  
 
 164  
     public boolean isGenerateInitializationVectors() {
 165  9
         return generateInitializationVectors;
 166  
     }
 167  
 
 168  
     public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
 169  0
         this.generateInitializationVectors = generateInitializationVectors;
 170  0
     }
 171  
 
 172  
     /**
 173  
      * Returns the algorithm-specific size in bits of generated initialization vectors.
 174  
      *
 175  
      * @return the algorithm-specific size in bits of generated initialization vectors.
 176  
      */
 177  
     public int getInitializationVectorSize() {
 178  17
         return initializationVectorSize;
 179  
     }
 180  
 
 181  
     /**
 182  
      * Sets the algorithm-specific initialization vector size in bits (not bytes!) to be used when generating
 183  
      * initialization vectors.  The  value must be a multiple of {@code 8} to ensure that the IV can be represented
 184  
      * as a byte array.
 185  
      *
 186  
      * @param initializationVectorSize the size in bits (not bytes) of generated initialization vectors.
 187  
      * @throws IllegalArgumentException if the size is not a multiple of {@code 8}.
 188  
      */
 189  
     public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
 190  2
         if (initializationVectorSize % BITS_PER_BYTE != 0) {
 191  0
             String msg = "Initialization vector sizes are specified in bits, but must be a multiple of 8 so they " +
 192  
                     "can be easily represented as a byte array.";
 193  0
             throw new IllegalArgumentException(msg);
 194  
         }
 195  2
         this.initializationVectorSize = initializationVectorSize;
 196  2
     }
 197  
 
 198  
     protected boolean isGenerateInitializationVectors(boolean streaming) {
 199  1
         return isGenerateInitializationVectors();
 200  
     }
 201  
 
 202  
     /**
 203  
      * Returns the size in bytes of the internal buffer used to transfer data from one stream to another during stream
 204  
      * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
 205  
      * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
 206  
      * <p/>
 207  
      * Default size is {@code 512} bytes.
 208  
      *
 209  
      * @return the size of the internal buffer used to transfer data from one stream to another during stream
 210  
      *         operations
 211  
      */
 212  
     public int getStreamingBufferSize() {
 213  8
         return streamingBufferSize;
 214  
     }
 215  
 
 216  
     /**
 217  
      * Sets the size in bytes of the internal buffer used to transfer data from one stream to another during stream
 218  
      * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
 219  
      * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
 220  
      * <p/>
 221  
      * Default size is {@code 512} bytes.
 222  
      *
 223  
      * @param streamingBufferSize the size of the internal buffer used to transfer data from one stream to another
 224  
      *                            during stream operations
 225  
      */
 226  
     public void setStreamingBufferSize(int streamingBufferSize) {
 227  0
         this.streamingBufferSize = streamingBufferSize;
 228  0
     }
 229  
 
 230  
     /**
 231  
      * Returns a source of randomness for encryption operations.  If one is not configured, and the underlying
 232  
      * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
 233  
      *
 234  
      * @return a source of randomness for encryption operations.  If one is not configured, and the underlying
 235  
      *         algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
 236  
      */
 237  
     public SecureRandom getSecureRandom() {
 238  24
         return secureRandom;
 239  
     }
 240  
 
 241  
     /**
 242  
      * Sets a source of randomness for encryption operations.  If one is not configured, and the underlying
 243  
      * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
 244  
      *
 245  
      * @param secureRandom a source of randomness for encryption operations.  If one is not configured, and the
 246  
      *                     underlying algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
 247  
      */
 248  
     public void setSecureRandom(SecureRandom secureRandom) {
 249  0
         this.secureRandom = secureRandom;
 250  0
     }
 251  
 
 252  
     protected static SecureRandom getDefaultSecureRandom() {
 253  
         try {
 254  8
             return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
 255  0
         } catch (java.security.NoSuchAlgorithmException e) {
 256  0
             log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
 257  
                     "platform's default SecureRandom algorithm.", e);
 258  0
             return new java.security.SecureRandom();
 259  
         }
 260  
     }
 261  
 
 262  
     protected SecureRandom ensureSecureRandom() {
 263  8
         SecureRandom random = getSecureRandom();
 264  8
         if (random == null) {
 265  8
             random = getDefaultSecureRandom();
 266  
         }
 267  8
         return random;
 268  
     }
 269  
 
 270  
     /**
 271  
      * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
 272  
      * creating a new {@code Cipher} instance.  This default implementation always returns
 273  
      * {@link #getAlgorithmName() getAlgorithmName()}.  Block cipher implementations will want to override this method
 274  
      * to support appending cipher operation modes and padding schemes.
 275  
      *
 276  
      * @param streaming if the transformation string is going to be used for a Cipher for stream-based encryption or not.
 277  
      * @return the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
 278  
      *         creating a new {@code Cipher} instance.
 279  
      */
 280  
     protected String getTransformationString(boolean streaming) {
 281  0
         return getAlgorithmName();
 282  
     }
 283  
 
 284  
     protected byte[] generateInitializationVector(boolean streaming) {
 285  8
         int size = getInitializationVectorSize();
 286  8
         if (size <= 0) {
 287  0
             String msg = "initializationVectorSize property must be greater than zero.  This number is " +
 288  
                     "typically set in the " + CipherService.class.getSimpleName() + " subclass constructor.  " +
 289  
                     "Also check your configuration to ensure that if you are setting a value, it is positive.";
 290  0
             throw new IllegalStateException(msg);
 291  
         }
 292  8
         if (size % BITS_PER_BYTE != 0) {
 293  0
             String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
 294  0
             throw new IllegalStateException(msg);
 295  
         }
 296  8
         int sizeInBytes = size / BITS_PER_BYTE;
 297  8
         byte[] ivBytes = new byte[sizeInBytes];
 298  8
         SecureRandom random = ensureSecureRandom();
 299  8
         random.nextBytes(ivBytes);
 300  8
         return ivBytes;
 301  
     }
 302  
 
 303  
     public ByteSource encrypt(byte[] plaintext, byte[] key) {
 304  4
         byte[] ivBytes = null;
 305  4
         boolean generate = isGenerateInitializationVectors(false);
 306  4
         if (generate) {
 307  4
             ivBytes = generateInitializationVector(false);
 308  4
             if (ivBytes == null || ivBytes.length == 0) {
 309  0
                 throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
 310  
                         "cannot be null or empty.");
 311  
             }
 312  
         }
 313  4
         return encrypt(plaintext, key, ivBytes, generate);
 314  
     }
 315  
 
 316  
     private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
 317  
 
 318  4
         final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
 319  
 
 320  
         byte[] output;
 321  
 
 322  4
         if (prependIv && iv != null && iv.length > 0) {
 323  
 
 324  4
             byte[] encrypted = crypt(plaintext, key, iv, MODE);
 325  
 
 326  4
             output = new byte[iv.length + encrypted.length];
 327  
 
 328  
             //now copy the iv bytes + encrypted bytes into one output array:
 329  
 
 330  
             // iv bytes:
 331  4
             System.arraycopy(iv, 0, output, 0, iv.length);
 332  
 
 333  
             // + encrypted bytes:
 334  4
             System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
 335  4
         } else {
 336  0
             output = crypt(plaintext, key, iv, MODE);
 337  
         }
 338  
 
 339  4
         if (log.isTraceEnabled()) {
 340  4
             log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
 341  
                     "byte array is size " + (output != null ? output.length : 0));
 342  
         }
 343  
 
 344  4
         return ByteSource.Util.bytes(output);
 345  
     }
 346  
 
 347  
     public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
 348  
 
 349  5
         byte[] encrypted = ciphertext;
 350  
 
 351  
         //No IV, check if we need to read the IV from the stream:
 352  5
         byte[] iv = null;
 353  
 
 354  5
         if (isGenerateInitializationVectors(false)) {
 355  
             try {
 356  
                 //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
 357  
                 //is:
 358  
                 // - the first N bytes is the initialization vector, where N equals the value of the
 359  
                 // 'initializationVectorSize' attribute.
 360  
                 // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.
 361  
 
 362  
                 //So we need to chunk the method argument into its constituent parts to find the IV and then use
 363  
                 //the IV to decrypt the real ciphertext:
 364  
 
 365  5
                 int ivSize = getInitializationVectorSize();
 366  5
                 int ivByteSize = ivSize / BITS_PER_BYTE;
 367  
 
 368  
                 //now we know how large the iv is, so extract the iv bytes:
 369  5
                 iv = new byte[ivByteSize];
 370  5
                 System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
 371  
 
 372  
                 //remaining data is the actual encrypted ciphertext.  Isolate it:
 373  4
                 int encryptedSize = ciphertext.length - ivByteSize;
 374  4
                 encrypted = new byte[encryptedSize];
 375  4
                 System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
 376  1
             } catch (Exception e) {
 377  1
                 String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
 378  1
                 throw new CryptoException(msg, e);
 379  4
             }
 380  
         }
 381  
 
 382  4
         return decrypt(encrypted, key, iv);
 383  
     }
 384  
 
 385  
     private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
 386  4
         if (log.isTraceEnabled()) {
 387  4
             log.trace("Attempting to decrypt incoming byte array of length " +
 388  
                     (ciphertext != null ? ciphertext.length : 0));
 389  
         }
 390  4
         byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
 391  4
         return decrypted == null ? null : ByteSource.Util.bytes(decrypted);
 392  
     }
 393  
 
 394  
     /**
 395  
      * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations.  The
 396  
      * Cipher's {@code transformationString} for the {@code Cipher}.{@link javax.crypto.Cipher#getInstance getInstance}
 397  
      * call is obtaind via the {@link #getTransformationString(boolean) getTransformationString} method.
 398  
      *
 399  
      * @param streaming {@code true} if the cipher instance will be used as a stream cipher, {@code false} if it will be
 400  
      *                  used as a block cipher.
 401  
      * @return a new JDK {@code Cipher} instance.
 402  
      * @throws CryptoException if a new Cipher instance cannot be constructed based on the
 403  
      *                         {@link #getTransformationString(boolean) getTransformationString} value.
 404  
      */
 405  
     private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException {
 406  16
         String transformationString = getTransformationString(streaming);
 407  
         try {
 408  16
             return javax.crypto.Cipher.getInstance(transformationString);
 409  0
         } catch (Exception e) {
 410  0
             String msg = "Unable to acquire a Java JCA Cipher instance using " +
 411  
                     javax.crypto.Cipher.class.getName() + ".getInstance( \"" + transformationString + "\" ). " +
 412  
                     getAlgorithmName() + " under this configuration is required for the " +
 413  
                     getClass().getName() + " instance to function.";
 414  0
             throw new CryptoException(msg, e);
 415  
         }
 416  
     }
 417  
 
 418  
     /**
 419  
      * Functions as follows:
 420  
      * <ol>
 421  
      * <li>Creates a {@link #newCipherInstance(boolean) new JDK cipher instance}</li>
 422  
      * <li>Converts the specified key bytes into an {@link #getAlgorithmName() algorithm}-compatible JDK
 423  
      * {@link Key key} instance</li>
 424  
      * <li>{@link #init(javax.crypto.Cipher, int, java.security.Key, AlgorithmParameterSpec, SecureRandom) Initializes}
 425  
      * the JDK cipher instance with the JDK key</li>
 426  
      * <li>Calls the {@link #crypt(javax.crypto.Cipher, byte[]) crypt(cipher,bytes)} method to either encrypt or
 427  
      * decrypt the data based on the specified Cipher behavior mode
 428  
      * ({@link javax.crypto.Cipher#ENCRYPT_MODE Cipher.ENCRYPT_MODE} or
 429  
      * {@link javax.crypto.Cipher#DECRYPT_MODE Cipher.DECRYPT_MODE})</li>
 430  
      * </ol>
 431  
      *
 432  
      * @param bytes the bytes to crypt
 433  
      * @param key   the key to use to perform the encryption or decryption.
 434  
      * @param iv    the initialization vector to use for the crypt operation (optional, may be {@code null}).
 435  
      * @param mode  the JDK Cipher behavior mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE).
 436  
      * @return the resulting crypted byte array
 437  
      * @throws IllegalArgumentException if {@code bytes} are null or empty.
 438  
      * @throws CryptoException          if Cipher initialization or the crypt operation fails
 439  
      */
 440  
     private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
 441  8
         if (key == null || key.length == 0) {
 442  0
             throw new IllegalArgumentException("key argument cannot be null or empty.");
 443  
         }
 444  8
         javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
 445  8
         return crypt(cipher, bytes);
 446  
     }
 447  
 
 448  
     /**
 449  
      * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that
 450  
      * might arise in an {@link CryptoException}
 451  
      *
 452  
      * @param cipher the JDK Cipher to finalize (perform the actual cryption)
 453  
      * @param bytes  the bytes to crypt
 454  
      * @return the resulting crypted byte array.
 455  
      * @throws CryptoException if there is an illegal block size or bad padding
 456  
      */
 457  
     private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException {
 458  
         try {
 459  8
             return cipher.doFinal(bytes);
 460  0
         } catch (Exception e) {
 461  0
             String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
 462  0
             throw new CryptoException(msg, e);
 463  
         }
 464  
     }
 465  
 
 466  
     /**
 467  
      * Initializes the JDK Cipher with the specified mode and key.  This is primarily a utility method to catch any
 468  
      * potential {@link java.security.InvalidKeyException InvalidKeyException} that might arise.
 469  
      *
 470  
      * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}.
 471  
      * @param mode   the Cipher mode
 472  
      * @param key    the Cipher's Key
 473  
      * @param spec   the JDK AlgorithmParameterSpec for cipher initialization (optional, may be null).
 474  
      * @param random the SecureRandom to use for cipher initialization (optional, may be null).
 475  
      * @throws CryptoException if the key is invalid
 476  
      */
 477  
     private void init(javax.crypto.Cipher cipher, int mode, java.security.Key key,
 478  
                       AlgorithmParameterSpec spec, SecureRandom random) throws CryptoException {
 479  
         try {
 480  16
             if (random != null) {
 481  0
                 if (spec != null) {
 482  0
                     cipher.init(mode, key, spec, random);
 483  
                 } else {
 484  0
                     cipher.init(mode, key, random);
 485  
                 }
 486  
             } else {
 487  16
                 if (spec != null) {
 488  16
                     cipher.init(mode, key, spec);
 489  
                 } else {
 490  0
                     cipher.init(mode, key);
 491  
                 }
 492  
             }
 493  0
         } catch (Exception e) {
 494  0
             String msg = "Unable to init cipher instance.";
 495  0
             throw new CryptoException(msg, e);
 496  16
         }
 497  16
     }
 498  
 
 499  
 
 500  
     public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
 501  4
         byte[] iv = null;
 502  4
         boolean generate = isGenerateInitializationVectors(true);
 503  4
         if (generate) {
 504  4
             iv = generateInitializationVector(true);
 505  4
             if (iv == null || iv.length == 0) {
 506  0
                 throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
 507  
                         "cannot be null or empty.");
 508  
             }
 509  
         }
 510  4
         encrypt(in, out, key, iv, generate);
 511  4
     }
 512  
 
 513  
     private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
 514  4
         if (prependIv && iv != null && iv.length > 0) {
 515  
             try {
 516  
                 //first write the IV:
 517  4
                 out.write(iv);
 518  0
             } catch (IOException e) {
 519  0
                 throw new CryptoException(e);
 520  4
             }
 521  
         }
 522  
 
 523  4
         crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
 524  4
     }
 525  
 
 526  
     public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
 527  4
         decrypt(in, out, key, isGenerateInitializationVectors(true));
 528  4
     }
 529  
 
 530  
     private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
 531  
 
 532  4
         byte[] iv = null;
 533  
         //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream:
 534  4
         if (ivPrepended) {
 535  
             //we are generating IVs, so we need to read the previously-generated IV from the stream before
 536  
             //we decrypt the rest of the stream (we need the IV to decrypt):
 537  4
             int ivSize = getInitializationVectorSize();
 538  4
             int ivByteSize = ivSize / BITS_PER_BYTE;
 539  4
             iv = new byte[ivByteSize];
 540  
             int read;
 541  
 
 542  
             try {
 543  4
                 read = in.read(iv);
 544  0
             } catch (IOException e) {
 545  0
                 String msg = "Unable to correctly read the Initialization Vector from the input stream.";
 546  0
                 throw new CryptoException(msg, e);
 547  4
             }
 548  
 
 549  4
             if (read != ivByteSize) {
 550  0
                 throw new CryptoException("Unable to read initialization vector bytes from the InputStream.  " +
 551  
                         "This is required when initialization vectors are autogenerated during an encryption " +
 552  
                         "operation.");
 553  
             }
 554  
         }
 555  
 
 556  4
         decrypt(in, out, key, iv);
 557  4
     }
 558  
 
 559  
     private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
 560  4
         crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
 561  4
     }
 562  
 
 563  
     private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
 564  8
         if (in == null) {
 565  0
             throw new NullPointerException("InputStream argument cannot be null.");
 566  
         }
 567  8
         if (out == null) {
 568  0
             throw new NullPointerException("OutputStream argument cannot be null.");
 569  
         }
 570  
 
 571  8
         javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
 572  
 
 573  8
         CipherInputStream cis = new CipherInputStream(in, cipher);
 574  
 
 575  8
         int bufSize = getStreamingBufferSize();
 576  8
         byte[] buffer = new byte[bufSize];
 577  
 
 578  
         int bytesRead;
 579  
         try {
 580  24
             while ((bytesRead = cis.read(buffer)) != -1) {
 581  16
                 out.write(buffer, 0, bytesRead);
 582  
             }
 583  0
         } catch (IOException e) {
 584  0
             throw new CryptoException(e);
 585  8
         }
 586  8
     }
 587  
 
 588  
     private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
 589  
             throws CryptoException {
 590  
 
 591  16
         javax.crypto.Cipher cipher = newCipherInstance(streaming);
 592  16
         java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
 593  16
         IvParameterSpec ivSpec = null;
 594  16
         if (iv != null && iv.length > 0) {
 595  16
             ivSpec = new IvParameterSpec(iv);
 596  
         }
 597  
 
 598  16
         init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
 599  
 
 600  16
         return cipher;
 601  
     }
 602  
 }