View Javadoc
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      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     protected JcaCipherService(String algorithmName) {
126         if (!StringUtils.hasText(algorithmName)) {
127             throw new IllegalArgumentException("algorithmName argument cannot be null or empty.");
128         }
129         this.algorithmName = algorithmName;
130         this.keySize = DEFAULT_KEY_SIZE;
131         this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice)
132         this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
133         this.generateInitializationVectors = true;
134     }
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         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         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         this.keySize = keySize;
162     }
163 
164     public boolean isGenerateInitializationVectors() {
165         return generateInitializationVectors;
166     }
167 
168     public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
169         this.generateInitializationVectors = generateInitializationVectors;
170     }
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         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         if (initializationVectorSize % BITS_PER_BYTE != 0) {
191             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             throw new IllegalArgumentException(msg);
194         }
195         this.initializationVectorSize = initializationVectorSize;
196     }
197 
198     protected boolean isGenerateInitializationVectors(boolean streaming) {
199         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         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         this.streamingBufferSize = streamingBufferSize;
228     }
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         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         this.secureRandom = secureRandom;
250     }
251 
252     protected static SecureRandom getDefaultSecureRandom() {
253         try {
254             return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
255         } catch (java.security.NoSuchAlgorithmException e) {
256             log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
257                     "platform's default SecureRandom algorithm.", e);
258             return new java.security.SecureRandom();
259         }
260     }
261 
262     protected SecureRandom ensureSecureRandom() {
263         SecureRandom random = getSecureRandom();
264         if (random == null) {
265             random = getDefaultSecureRandom();
266         }
267         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         return getAlgorithmName();
282     }
283 
284     protected byte[] generateInitializationVector(boolean streaming) {
285         int size = getInitializationVectorSize();
286         if (size <= 0) {
287             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             throw new IllegalStateException(msg);
291         }
292         if (size % BITS_PER_BYTE != 0) {
293             String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
294             throw new IllegalStateException(msg);
295         }
296         int sizeInBytes = size / BITS_PER_BYTE;
297         byte[] ivBytes = new byte[sizeInBytes];
298         SecureRandom random = ensureSecureRandom();
299         random.nextBytes(ivBytes);
300         return ivBytes;
301     }
302 
303     public ByteSource encrypt(byte[] plaintext, byte[] key) {
304         byte[] ivBytes = null;
305         boolean generate = isGenerateInitializationVectors(false);
306         if (generate) {
307             ivBytes = generateInitializationVector(false);
308             if (ivBytes == null || ivBytes.length == 0) {
309                 throw new IllegalStateException("Initialization vector generation is enabled - generated vector " +
310                         "cannot be null or empty.");
311             }
312         }
313         return encrypt(plaintext, key, ivBytes, generate);
314     }
315 
316     private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
317 
318         final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
319 
320         byte[] output;
321 
322         if (prependIv && iv != null && iv.length > 0) {
323 
324             byte[] encrypted = crypt(plaintext, key, iv, MODE);
325 
326             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             System.arraycopy(iv, 0, output, 0, iv.length);
332 
333             // + encrypted bytes:
334             System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
335         } else {
336             output = crypt(plaintext, key, iv, MODE);
337         }
338 
339         if (log.isTraceEnabled()) {
340             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         return ByteSource.Util.bytes(output);
345     }
346 
347     public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
348 
349         byte[] encrypted = ciphertext;
350 
351         //No IV, check if we need to read the IV from the stream:
352         byte[] iv = null;
353 
354         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                 int ivSize = getInitializationVectorSize();
366                 int ivByteSize = ivSize / BITS_PER_BYTE;
367 
368                 //now we know how large the iv is, so extract the iv bytes:
369                 iv = new byte[ivByteSize];
370                 System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
371 
372                 //remaining data is the actual encrypted ciphertext.  Isolate it:
373                 int encryptedSize = ciphertext.length - ivByteSize;
374                 encrypted = new byte[encryptedSize];
375                 System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
376             } catch (Exception e) {
377                 String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
378                 throw new CryptoException(msg, e);
379             }
380         }
381 
382         return decrypt(encrypted, key, iv);
383     }
384 
385     private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
386         if (log.isTraceEnabled()) {
387             log.trace("Attempting to decrypt incoming byte array of length " +
388                     (ciphertext != null ? ciphertext.length : 0));
389         }
390         byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
391         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         String transformationString = getTransformationString(streaming);
407         try {
408             return javax.crypto.Cipher.getInstance(transformationString);
409         } catch (Exception e) {
410             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             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         if (key == null || key.length == 0) {
442             throw new IllegalArgumentException("key argument cannot be null or empty.");
443         }
444         javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
445         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             return cipher.doFinal(bytes);
460         } catch (Exception e) {
461             String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
462             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             if (random != null) {
481                 if (spec != null) {
482                     cipher.init(mode, key, spec, random);
483                 } else {
484                     cipher.init(mode, key, random);
485                 }
486             } else {
487                 if (spec != null) {
488                     cipher.init(mode, key, spec);
489                 } else {
490                     cipher.init(mode, key);
491                 }
492             }
493         } catch (Exception e) {
494             String msg = "Unable to init cipher instance.";
495             throw new CryptoException(msg, e);
496         }
497     }
498 
499 
500     public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
501         byte[] iv = null;
502         boolean generate = isGenerateInitializationVectors(true);
503         if (generate) {
504             iv = generateInitializationVector(true);
505             if (iv == null || iv.length == 0) {
506                 throw new IllegalStateException("Initialization vector generation is enabled - generated vector " +
507                         "cannot be null or empty.");
508             }
509         }
510         encrypt(in, out, key, iv, generate);
511     }
512 
513     private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
514         if (prependIv && iv != null && iv.length > 0) {
515             try {
516                 //first write the IV:
517                 out.write(iv);
518             } catch (IOException e) {
519                 throw new CryptoException(e);
520             }
521         }
522 
523         crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
524     }
525 
526     public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
527         decrypt(in, out, key, isGenerateInitializationVectors(true));
528     }
529 
530     private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
531 
532         byte[] iv = null;
533         //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream:
534         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             int ivSize = getInitializationVectorSize();
538             int ivByteSize = ivSize / BITS_PER_BYTE;
539             iv = new byte[ivByteSize];
540             int read;
541 
542             try {
543                 read = in.read(iv);
544             } catch (IOException e) {
545                 String msg = "Unable to correctly read the Initialization Vector from the input stream.";
546                 throw new CryptoException(msg, e);
547             }
548 
549             if (read != ivByteSize) {
550                 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         decrypt(in, out, key, iv);
557     }
558 
559     private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
560         crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
561     }
562 
563     private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
564         if (in == null) {
565             throw new NullPointerException("InputStream argument cannot be null.");
566         }
567         if (out == null) {
568             throw new NullPointerException("OutputStream argument cannot be null.");
569         }
570 
571         javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
572 
573         CipherInputStream cis = new CipherInputStream(in, cipher);
574 
575         int bufSize = getStreamingBufferSize();
576         byte[] buffer = new byte[bufSize];
577 
578         int bytesRead;
579         try {
580             while ((bytesRead = cis.read(buffer)) != -1) {
581                 out.write(buffer, 0, bytesRead);
582             }
583         } catch (IOException e) {
584             throw new CryptoException(e);
585         }
586     }
587 
588     private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
589             throws CryptoException {
590 
591         javax.crypto.Cipher cipher = newCipherInstance(streaming);
592         java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
593         AlgorithmParameterSpec ivSpec = null;
594 
595         if (iv != null && iv.length > 0) {
596             ivSpec = createParameterSpec(iv, streaming);
597         }
598 
599         init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
600 
601         return cipher;
602     }
603 
604     protected AlgorithmParameterSpec createParameterSpec(byte[] iv, boolean streaming) {
605         return new IvParameterSpec(iv);
606     }
607 }