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