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;
020
021import org.apache.shiro.util.ByteSource;
022import org.apache.shiro.util.StringUtils;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import javax.crypto.CipherInputStream;
027import javax.crypto.spec.IvParameterSpec;
028import javax.crypto.spec.SecretKeySpec;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.OutputStream;
032import java.security.Key;
033import java.security.SecureRandom;
034import 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 */
069public 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}