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.SimpleByteSource;
023    import org.apache.shiro.util.StringUtils;
024    import org.slf4j.Logger;
025    import org.slf4j.LoggerFactory;
026    
027    import javax.crypto.CipherInputStream;
028    import javax.crypto.spec.IvParameterSpec;
029    import javax.crypto.spec.SecretKeySpec;
030    import java.io.IOException;
031    import java.io.InputStream;
032    import java.io.OutputStream;
033    import java.security.Key;
034    import java.security.SecureRandom;
035    import 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     * @author Les Hazlewood
069     * @since 1.0
070     */
071    public abstract class JcaCipherService implements CipherService {
072    
073        /**
074         * Internal private log instance.
075         */
076        private static final Logger log = 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            this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice)
134            this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
135            this.generateInitializationVectors = true;
136        }
137    
138        /**
139         * Returns the cipher algorithm name that will be used for all encryption, decryption, and key operations (for
140         * example, 'AES', 'Blowfish', 'RSA', 'DSA', 'TripleDES', etc).
141         *
142         * @return the cipher algorithm name that will be used for all encryption, decryption, and key operations
143         */
144        public String getAlgorithmName() {
145            return algorithmName;
146        }
147    
148        /**
149         * Returns the size in bits (not bytes) of generated cipher keys.
150         *
151         * @return the size in bits (not bytes) of generated cipher keys.
152         */
153        public int getKeySize() {
154            return keySize;
155        }
156    
157        /**
158         * Sets the size in bits (not bytes) of generated cipher keys.
159         *
160         * @param keySize the size in bits (not bytes) of generated cipher keys.
161         */
162        public void setKeySize(int keySize) {
163            this.keySize = keySize;
164        }
165    
166        public boolean isGenerateInitializationVectors() {
167            return generateInitializationVectors;
168        }
169    
170        public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
171            this.generateInitializationVectors = generateInitializationVectors;
172        }
173    
174        /**
175         * Returns the algorithm-specific size in bits of generated initialization vectors.
176         *
177         * @return the algorithm-specific size in bits of generated initialization vectors.
178         */
179        public int getInitializationVectorSize() {
180            return initializationVectorSize;
181        }
182    
183        /**
184         * Sets the algorithm-specific initialization vector size in bits (not bytes!) to be used when generating
185         * initialization vectors.  The  value must be a multiple of {@code 8} to ensure that the IV can be represented
186         * as a byte array.
187         *
188         * @param initializationVectorSize the size in bits (not bytes) of generated initialization vectors.
189         * @throws IllegalArgumentException if the size is not a multiple of {@code 8}.
190         */
191        public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
192            if (initializationVectorSize % BITS_PER_BYTE != 0) {
193                String msg = "Initialization vector sizes are specified in bits, but must be a multiple of 8 so they " +
194                        "can be easily represented as a byte array.";
195                throw new IllegalArgumentException(msg);
196            }
197            this.initializationVectorSize = initializationVectorSize;
198        }
199    
200        protected boolean isGenerateInitializationVectors(boolean streaming) {
201            return isGenerateInitializationVectors();
202        }
203    
204        /**
205         * Returns the size in bytes of the internal buffer used to transfer data from one stream to another during stream
206         * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
207         * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
208         * <p/>
209         * Default size is {@code 512} bytes.
210         *
211         * @return the size of the internal buffer used to transfer data from one stream to another during stream
212         *         operations
213         */
214        public int getStreamingBufferSize() {
215            return streamingBufferSize;
216        }
217    
218        /**
219         * Sets the size in bytes of the internal buffer used to transfer data from one stream to another during stream
220         * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
221         * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
222         * <p/>
223         * Default size is {@code 512} bytes.
224         *
225         * @param streamingBufferSize the size of the internal buffer used to transfer data from one stream to another
226         *                            during stream operations
227         */
228        public void setStreamingBufferSize(int streamingBufferSize) {
229            this.streamingBufferSize = streamingBufferSize;
230        }
231    
232        /**
233         * Returns a source of randomness for encryption operations.  If one is not configured, and the underlying
234         * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
235         *
236         * @return a source of randomness for encryption operations.  If one is not configured, and the underlying
237         *         algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
238         */
239        public SecureRandom getSecureRandom() {
240            return secureRandom;
241        }
242    
243        /**
244         * Sets a source of randomness for encryption operations.  If one is not configured, and the underlying
245         * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
246         *
247         * @param secureRandom a source of randomness for encryption operations.  If one is not configured, and the
248         *                     underlying algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
249         */
250        public void setSecureRandom(SecureRandom secureRandom) {
251            this.secureRandom = secureRandom;
252        }
253    
254        protected static SecureRandom getDefaultSecureRandom() {
255            try {
256                return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
257            } catch (java.security.NoSuchAlgorithmException e) {
258                log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
259                        "platform's default SecureRandom algorithm.", e);
260                return new java.security.SecureRandom();
261            }
262        }
263    
264        protected SecureRandom ensureSecureRandom() {
265            SecureRandom random = getSecureRandom();
266            if (random == null) {
267                random = getDefaultSecureRandom();
268            }
269            return random;
270        }
271    
272        /**
273         * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
274         * creating a new {@code Cipher} instance.  This default implementation always returns
275         * {@link #getAlgorithmName() getAlgorithmName()}.  Block cipher implementations will want to override this method
276         * to support appending cipher operation modes and padding schemes.
277         *
278         * @param streaming if the transformation string is going to be used for a Cipher for stream-based encryption or not.
279         * @return the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
280         *         creating a new {@code Cipher} instance.
281         */
282        protected String getTransformationString(boolean streaming) {
283            return getAlgorithmName();
284        }
285    
286        protected byte[] generateInitializationVector(boolean streaming) {
287            int size = getInitializationVectorSize();
288            if (size <= 0) {
289                String msg = "initializationVectorSize property must be greater than zero.  This number is " +
290                        "typically set in the " + CipherService.class.getSimpleName() + " subclass constructor.  " +
291                        "Also check your configuration to ensure that if you are setting a value, it is positive.";
292                throw new IllegalStateException(msg);
293            }
294            if (size % BITS_PER_BYTE != 0) {
295                String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
296                throw new IllegalStateException(msg);
297            }
298            int sizeInBytes = size / BITS_PER_BYTE;
299            byte[] ivBytes = new byte[sizeInBytes];
300            SecureRandom random = ensureSecureRandom();
301            random.nextBytes(ivBytes);
302            return ivBytes;
303        }
304    
305        public ByteSource encrypt(byte[] plaintext, byte[] key) {
306            byte[] ivBytes = null;
307            boolean generate = isGenerateInitializationVectors(false);
308            if (generate) {
309                ivBytes = generateInitializationVector(false);
310                if (ivBytes == null || ivBytes.length == 0) {
311                    throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
312                            "cannot be null or empty.");
313                }
314            }
315            return encrypt(plaintext, key, ivBytes, generate);
316        }
317    
318        private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
319    
320            final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
321    
322            byte[] output;
323    
324            if (prependIv && iv != null && iv.length > 0) {
325    
326                byte[] encrypted = crypt(plaintext, key, iv, MODE);
327    
328                output = new byte[iv.length + encrypted.length];
329    
330                //now copy the iv bytes + encrypted bytes into one output array:
331    
332                // iv bytes:
333                System.arraycopy(iv, 0, output, 0, iv.length);
334    
335                // + encrypted bytes:
336                System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
337            } else {
338                output = crypt(plaintext, key, iv, MODE);
339            }
340    
341            if (log.isTraceEnabled()) {
342                log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
343                        "byte array is size " + (output != null ? output.length : 0));
344            }
345    
346            return new SimpleByteSource(output);
347    
348        }
349    
350        public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
351    
352            byte[] encrypted = ciphertext;
353    
354            //No IV, check if we need to read the IV from the stream:
355            byte[] iv = null;
356    
357            if (isGenerateInitializationVectors(false)) {
358                try {
359                    //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
360                    //is:
361                    // - the first N bytes is the initialization vector, where N equals the value of the
362                    // 'initializationVectorSize' attribute.
363                    // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.
364    
365                    //So we need to chunk the method argument into its constituent parts to find the IV and then use
366                    //the IV to decrypt the real ciphertext:
367    
368                    int ivSize = getInitializationVectorSize();
369                    int ivByteSize = ivSize / BITS_PER_BYTE;
370    
371                    //now we know how large the iv is, so extract the iv bytes:
372                    iv = new byte[ivByteSize];
373                    System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
374    
375                    //remaining data is the actual encrypted ciphertext.  Isolate it:
376                    int encryptedSize = ciphertext.length - ivByteSize;
377                    encrypted = new byte[encryptedSize];
378                    System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
379                } catch (Exception e) {
380                    String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
381                    throw new CryptoException(msg, e);
382                }
383            }
384    
385            return decrypt(encrypted, key, iv);
386        }
387    
388        private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
389            if (log.isTraceEnabled()) {
390                log.trace("Attempting to decrypt incoming byte array of length " +
391                        (ciphertext != null ? ciphertext.length : 0));
392            }
393            byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
394            return decrypted == null ? null : new SimpleByteSource(decrypted);
395        }
396    
397        /**
398         * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations.  The
399         * Cipher's {@code transformationString} for the {@code Cipher}.{@link javax.crypto.Cipher#getInstance getInstance}
400         * call is obtaind via the {@link #getTransformationString(boolean) getTransformationString} method.
401         *
402         * @param streaming {@code true} if the cipher instance will be used as a stream cipher, {@code false} if it will be
403         *                  used as a block cipher.
404         * @return a new JDK {@code Cipher} instance.
405         * @throws CryptoException if a new Cipher instance cannot be constructed based on the
406         *                         {@link #getTransformationString(boolean) getTransformationString} value.
407         */
408        private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException {
409            String transformationString = getTransformationString(streaming);
410            try {
411                return javax.crypto.Cipher.getInstance(transformationString);
412            } catch (Exception e) {
413                String msg = "Unable to acquire a Java JCA Cipher instance using " +
414                        javax.crypto.Cipher.class.getName() + ".getInstance( \"" + transformationString + "\" ). " +
415                        getAlgorithmName() + " under this configuration is required for the " +
416                        getClass().getName() + " instance to function.";
417                throw new CryptoException(msg, e);
418            }
419        }
420    
421        /**
422         * Functions as follows:
423         * <ol>
424         * <li>Creates a {@link #newCipherInstance(boolean) new JDK cipher instance}</li>
425         * <li>Converts the specified key bytes into an {@link #getAlgorithmName() algorithm}-compatible JDK
426         * {@link Key key} instance</li>
427         * <li>{@link #init(javax.crypto.Cipher, int, java.security.Key, AlgorithmParameterSpec, SecureRandom) Initializes}
428         * the JDK cipher instance with the JDK key</li>
429         * <li>Calls the {@link #crypt(javax.crypto.Cipher, byte[]) crypt(cipher,bytes)} method to either encrypt or
430         * decrypt the data based on the specified Cipher behavior mode
431         * ({@link javax.crypto.Cipher#ENCRYPT_MODE Cipher.ENCRYPT_MODE} or
432         * {@link javax.crypto.Cipher#DECRYPT_MODE Cipher.DECRYPT_MODE})</li>
433         * </ol>
434         *
435         * @param bytes the bytes to crypt
436         * @param key   the key to use to perform the encryption or decryption.
437         * @param iv    the initialization vector to use for the crypt operation (optional, may be {@code null}).
438         * @param mode  the JDK Cipher behavior mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE).
439         * @return the resulting crypted byte array
440         * @throws IllegalArgumentException if {@code bytes} are null or empty.
441         * @throws CryptoException          if Cipher initialization or the crypt operation fails
442         */
443        private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
444            if (key == null || key.length == 0) {
445                throw new IllegalArgumentException("key argument cannot be null or empty.");
446            }
447            javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
448            return crypt(cipher, bytes);
449        }
450    
451        /**
452         * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that
453         * might arise in an {@link CryptoException}
454         *
455         * @param cipher the JDK Cipher to finalize (perform the actual cryption)
456         * @param bytes  the bytes to crypt
457         * @return the resulting crypted byte array.
458         * @throws CryptoException if there is an illegal block size or bad padding
459         */
460        private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException {
461            try {
462                return cipher.doFinal(bytes);
463            } catch (Exception e) {
464                String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
465                throw new CryptoException(msg, e);
466            }
467        }
468    
469        /**
470         * Initializes the JDK Cipher with the specified mode and key.  This is primarily a utility method to catch any
471         * potential {@link java.security.InvalidKeyException InvalidKeyException} that might arise.
472         *
473         * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}.
474         * @param mode   the Cipher mode
475         * @param key    the Cipher's Key
476         * @param spec   the JDK AlgorithmParameterSpec for cipher initialization (optional, may be null).
477         * @param random the SecureRandom to use for cipher initialization (optional, may be null).
478         * @throws CryptoException if the key is invalid
479         */
480        private void init(javax.crypto.Cipher cipher, int mode, java.security.Key key,
481                          AlgorithmParameterSpec spec, SecureRandom random) throws CryptoException {
482            try {
483                if (random != null) {
484                    if (spec != null) {
485                        cipher.init(mode, key, spec, random);
486                    } else {
487                        cipher.init(mode, key, random);
488                    }
489                } else {
490                    if (spec != null) {
491                        cipher.init(mode, key, spec);
492                    } else {
493                        cipher.init(mode, key);
494                    }
495                }
496            } catch (Exception e) {
497                String msg = "Unable to init cipher instance.";
498                throw new CryptoException(msg, e);
499            }
500        }
501    
502    
503        public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
504            byte[] iv = null;
505            boolean generate = isGenerateInitializationVectors(true);
506            if (generate) {
507                iv = generateInitializationVector(true);
508                if (iv == null || iv.length == 0) {
509                    throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
510                            "cannot be null or empty.");
511                }
512            }
513            encrypt(in, out, key, iv, generate);
514        }
515    
516        private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
517            if (prependIv && iv != null && iv.length > 0) {
518                try {
519                    //first write the IV:
520                    out.write(iv);
521                } catch (IOException e) {
522                    throw new CryptoException(e);
523                }
524            }
525    
526            crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
527        }
528    
529        public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
530            decrypt(in, out, key, isGenerateInitializationVectors(true));
531        }
532    
533        private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
534    
535            byte[] iv = null;
536            //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream:
537            if (ivPrepended) {
538                //we are generating IVs, so we need to read the previously-generated IV from the stream before
539                //we decrypt the rest of the stream (we need the IV to decrypt):
540                int ivSize = getInitializationVectorSize();
541                int ivByteSize = ivSize / BITS_PER_BYTE;
542                iv = new byte[ivByteSize];
543                int read;
544    
545                try {
546                    read = in.read(iv);
547                } catch (IOException e) {
548                    String msg = "Unable to correctly read the Initialization Vector from the input stream.";
549                    throw new CryptoException(msg, e);
550                }
551    
552                if (read != ivByteSize) {
553                    throw new CryptoException("Unable to read initialization vector bytes from the InputStream.  " +
554                            "This is required when initialization vectors are autogenerated during an encryption " +
555                            "operation.");
556                }
557            }
558    
559            decrypt(in, out, key, iv);
560        }
561    
562        private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
563            crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
564        }
565    
566        private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
567            if (in == null) {
568                throw new NullPointerException("InputStream argument cannot be null.");
569            }
570            if (out == null) {
571                throw new NullPointerException("OutputStream argument cannot be null.");
572            }
573    
574            javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
575    
576            CipherInputStream cis = new CipherInputStream(in, cipher);
577    
578            int bufSize = getStreamingBufferSize();
579            byte[] buffer = new byte[bufSize];
580    
581            int bytesRead;
582            try {
583                while ((bytesRead = cis.read(buffer)) != -1) {
584                    out.write(buffer, 0, bytesRead);
585                }
586            } catch (IOException e) {
587                throw new CryptoException(e);
588            }
589        }
590    
591        private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
592                throws CryptoException {
593    
594            javax.crypto.Cipher cipher = newCipherInstance(streaming);
595            java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
596            IvParameterSpec ivSpec = null;
597            if (iv != null && iv.length > 0) {
598                ivSpec = new IvParameterSpec(iv);
599            }
600    
601            init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
602    
603            return cipher;
604        }
605    }