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     */
019    package org.apache.shiro.crypto;
020    
021    import org.apache.shiro.util.ByteSource;
022    import org.apache.shiro.util.StringUtils;
023    import org.slf4j.Logger;
024    import org.slf4j.LoggerFactory;
025    
026    import javax.crypto.CipherInputStream;
027    import javax.crypto.spec.IvParameterSpec;
028    import javax.crypto.spec.SecretKeySpec;
029    import java.io.IOException;
030    import java.io.InputStream;
031    import java.io.OutputStream;
032    import java.security.Key;
033    import java.security.SecureRandom;
034    import java.security.spec.AlgorithmParameterSpec;
035    
036    /**
037     * Abstract {@code CipherService} implementation utilizing Java's JCA APIs.
038     * <h2>Auto-generated Initialization Vectors</h2>
039     * Shiro does something by default for all of its {@code CipherService} implementations that the JCA
040     * {@link javax.crypto.Cipher Cipher} does not do:  by default,
041     * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly
042     * generated and prepended to encrypted data before returning from the {@code encrypt} methods.  That is, the returned
043     * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual
044     * encrypted data byte array.  The {@code decrypt} methods in turn know to read this prepended initialization vector
045     * before decrypting the real data that follows.
046     * <p/>
047     * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted
048     * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>.
049     * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources
050     * that are the same or similar.
051     * <p/>
052     * You can turn off this behavior by setting the
053     * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it
054     * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing
055     * a critical security feature.
056     * <h3>Initialization Vector Size</h3>
057     * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to
058     * {@code 128} bits, a fairly common size.  Initialization vector sizes are very algorithm specific however, so subclass
059     * implementations will often override this value in their constructor if necessary.
060     * <p/>
061     * Also note that {@code initializationVectorSize} values are specified in the number of
062     * bits (not bytes!) to match common references in most cryptography documentation.  In practice though, initialization
063     * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple
064     * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the
065     * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this).
066     *
067     * @since 1.0
068     */
069    public abstract class JcaCipherService implements CipherService {
070    
071        /**
072         * Internal private log instance.
073         */
074        private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class);
075    
076        /**
077         * Default key size (in bits) for generated keys.
078         */
079        private static final int DEFAULT_KEY_SIZE = 128;
080    
081        /**
082         * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations
083         */
084        private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
085    
086        private static final int BITS_PER_BYTE = 8;
087    
088        /**
089         * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance.
090         */
091        private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
092    
093        /**
094         * The name of the cipher algorithm to use for all encryption, decryption, and key operations
095         */
096        private String algorithmName;
097    
098        /**
099         * 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            IvParameterSpec ivSpec = null;
594            if (iv != null && iv.length > 0) {
595                ivSpec = new IvParameterSpec(iv);
596            }
597    
598            init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
599    
600            return cipher;
601        }
602    }