001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.crypto.cipher;
020
021import org.apache.shiro.crypto.CryptoException;
022import org.apache.shiro.lang.util.ByteSource;
023import org.apache.shiro.lang.util.StringUtils;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027import javax.crypto.CipherInputStream;
028import javax.crypto.spec.IvParameterSpec;
029import javax.crypto.spec.SecretKeySpec;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.OutputStream;
033import java.security.Key;
034import java.security.SecureRandom;
035import java.security.spec.AlgorithmParameterSpec;
036
037/**
038 * Abstract {@code CipherService} implementation utilizing Java's JCA APIs.
039 * <h2>Auto-generated Initialization Vectors</h2>
040 * Shiro does something by default for all of its {@code CipherService} implementations that the JCA
041 * {@link javax.crypto.Cipher Cipher} does not do:  by default,
042 * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly
043 * generated and prepended to encrypted data before returning from the {@code encrypt} methods.  That is, the returned
044 * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual
045 * encrypted data byte array.  The {@code decrypt} methods in turn know to read this prepended initialization vector
046 * before decrypting the real data that follows.
047 * <p/>
048 * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted
049 * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>.
050 * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources
051 * that are the same or similar.
052 * <p/>
053 * You can turn off this behavior by setting the
054 * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it
055 * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing
056 * a critical security feature.
057 * <h3>Initialization Vector Size</h3>
058 * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to
059 * {@code 128} bits, a fairly common size.  Initialization vector sizes are very algorithm specific however, so subclass
060 * implementations will often override this value in their constructor if necessary.
061 * <p/>
062 * Also note that {@code initializationVectorSize} values are specified in the number of
063 * bits (not bytes!) to match common references in most cryptography documentation.  In practice though, initialization
064 * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple
065 * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the
066 * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this).
067 *
068 * @since 1.0
069 */
070@SuppressWarnings("checkstyle:MethodCount")
071public abstract class JcaCipherService implements CipherService {
072
073    /**
074     * Internal private log instance.
075     */
076    private static final Logger LOGGER = LoggerFactory.getLogger(JcaCipherService.class);
077
078    /**
079     * Default key size (in bits) for generated keys.
080     */
081    private static final int DEFAULT_KEY_SIZE = 128;
082
083    /**
084     * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations
085     */
086    private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
087
088    private static final int BITS_PER_BYTE = 8;
089
090    /**
091     * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance.
092     */
093    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
094
095    /**
096     * The name of the cipher algorithm to use for all encryption, decryption, and key operations
097     */
098    private String algorithmName;
099
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}