You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lh...@apache.org on 2010/04/17 04:00:41 UTC

svn commit: r935106 [1/2] - in /incubator/shiro/trunk: core/src/main/java/org/apache/shiro/crypto/ core/src/main/java/org/apache/shiro/crypto/hash/ core/src/main/java/org/apache/shiro/io/ core/src/main/java/org/apache/shiro/mgt/ core/src/main/java/org/...

Author: lhazlewood
Date: Sat Apr 17 02:00:40 2010
New Revision: 935106

URL: http://svn.apache.org/viewvc?rev=935106&view=rev
Log:
Refactoring of cryptograhy support.  Converted Cipher name and implementations to 'CipherService' and relative implementations to reflect their stateful nature.  Did more JavadDoc than I ever care to remember...

Added:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CryptoException.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/OperationMode.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/PaddingScheme.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/util/ByteSource.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/util/SimpleByteSource.java
    incubator/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/
    incubator/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/AesCipherServiceTest.java
    incubator/shiro/trunk/core/src/test/java/org/apache/shiro/crypto/BlowfishCipherServiceTest.java
Removed:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipher.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/Cipher.java
Modified:
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/hash/Hash.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/package-info.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/io/Serializer.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
    incubator/shiro/trunk/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java
    incubator/shiro/trunk/samples/web/src/main/webapp/logout.jsp
    incubator/shiro/trunk/web/src/test/java/org/apache/shiro/web/WebRememberMeManagerTest.java

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AbstractSymmetricCipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import javax.crypto.KeyGenerator;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Base abstract class for supporting symmetric key cipher algorithms.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public abstract class AbstractSymmetricCipherService extends JcaCipherService {
+
+    protected AbstractSymmetricCipherService(String algorithmName) {
+        super(algorithmName);
+    }
+
+    /**
+     * Generates a new {@link java.security.Key Key} suitable for this CipherService's {@link #getAlgorithmName() algorithm}
+     * by calling {@link #generateNewKey(int) generateNewKey(128)} (uses a 128 bit size by default).
+     *
+     * @return a new {@link java.security.Key Key}, 128 bits in length.
+     */
+    public Key generateNewKey() {
+        return generateNewKey(getKeySize());
+    }
+
+    /**
+     * Generates a new {@link Key Key} of the specified size suitable for this CipherService
+     * (based on the {@link #getAlgorithmName() algorithmName} using the JDK {@link javax.crypto.KeyGenerator KeyGenerator}.
+     *
+     * @param keyBitSize the bit size of the key to create
+     * @return the created key suitable for use with this CipherService
+     */
+    public Key generateNewKey(int keyBitSize) {
+        KeyGenerator kg;
+        try {
+            kg = KeyGenerator.getInstance(getAlgorithmName());
+        } catch (NoSuchAlgorithmException e) {
+            String msg = "Unable to acquire " + getAlgorithmName() + " algorithm.  This is required to function.";
+            throw new IllegalStateException(msg, e);
+        }
+        kg.init(keyBitSize);
+        return kg.generateKey();
+    }
+
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/AesCipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * {@code CipherService} using the {@code AES} cipher algorithm for all encryption, decryption, and key operations.
+ * <p/>
+ * The AES algorithm can support key sizes of {@code 128}, {@code 192} and {@code 256} bits<b>*</b>.  This implementation
+ * defaults to 128 bits.
+ * <p/>
+ * Note that this class retains the parent class's default {@link OperationMode#CFB CFB} mode of operation
+ * instead of the typical JDK default of {@link OperationMode#ECB ECB}.  {@code ECB} should not be used in
+ * security-sensitive environments because {@code ECB} does not allow for initialization vectors, which are
+ * considered necessary for strong encryption.  See the {@link DefaultBlockCipherService parent class}'s JavaDoc and the
+ * {@link JcaCipherService JcaCipherService} JavaDoc for more on why the JDK default should not be used and is not
+ * used in this implementation.
+ * <p/>
+ * <b>*</b> Generating and using AES key sizes greater than 128 require installation of the
+ * <a href="http://java.sun.com/javase/downloads/index.jsp">Java Cryptography Extension (JCE) Unlimited Strength
+ * Jurisdiction Policy files</a>.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class AesCipherService extends DefaultBlockCipherService {
+
+    private static final String ALGORITHM_NAME = "AES";
+
+    /**
+     * Creates a new {@link CipherService} instance using the {@code AES} cipher algorithm with the following
+     * important cipher default attributes:
+     * <table>
+     * <tr>
+     * <th>Attribute</th>
+     * <th>Value</th>
+     * </tr>
+     * <tr>
+     * <td>{@link #setKeySize keySize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setBlockSize blockSize}</td>
+     * <td>{@code 128} bits (required for {@code AES}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setMode mode}</td>
+     * <td>{@link OperationMode#CFB CFB}<b>*</b></td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setPaddingScheme paddingScheme}</td>
+     * <td>{@link PaddingScheme#PKCS5 PKCS5}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setInitializationVectorSize(int) initializationVectorSize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}</td>
+     * <td>{@code true}<b>**</b></td>
+     * </tr>
+     * </table>
+     * <p/>
+     * <b>*</b> The {@link OperationMode#CFB CFB} operation mode is used instead of the JDK default {@code ECB} to
+     * ensure strong encryption.  {@code ECB} should not be used in security-sensitive environments - see the
+     * {@link DefaultBlockCipherService DefaultBlockCipherService} class JavaDoc's &quot;Operation Mode&quot; section
+     * for more.
+     * <p/>
+     * <b>**</b>In conjunction with the default {@code CFB} operation mode, initialization vectors are generated by
+     * default to ensure strong encryption.  See the {@link JcaCipherService JcaCipherService} class JavaDoc for more.
+     */
+    public AesCipherService() {
+        super(ALGORITHM_NAME);
+    }
+
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/BlowfishCipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+/**
+ * {@code CipherService} using the {@code Blowfish} cipher algorithm for all encryption, decryption, and key operations.
+ * <p/>
+ * The Blowfish algorithm can support key sizes between {@code 32} and {@code 448} bits<b>*</b>, inclusive.  However,
+ * modern cryptanalysis techniques render keys of 80 bits or less mostly worthless - use {@code 128} or more whenever
+ * possible.
+ * <p/>
+ * Note that this class retains the parent class's default {@link OperationMode#CFB CFB} mode of operation
+ * instead of the typical JDK default of {@link OperationMode#ECB ECB}.  {@code ECB} should not be used in
+ * security-sensitive environments because {@code ECB} does not allow for initialization vectors, which are
+ * considered necessary for strong encryption.  See the {@link DefaultBlockCipherService parent class}'s JavaDoc and the
+ * {@link JcaCipherService JcaCipherService} JavaDoc for more on why the JDK default should not be used and is not
+ * used in this implementation.
+ * <p/>
+ * <b>*</b> Generating and using Blowfish key sizes greater than 128 require installation of the
+ * <a href="http://java.sun.com/javase/downloads/index.jsp">Java Cryptography Extension (JCE) Unlimited Strength
+ * Jurisdiction Policy files</a>.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class BlowfishCipherService extends DefaultBlockCipherService {
+
+    private static final String ALGORITHM_NAME = "Blowfish";
+    private static final int BLOCK_SIZE = 64;
+
+    /**
+     * Creates a new {@link CipherService} instance using the {@code Blowfish} cipher algorithm with the following
+     * important cipher default attributes:
+     * <table>
+     * <tr>
+     * <th>Attribute</th>
+     * <th>Value</th>
+     * </tr>
+     * <tr>
+     * <td>{@link #setKeySize keySize}</td>
+     * <td>{@code 128} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setBlockSize blockSize}</td>
+     * <td>{@code 64} bits (required for {@code Blowfish})</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setMode mode}</td>
+     * <td>{@link OperationMode#CFB CFB}<b>*</b></td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setPaddingScheme paddingScheme}</td>
+     * <td>{@link PaddingScheme#PKCS5 PKCS5}</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setInitializationVectorSize(int) initializationVectorSize}</td>
+     * <td>{@code 64} bits</td>
+     * </tr>
+     * <tr>
+     * <td>{@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}</td>
+     * <td>{@code true}<b>**</b></td>
+     * </tr>
+     * </table>
+     * <p/>
+     * <b>*</b> The {@link OperationMode#CFB CFB} operation mode is used instead of the JDK default {@code ECB} to
+     * ensure strong encryption.  {@code ECB} should not be used in security-sensitive environments - see the
+     * {@link DefaultBlockCipherService DefaultBlockCipherService} class JavaDoc's &quot;Operation Mode&quot; section
+     * for more.
+     * <p/>
+     * <b>**</b>In conjunction with the default {@code CFB} operation mode, initialization vectors are generated by
+     * default to ensure strong encryption.  See the {@link JcaCipherService JcaCipherService} class JavaDoc for more.
+     */
+    public BlowfishCipherService() {
+        super(ALGORITHM_NAME);
+        setInitializationVectorSize(BLOCK_SIZE); //like most block ciphers, the IV size is the same as the block size
+    }
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@code CipherService} uses a cryptographic algorithm called a
+ * <a href="http://en.wikipedia.org/wiki/Cipher">Cipher</a> to convert an original input source using a {@code key} to
+ * an uninterpretable format.  The resulting encrypted output is only able to be converted back to original form with
+ * a {@code key} as well.  {@code CipherService}s can perform both encryption and decryption.
+ * <h2>Cipher Basics</h2>
+ * For what is known as <em>Symmetric</em> {@code Cipher}s, the {@code Key} used to encrypt the source is the same
+ * as (or trivially similar to) the {@code Key} used to decrypt it.
+ * <p/>
+ * For <em>Asymmetric</em> {@code Cipher}s, the encryption {@code Key} is not the same as the decryption {@code Key}.
+ * The most common type of Asymmetric Ciphers are based on what is called public/private key pairs:
+ * <p/>
+ * A <em>private</em> key is known only to a single party, and as its name implies, is supposed be kept very private
+ * and secure.  A <em>public</em> key that is associated with the private key can be disseminated freely to anyone.
+ * Then data encrypted by the public key can only be decrypted by the private key and vice versa, but neither party
+ * need share their private key with anyone else.  By not sharing a private key, you can guarantee no 3rd party can
+ * intercept the key and therefore use it to decrypt a message.
+ * <p/>
+ * This asymmetric key technology was created as a
+ * more secure alternative to symmetric ciphers that sometimes suffer from man-in-the-middle attacks since, for
+ * data shared between two parties, the same Key must also be shared and may be compromised.
+ * <p/>
+ * Note that a symmetric cipher is perfectly fine to use if you just want to encode data in a format no one else
+ * can understand and you never give away the key.  Shiro uses a symmetric cipher when creating certain
+ * HTTP Cookies for example - because it is often undesirable to have user's identity stored in a plain-text cookie,
+ * that identity can be converted via a symmetric cipher.  Since the the same exact Shiro application will receive
+ * the cookie, it can decrypt it via the same {@code Key} and there is no potential for discovery since that Key
+ * is never shared with anyone.
+ * <h2>{@code CipherService}s vs JDK {@link javax.crypto.Cipher Cipher}s</h2>
+ * Shiro {@code CipherService}s essentially do the same things as JDK {@link javax.crypto.Cipher Cipher}s, but in
+ * simpler and easier-to-use ways for most application developers.  When thinking about encrypting and decrypting data
+ * in an application, most app developers want what a {@code CipherService} provides, rather than having to manage the
+ * lower-level intricacies of the JDK's {@code Cipher} API.  Here are a few reasons why most people prefer
+ * {@code CipherService}s:
+ * <ul>
+ * <li><b>Stateless Methods</b> - {@code CipherService} method calls do not retain state between method invocations.
+ * JDK {@code Cipher} instances do retain state across invocations, requiring its end-users to manage the instance
+ * and its state themselves.</li>
+ * <li><b>Thread Safety</b> - {@code CipherService} instances are thread-safe inherently because no state is
+ * retained across method invocations.  JDK {@code Cipher} instances retain state and cannot be used by multiple
+ * threads concurrently.</li>
+ * <li><b>Single Operation</b> - {@code CipherService} method calls are single operation methods: encryption or
+ * decryption in their entirety are done as a single method call.  This is ideal for the large majority of developer
+ * needs where you have something unencrypted and just want it decrypted (or vice versa) in a single method call.  In
+ * contrast, JDK {@code Cipher} instances can support encrypting/decrypting data in chunks over time (because it
+ * retains state), but this often introduces API clutter and confusion for most application developers.</li>
+ * <li><b>Type Safe</b> - There are {@code CipherService} implementations for different Cipher algorithms
+ * ({@code AesCipherService}, {@code BlowfishCipherService}, etc).  There is only one JDK {@code Cipher} class to
+ * represent all cipher algorithms/instances.
+ * <li><b>Simple Construction</b> - Because {@code CipherService} instances are type-safe, instantiating and using
+ * one is often as simple as calling the default constructor, for example, <code>new AesCipherService();</code>.  The
+ * JDK {@code Cipher} class however requires using a procedural factory method with String arguments to indicate how
+ * the instance should be created.  The String arguments themselves are somewhat cryptic and hard to
+ * understand unless you're a security expert.  Shiro hides these details from you, but allows you to configure them
+ * if you want.</li>
+ * </ul>
+ *
+ * @author Les Hazlewood
+ * @see BlowfishCipherService
+ * @see AesCipherService
+ * @since 1.0
+ */
+public interface CipherService {
+
+    /**
+     * Decrypts encrypted data via the specified cipher key and returns the original (pre-encrypted) data.
+     * Note that the key must be in a format understood by the CipherService implementation.
+     *
+     * @param encrypted     the previously encrypted data to decrypt
+     * @param decryptionKey the cipher key used during decryption.
+     * @return a byte source representing the original form of the specified encrypted data.
+     * @throws CryptoException if there is an error during decryption
+     */
+    ByteSource decrypt(byte[] encrypted, byte[] decryptionKey) throws CryptoException;
+
+    /**
+     * Receives encrypted data from the given {@code InputStream}, decrypts it, and sends the resulting decrypted data
+     * to the given {@code OutputStream}.
+     * <p/>
+     * <b>NOTE:</b> This method <em>does NOT</em> flush or close either stream prior to returning - the caller must
+     * do so when they are finished with the streams.  For example:
+     * <pre>
+     * try {
+     *     InputStream in = ...
+     *     OutputStream out = ...
+     *     cipherService.decrypt(in, out, decryptionKey);
+     * } finally {
+     *     if (in != null) {
+     *         try {
+     *             in.close();
+     *         } catch (IOException ioe1) { ... log, trigger event, etc }
+     *     }
+     *     if (out != null) {
+     *         try {
+     *             out.close();
+     *         } catch (IOException ioe2) { ... log, trigger event, etc }
+     *     }
+     * }
+     * </pre>
+     *
+     * @param in            the stream supplying the data to decrypt
+     * @param out           the stream to send the decrypted data
+     * @param decryptionKey the cipher key to use for decryption
+     * @throws CryptoException if there is any problem during decryption.
+     */
+    void decrypt(InputStream in, OutputStream out, byte[] decryptionKey) throws CryptoException;
+
+    /**
+     * Encrypts data via the specified cipher key.  Note that the key must be in a format understood by
+     * the {@code CipherService} implementation.
+     *
+     * @param raw           the data to encrypt
+     * @param encryptionKey the cipher key used during encryption.
+     * @return a byte source with the encrypted representation of the specified raw data.
+     * @throws CryptoException if there is an error during encryption
+     */
+    ByteSource encrypt(byte[] raw, byte[] encryptionKey) throws CryptoException;
+
+    /**
+     * Receives the data from the given {@code InputStream}, encrypts it, and sends the resulting encrypted data to the
+     * given {@code OutputStream}.
+     * <p/>
+     * <b>NOTE:</b> This method <em>does NOT</em> flush or close either stream prior to returning - the caller must
+     * do so when they are finished with the streams.  For example:
+     * <pre>
+     * try {
+     *     InputStream in = ...
+     *     OutputStream out = ...
+     *     cipherService.encrypt(in, out, encryptionKey);
+     * } finally {
+     *     if (in != null) {
+     *         try {
+     *             in.close();
+     *         } catch (IOException ioe1) { ... log, trigger event, etc }
+     *     }
+     *     if (out != null) {
+     *         try {
+     *             out.close();
+     *         } catch (IOException ioe2) { ... log, trigger event, etc }
+     *     }
+     * }
+     * </pre>
+     *
+     * @param in            the stream supplying the data to encrypt
+     * @param out           the stream to send the encrypted data
+     * @param encryptionKey the cipher key to use for encryption
+     * @throws CryptoException if there is any problem during encryption.
+     */
+    void encrypt(InputStream in, OutputStream out, byte[] encryptionKey) throws CryptoException;
+
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CryptoException.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CryptoException.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CryptoException.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/CryptoException.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.ShiroException;
+
+/**
+ * Base Shiro exception for problems encountered during cryptographic operations.
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public class CryptoException extends ShiroException {
+
+    public CryptoException(String message) {
+        super(message);
+    }
+
+    public CryptoException(Throwable cause) {
+        super(cause);
+    }
+
+    public CryptoException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/DefaultBlockCipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,533 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.StringUtils;
+
+/**
+ * Base abstract class for block cipher algorithms.
+ *
+ * <h2>Usage</h2>
+ * Note that this class exists mostly to simplify algorithm-specific subclasses.  Unless you understand the concepts of
+ * cipher modes of operations, block sizes, and padding schemes, and you want direct control of these things, you should
+ * typically not uses instances of this class directly.  Instead, algorithm-specific subclasses, such as
+ * {@link AesCipherService}, {@link BlowfishCipherService}, and others are usually better suited for regular use.
+ * <p/>
+ * However, if you have the need to create a custom block cipher service where no sufficient algorithm-specific subclass
+ * exists in Shiro, this class would be very useful.
+ *
+ * <h2>Configuration</h2>
+ * Block ciphers can accept configuration parameters that direct how they operate.  These parameters concatenated
+ * together in a single String comprise what the JDK JCA documentation calls a
+ * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#trans">transformation
+ * string</a>.  We think that it is better for Shiro to construct this transformation string automatically based on its
+ * constituent parts instead of having the end-user construct the string manually, which may be error prone or
+ * confusing.  To that end, Shiro {@link DefaultBlockCipherService}s have attributes that can be set individually in
+ * a type-safe manner based on your configuration needs, and Shiro will build the transformation string for you.
+ * <p/>
+ * The following sections typically document the configuration options for block (byte array)
+ * {@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])} method invocations.  Streaming configuration
+ * for those same attributes are done via mirrored {@code streaming}* attributes, and their purpose is identical, but
+ * they're only used during streaming {@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+ * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])} methods.  See the &quot;Streaming&quot;
+ * section below for more.
+ *
+ * <h3>Block Size</h3>
+ * The block size specifies the number of bits (not bytes) that the cipher operates on when performing an operation.
+ * It can be specified explicitly via the {@link #setBlockSize blockSize} attribute.  If not set, the JCA Provider
+ * default will be used based on the cipher algorithm.  Block sizes are usually very algorithm specific, so set this
+ * value only if you know you don't want the JCA Provider's default for the desired algorithm.  For example, the
+ * AES algorithm's Rijndael implementation <em>only</em> supports a 128 bit block size and will not work with any other
+ * size.
+ * <p/>
+ * Also note that the {@link #setInitializationVectorSize initializationVectorSize} is usually the same as the
+ * {@link #setBlockSize blockSize} in block ciphers.  If you change either attribute, you should ensure that the other
+ * attribute is correct for the target cipher algorithm.
+ *
+ * <h3>Operation Mode</h3>
+ * You may set the block cipher's<a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">mode of
+ * operation</a> via the {@link #setMode(OperationMode) mode} attribute, which accepts a type-safe
+ * {@link OperationMode OperationMode} enum instance.  This type safety helps avoid typos when specifying the mode and
+ * guarantees that the mode name will be recognized by the underlying JCA Provider.
+ * <p/>
+ * <b>*</b>If no operation mode is specified, Shiro defaults all of its block {@code CipherService} instances to the
+ * {@link OperationMode#CFB CFB} mode, specifically to support auto-generation of initialization vectors during
+ * encryption.  This is different than the JDK's default {@link OperationMode#ECB ECB} mode because {@code ECB} does
+ * not support initialization vectors, which are necessary for strong encryption.  See  the
+ * {@link org.apache.shiro.crypto.JcaCipherService JcaCipherService parent class} class JavaDoc for an extensive
+ * explanation on why we do this and why we do not use the Sun {@code ECB} default.  You also might also want read
+ * the <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29">Wikipedia
+ * section on ECB<a/> and look at the encrypted image to see an example of why {@code ECB} should not be used in
+ * security-sensitive environments.
+ * <p/>
+ * In the rare case that you need to override the default with a mode not represented
+ * by the {@link OperationMode} enum, you may specify the raw mode name string that will be recognized by your JCA
+ * provider via the {@link #setModeName modeName} attribute.  Because this is not type-safe, it is recommended only to
+ * use this attribute if the {@link OperationMode} enum does not represent your desired mode.
+ * <p/>
+ * <b>NOTE:</b> If you change the mode to one that does not support initialization vectors (such as
+ * {@link OperationMode#ECB ECB} or {@link OperationMode#NONE NONE}), you <em>must</em> turn off auto-generated
+ * initialization vectors by setting {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors}
+ * to {@code false}.  Abandoning initialization vectors significantly weakens encryption, so think twice before
+ * disabling this feature.
+ *
+ * <h3>Padding Scheme</h3>
+ * Because block ciphers process messages in fixed-length blocks, if the final block in a message is not equal to the
+ * block length, <a href="http://en.wikipedia.org/wiki/Padding_(cryptography)">padding</a> is applied to match that
+ * size to maintain the total length of the message.  This is good because it protects data patterns from being
+ * identified  - when all chunks look the same length, it is much harder to infer what that data might be.
+ * <p/>
+ * You may set a padding scheme via the {@link #setPaddingScheme(PaddingScheme) paddingScheme} attribute, which
+ * accepts a type-safe {@link PaddingScheme PaddingScheme} enum instance.  Like the {@link OperationMode} enum,
+ * this enum offers type safety to help avoid typos and guarantees that the mode will be recongized by the underlying
+ * JCA provider.
+ * <p/>
+ * <b>*</b>If no padding scheme is specified, this class defaults to the {@link PaddingScheme#PKCS5} scheme, specifically
+ * to be compliant with the default behavior of auto-generating initialization vectors during encryption (see the
+ * {@link org.apache.shiro.crypto.JcaCipherService JcaCipherService parent class} class JavaDoc for why).
+ * <p/>
+ * In the rare case that you need to override the default with a scheme not represented by the {@link PaddingScheme}
+ * enum, you may specify the raw padding scheme name string that will be recognized by your JCA provider via the
+ * {@link #setPaddingScheme paddingSchemeName} attribute.  Because this is not type-safe, it is recommended only to
+ * use this attribute if the {@link PaddingScheme} enum does not represent your desired scheme.
+ *
+ * <h2>Streaming</h2>
+ * Most people don't think of using block ciphers as stream ciphers, since their name implies working
+ * with block data (i.e. byte arrays) only.  However, block ciphers can be turned into byte-oriented stream ciphers by
+ * using an appropriate {@link OperationMode operation mode} with a {@link #getStreamingBlockSize() streaming block size}
+ * of 8 bits.  This is why the {@link CipherService} interface provides both block and streaming operations.
+ * <p/>
+ * Because this streaming 8-bit block size rarely changes across block-cipher algorithms, default values have been set
+ * for all three streaming configuration parameters.  The defaults are:
+ * <ul>
+ * <li>{@link #setStreamingBlockSize(int) streamingBlockSize} = {@code 8} (bits)</li>
+ * <li>{@link #setStreamingMode streamingMode} = {@link OperationMode#CFB CFB}</li>
+ * <li>{@link #setStreamingPaddingScheme(PaddingScheme) streamingPaddingScheme} = {@link PaddingScheme#NONE none} (since
+ * the block size is already the most atomic size of a single byte)</li>
+ * </ul>
+ * <p/>
+ * These attributes have the same meaning as the {@code mode}, {@code blockSize}, and {@code paddingScheme} attributes
+ * described above, but they are applied during streaming method invocations only ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])}
+ * and {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+ *
+ * @author Les Hazlewood
+ * @see BlowfishCipherService
+ * @see AesCipherService
+ * @see <a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">Wikipedia: Block Cipher Modes of Operation</a>
+ * @since 1.0
+ */
+public class DefaultBlockCipherService extends AbstractSymmetricCipherService {
+
+    private static final int DEFAULT_BLOCK_SIZE = 0;
+
+    private static final String TRANSFORMATION_STRING_DELIMITER = "/";
+    private static final int DEFAULT_STREAMING_BLOCK_SIZE = 8; //8 bits (1 byte)
+
+    private String modeName;
+    private int blockSize; //size in bits (not bytes) - i.e. a blockSize of 8 equals 1 byte. negative or zero value = use system default
+    private String paddingSchemeName;
+
+    private String streamingModeName;
+    private int streamingBlockSize;
+    private String streamingPaddingSchemeName;
+
+    private String transformationString; //cached value - rebuilt whenever any of its constituent parts change
+    private String streamingTransformationString; //cached value - rebuilt whenever any of its constituent parts change
+
+
+    /**
+     * Creates a new {@link DefaultBlockCipherService} using the specified block cipher {@code algorithmName}.  Per this
+     * class's JavaDoc, this constructor also sets the following defaults:
+     * <ul>
+     * <li>{@code streamingMode} = {@link OperationMode#CFB CFB}</li>
+     * <li>{@code streamingPaddingScheme} = {@link PaddingScheme#NONE none}</li>
+     * <li>{@code streamingBlockSize} = 8</li>
+     * </ul>
+     * All other attributes are null/unset, indicating the JCA Provider defaults will be used.
+     *
+     * @param algorithmName the block cipher algorithm to use when encrypting and decrypting
+     */
+    public DefaultBlockCipherService(String algorithmName) {
+        super(algorithmName);
+
+        this.modeName = OperationMode.CFB.name();
+        this.paddingSchemeName = PaddingScheme.PKCS5.getTransformationName();
+        this.blockSize = DEFAULT_BLOCK_SIZE; //0 = use the JCA provider's default
+
+        this.streamingModeName = OperationMode.CFB.name();
+        this.streamingPaddingSchemeName = PaddingScheme.NONE.getTransformationName();
+        this.streamingBlockSize = DEFAULT_STREAMING_BLOCK_SIZE;
+    }
+
+    /**
+     * Returns the cipher operation mode name (as a String) to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingModeName() streamingModeName} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     *
+     * @return the cipher operation mode name (as a String) to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default
+     *         mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public String getModeName() {
+        return modeName;
+    }
+
+    /**
+     * Sets the cipher operation mode name to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string.  A {@code null} value indicates that the JCA Provider
+     * default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingModeName() streamingModeName} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> most standard mode names are represented by the {@link OperationMode OperationMode} enum.  That enum
+     * should be used with the {@link #setMode mode} attribute when possible to retain type-safety and reduce the
+     * possibility of errors.  This method is better used if the {@link OperationMode} enum does not represent the
+     * necessary mode.
+     *
+     * @param modeName the cipher operation mode name to be used when constructing
+     *                 {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *                 default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * @see #setMode
+     */
+    public void setModeName(String modeName) {
+        this.modeName = modeName;
+        //clear out the transformation string so the next invocation will rebuild it with the new mode:
+        this.transformationString = null;
+    }
+
+    /**
+     * Sets the cipher operation mode of operation to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string.  A {@code null} value indicates that the JCA Provider
+     * default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #setStreamingMode streamingMode} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * If the {@link OperationMode} enum cannot represent your desired mode, you can set the name explicitly
+     * via the {@link #setModeName modeName} attribute directly.  However, because {@link OperationMode} represents all
+     * standard JDK mode names already, ensure that your underlying JCA Provider supports the non-standard name first.
+     *
+     * @param mode the cipher operation mode to be used when constructing
+     *             {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *             default mode for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setMode(OperationMode mode) {
+        setModeName(mode.name());
+    }
+
+    /**
+     * Returns the cipher algorithm padding scheme name (as a String) to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingPaddingSchemeName() streamingPaddingSchemeName} attribute is used when the block cipher is
+     * used for streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     *
+     * @return the padding scheme name (as a String) to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default
+     *         padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public String getPaddingSchemeName() {
+        return paddingSchemeName;
+    }
+
+    /**
+     * Sets the padding scheme name to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider default mode for
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingPaddingSchemeName() streamingPaddingSchemeName} attribute is used when the block cipher is
+     * used for streaming operations.
+     * <p/>
+     * The default value is {@code null} to retain the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> most standard padding schemes are represented by the {@link PaddingScheme PaddingScheme} enum.
+     * That enum should be used with the {@link #setPaddingScheme paddingScheme} attribute when possible to retain
+     * type-safety and reduce the possibility of errors.  Calling this method however is suitable if the
+     * {@code PaddingScheme} enum does not represent the desired scheme.
+     *
+     * @param paddingSchemeName the padding scheme name to be used when constructing
+     *                          {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA
+     *                          Provider default padding scheme for the specified {@link #getAlgorithmName() algorithm}
+     *                          should be used.
+     * @see #setPaddingScheme
+     */
+    public void setPaddingSchemeName(String paddingSchemeName) {
+        this.paddingSchemeName = paddingSchemeName;
+        //clear out the transformation string so the next invocation will rebuild it with the new padding scheme:
+        this.transformationString = null;
+    }
+
+    /**
+     * Sets the padding scheme to be used when constructing the
+     * {@link javax.crypto.Cipher Cipher} transformation string. A {@code null} value indicates that the JCA Provider
+     * default padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #setStreamingPaddingScheme streamingPaddingScheme} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * If the {@link PaddingScheme PaddingScheme} enum does represent your desired scheme, you can set the name explicitly
+     * via the {@link #setPaddingSchemeName paddingSchemeName} attribute directly.  However, because
+     * {@code PaddingScheme} represents all standard JDK scheme names already, ensure that your underlying JCA Provider
+     * supports the non-standard name first.
+     *
+     * @param paddingScheme the padding scheme to be used when constructing
+     *                      {@link javax.crypto.Cipher Cipher} transformation string, or {@code null} if the JCA Provider
+     *                      default padding scheme for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setPaddingScheme(PaddingScheme paddingScheme) {
+        setPaddingSchemeName(paddingScheme.getTransformationName());
+    }
+
+    /**
+     * Returns the block cipher's block size to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string or {@code -1} if the JCA Provider default block size
+     * the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingBlockSize() streamingBlockSize} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code -1} to retain the JCA Provider default.
+     *
+     * @return the block cipher block size to be used when constructing the
+     *         {@link javax.crypto.Cipher Cipher} transformation string, or {@code -1} if the JCA Provider default
+     *         block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * Sets the block cipher's block size to be used when constructing
+     * {@link javax.crypto.Cipher Cipher} transformation string.  {@code -1} indicates that the JCA Provider default
+     * block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     * <p/>
+     * This attribute is used <em>only</em> when constructing the transformation string for block (byte array)
+     * operations ({@link #encrypt(byte[], byte[])} and {@link #decrypt(byte[], byte[])}).  The
+     * {@link #getStreamingBlockSize() streamingBlockSize} attribute is used when the block cipher is used for
+     * streaming operations.
+     * <p/>
+     * The default value is {@code -1} to retain the JCA Provider default.
+     * <p/>
+     * <b>NOTE:</b> block cipher block sizes are very algorithm-specific.  If you change this value, ensure that it
+     * will work with the specified {@link #getAlgorithmName() algorithm}.
+     *
+     * @param blockSize the block cipher block size to be used when constructing the
+     *                  {@link javax.crypto.Cipher Cipher} transformation string, or {@code -1} if the JCA Provider
+     *                  default block size for the specified {@link #getAlgorithmName() algorithm} should be used.
+     */
+    public void setBlockSize(int blockSize) {
+        this.blockSize = Math.max(DEFAULT_BLOCK_SIZE, blockSize);
+        //clear out the transformation string so the next invocation will rebuild it with the new block size:
+        this.transformationString = null;
+    }
+
+    /**
+     * Same purpose as the {@link #getModeName modeName} attribute, but is used instead only for for streaming
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Note that unlike the {@link #getModeName modeName} attribute, the default value of this attribute is not
+     * {@code null} - it is {@link OperationMode#CFB CFB} for reasons described in the class-level JavaDoc in the
+     * {@code Streaming} section.
+     *
+     * @return the transformation string mode name to be used for streaming operations only.
+     */
+    public String getStreamingModeName() {
+        return streamingModeName;
+    }
+
+    private boolean isModeStreamingCompatible(String modeName) {
+        return modeName != null &&
+                !modeName.equalsIgnoreCase(OperationMode.ECB.name()) &&
+                !modeName.equalsIgnoreCase(OperationMode.NONE.name());
+    }
+
+    /**
+     * Sets the transformation string mode name to be used for streaming operations only.  The default value is
+     * {@link OperationMode#CFB CFB} for reasons described in the class-level JavaDoc in the {@code Streaming} section.
+     *
+     * @param streamingModeName transformation string mode name to be used for streaming operations only
+     */
+    public void setStreamingModeName(String streamingModeName) {
+        if (!isModeStreamingCompatible(streamingModeName)) {
+            String msg = "mode [" + streamingModeName + "] is not a valid operation mode for block cipher streaming.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.streamingModeName = streamingModeName;
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new mode:
+        this.streamingTransformationString = null;
+    }
+
+    /**
+     * Sets the transformation string mode to be used for streaming operations only.  The default value is
+     * {@link OperationMode#CFB CFB} for reasons described in the class-level JavaDoc in the {@code Streaming} section.
+     *
+     * @param mode the transformation string mode to be used for streaming operations only
+     */
+    public void setStreamingMode(OperationMode mode) {
+        setStreamingModeName(mode.name());
+    }
+
+    public String getStreamingPaddingSchemeName() {
+        return streamingPaddingSchemeName;
+    }
+
+    public void setStreamingPaddingSchemeName(String streamingPaddingSchemeName) {
+        this.streamingPaddingSchemeName = streamingPaddingSchemeName;
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new scheme:
+        this.streamingTransformationString = null;
+    }
+
+    public void setStreamingPaddingScheme(PaddingScheme scheme) {
+        setStreamingPaddingSchemeName(scheme.getTransformationName());
+    }
+
+    public int getStreamingBlockSize() {
+        return streamingBlockSize;
+    }
+
+    public void setStreamingBlockSize(int streamingBlockSize) {
+        this.streamingBlockSize = Math.max(DEFAULT_BLOCK_SIZE, streamingBlockSize);
+        //clear out the streaming transformation string so the next invocation will rebuild it with the new block size:
+        this.streamingTransformationString = null;
+    }
+
+    /**
+     * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} call.  If
+     * {@code streaming} is {@code true}, a block-cipher transformation string compatible with streaming operations will
+     * be constructed and cached for re-use later (see the class-level JavaDoc for more on using block ciphers
+     * for streaming).  If {@code streaming} is {@code false} a normal block-cipher transformation string will
+     * be constructed and cached for later re-use.
+     *
+     * @param streaming if the transformation string is going to be used for a Cipher performing stream-based encryption or not.
+     * @return the transformation string
+     */
+    protected String getTransformationString(boolean streaming) {
+        if (streaming) {
+            if (this.streamingTransformationString == null) {
+                this.streamingTransformationString = buildStreamingTransformationString();
+            }
+            return this.streamingTransformationString;
+        } else {
+            if (this.transformationString == null) {
+                this.transformationString = buildTransformationString();
+            }
+            return this.transformationString;
+        }
+    }
+
+    private String buildTransformationString() {
+        return buildTransformationString(getModeName(), getPaddingSchemeName(), getBlockSize());
+    }
+
+    private String buildStreamingTransformationString() {
+        return buildTransformationString(getStreamingModeName(), getStreamingPaddingSchemeName(), getStreamingBlockSize());
+    }
+
+    private String buildTransformationString(String modeName, String paddingSchemeName, int blockSize) {
+        StringBuilder sb = new StringBuilder(getAlgorithmName());
+        if (StringUtils.hasText(modeName)) {
+            sb.append(TRANSFORMATION_STRING_DELIMITER).append(modeName);
+        }
+        if (blockSize > 0) {
+            sb.append(blockSize);
+        }
+        if (StringUtils.hasText(paddingSchemeName)) {
+            sb.append(TRANSFORMATION_STRING_DELIMITER).append(paddingSchemeName);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Returns {@code true} if the specified cipher operation mode name supports initialization vectors,
+     * {@code false} otherwise.
+     *
+     * @param modeName the raw text name of the mode of operation
+     * @return {@code true} if the specified cipher operation mode name supports initialization vectors,
+     *         {@code false} otherwise.
+     */
+    private boolean isModeInitializationVectorCompatible(String modeName) {
+        return modeName != null &&
+                !modeName.equalsIgnoreCase(OperationMode.ECB.name()) &&
+                !modeName.equalsIgnoreCase(OperationMode.NONE.name());
+    }
+
+    /**
+     * Overrides the parent implementation to ensure initialization vectors are always generated if streaming is
+     * enabled (block ciphers <em>must</em> use initialization vectors if they are to be used as a stream cipher).  If
+     * not being used as a stream cipher, then the value is computed based on whether or not the currently configured
+     * {@link #getModeName modeName} is compatible with initialization vectors as well as the result of the configured
+     * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} value.
+     *
+     * @param streaming whether or not streaming is being performed
+     * @return {@code true} if streaming or a value computed based on if the currently configured mode is compatible
+     *         with initialization vectors.
+     */
+    @Override
+    protected boolean isGenerateInitializationVectors(boolean streaming) {
+        return streaming || super.isGenerateInitializationVectors() && isModeInitializationVectorCompatible(getModeName());
+    }
+
+    @Override
+    protected byte[] generateInitializationVector(boolean streaming) {
+        if (streaming) {
+            String streamingModeName = getStreamingModeName();
+            if (!isModeInitializationVectorCompatible(streamingModeName)) {
+                String msg = "streamingMode attribute value [" + streamingModeName + "] does not support " +
+                        "Initialization Vectors.  Ensure the streamingMode value represents an operation mode " +
+                        "that is compatible with initialization vectors.";
+                throw new IllegalStateException(msg);
+            }
+        } else {
+            String modeName = getModeName();
+            if (!isModeInitializationVectorCompatible(modeName)) {
+                String msg = "mode attribute value [" + modeName + "] does not support " +
+                        "Initialization Vectors.  Ensure the mode value represents an operation mode " +
+                        "that is compatible with initialization vectors.";
+                throw new IllegalStateException(msg);
+            }
+        }
+        return super.generateInitializationVector(streaming);
+    }
+}

Added: incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java
URL: http://svn.apache.org/viewvc/incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java?rev=935106&view=auto
==============================================================================
--- incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java (added)
+++ incubator/shiro/trunk/core/src/main/java/org/apache/shiro/crypto/JcaCipherService.java Sat Apr 17 02:00:40 2010
@@ -0,0 +1,605 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.crypto;
+
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.SimpleByteSource;
+import org.apache.shiro.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.CipherInputStream;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Abstract {@code CipherService} implementation utilizing Java's JCA APIs.
+ * <h2>Auto-generated Initialization Vectors</h2>
+ * Shiro does something by default for all of its {@code CipherService} implementations that the JCA
+ * {@link javax.crypto.Cipher Cipher} does not do:  by default,
+ * <a href="http://en.wikipedia.org/wiki/Initialization_vector">initialization vector</a>s are automatically randomly
+ * generated and prepended to encrypted data before returning from the {@code encrypt} methods.  That is, the returned
+ * byte array or {@code OutputStream} is actually a concatenation of an initialization vector byte array plus the actual
+ * encrypted data byte array.  The {@code decrypt} methods in turn know to read this prepended initialization vector
+ * before decrypting the real data that follows.
+ * <p/>
+ * This is highly desirable because initialization vectors guarantee that, for a key and any plaintext, the encrypted
+ * output will always be different <em>even if you call {@code encrypt} multiple times with the exact same arguments</em>.
+ * This is essential in cryptography to ensure that data patterns cannot be identified across multiple input sources
+ * that are the same or similar.
+ * <p/>
+ * You can turn off this behavior by setting the
+ * {@link #setGenerateInitializationVectors(boolean) generateInitializationVectors} property to {@code false}, but it
+ * is highly recommended that you do not do this unless you have a very good reason to do so, since you would be losing
+ * a critical security feature.
+ * <h3>Initialization Vector Size</h3>
+ * This implementation defaults the {@link #setInitializationVectorSize(int) initializationVectorSize} attribute to
+ * {@code 128} bits, a fairly common size.  Initialization vector sizes are very algorithm specific however, so subclass
+ * implementations will often override this value in their constructor if necessary.
+ * <p/>
+ * Also note that {@code initializationVectorSize} values are specified in the number of
+ * bits (not bytes!) to match common references in most cryptography documentation.  In practice though, initialization
+ * vectors are always specified as a byte array, so ensure that if you set this property, that the value is a multiple
+ * of {@code 8} to ensure that the IV can be correctly represented as a byte array (the
+ * {@link #setInitializationVectorSize(int) setInitializationVectorSize} mutator method enforces this).
+ *
+ * @author Les Hazlewood
+ * @since 1.0
+ */
+public abstract class JcaCipherService implements CipherService {
+
+    /**
+     * Internal private log instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(JcaCipherService.class);
+
+    /**
+     * Default key size (in bits) for generated keys.
+     */
+    private static final int DEFAULT_KEY_SIZE = 128;
+
+    /**
+     * Default size of the internal buffer (in bytes) used to transfer data between streams during stream operations
+     */
+    private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
+
+    private static final int BITS_PER_BYTE = 8;
+
+    /**
+     * Default SecureRandom algorithm name to use when acquiring the SecureRandom instance.
+     */
+    private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
+
+    /**
+     * The name of the cipher algorithm to use for all encryption, decryption, and key operations
+     */
+    private String algorithmName;
+
+    /**
+     * The size in bits (not bytes) of generated cipher keys
+     */
+    private int keySize;
+
+    /**
+     * The size of the internal buffer (in bytes) used to transfer data from one stream to another during stream operations
+     */
+    private int streamingBufferSize;
+
+    private boolean generateInitializationVectors;
+    private int initializationVectorSize;
+
+
+    private SecureRandom secureRandom;
+
+    /**
+     * Creates a new {@code JcaCipherService} instance which will use the specified cipher {@code algorithmName}
+     * for all encryption, decryption, and key operations.  Also, the following defaults are set:
+     * <ul>
+     * <li>{@link #setKeySize keySize} = 128 bits</li>
+     * <li>{@link #setInitializationVectorSize(int) initializationVectorSize} = 128 bits</li>
+     * <li>{@link #setStreamingBufferSize(int) streamingBufferSize} = 512 bytes</li>
+     * </ul>
+     *
+     * @param algorithmName the name of the cipher algorithm to use for all encryption, decryption, and key operations
+     */
+    protected JcaCipherService(String algorithmName) {
+        if (!StringUtils.hasText(algorithmName)) {
+            throw new IllegalArgumentException("algorithmName argument cannot be null or empty.");
+        }
+        this.algorithmName = algorithmName;
+        this.keySize = DEFAULT_KEY_SIZE;
+        this.initializationVectorSize = DEFAULT_KEY_SIZE; //default to same size as the key size (a common algorithm practice)
+        this.streamingBufferSize = DEFAULT_STREAMING_BUFFER_SIZE;
+        this.generateInitializationVectors = true;
+    }
+
+    /**
+     * Returns the cipher algorithm name that will be used for all encryption, decryption, and key operations (for
+     * example, 'AES', 'Blowfish', 'RSA', 'DSA', 'TripleDES', etc).
+     *
+     * @return the cipher algorithm name that will be used for all encryption, decryption, and key operations
+     */
+    public String getAlgorithmName() {
+        return algorithmName;
+    }
+
+    /**
+     * Returns the size in bits (not bytes) of generated cipher keys.
+     *
+     * @return the size in bits (not bytes) of generated cipher keys.
+     */
+    public int getKeySize() {
+        return keySize;
+    }
+
+    /**
+     * Sets the size in bits (not bytes) of generated cipher keys.
+     *
+     * @param keySize the size in bits (not bytes) of generated cipher keys.
+     */
+    public void setKeySize(int keySize) {
+        this.keySize = keySize;
+    }
+
+    public boolean isGenerateInitializationVectors() {
+        return generateInitializationVectors;
+    }
+
+    public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
+        this.generateInitializationVectors = generateInitializationVectors;
+    }
+
+    /**
+     * Returns the algorithm-specific size in bits of generated initialization vectors.
+     *
+     * @return the algorithm-specific size in bits of generated initialization vectors.
+     */
+    public int getInitializationVectorSize() {
+        return initializationVectorSize;
+    }
+
+    /**
+     * Sets the algorithm-specific initialization vector size in bits (not bytes!) to be used when generating
+     * initialization vectors.  The  value must be a multiple of {@code 8} to ensure that the IV can be represented
+     * as a byte array.
+     *
+     * @param initializationVectorSize the size in bits (not bytes) of generated initialization vectors.
+     * @throws IllegalArgumentException if the size is not a multiple of {@code 8}.
+     */
+    public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
+        if (initializationVectorSize % BITS_PER_BYTE != 0) {
+            String msg = "Initialization vector sizes are specified in bits, but must be a multiple of 8 so they " +
+                    "can be easily represented as a byte array.";
+            throw new IllegalArgumentException(msg);
+        }
+        this.initializationVectorSize = initializationVectorSize;
+    }
+
+    protected boolean isGenerateInitializationVectors(boolean streaming) {
+        return isGenerateInitializationVectors();
+    }
+
+    /**
+     * Returns the size in bytes of the internal buffer used to transfer data from one stream to another during stream
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Default size is {@code 512} bytes.
+     *
+     * @return the size of the internal buffer used to transfer data from one stream to another during stream
+     *         operations
+     */
+    public int getStreamingBufferSize() {
+        return streamingBufferSize;
+    }
+
+    /**
+     * Sets the size in bytes of the internal buffer used to transfer data from one stream to another during stream
+     * operations ({@link #encrypt(java.io.InputStream, java.io.OutputStream, byte[])} and
+     * {@link #decrypt(java.io.InputStream, java.io.OutputStream, byte[])}).
+     * <p/>
+     * Default size is {@code 512} bytes.
+     *
+     * @param streamingBufferSize the size of the internal buffer used to transfer data from one stream to another
+     *                            during stream operations
+     */
+    public void setStreamingBufferSize(int streamingBufferSize) {
+        this.streamingBufferSize = streamingBufferSize;
+    }
+
+    /**
+     * Returns a source of randomness for encryption operations.  If one is not configured, and the underlying
+     * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     *
+     * @return a source of randomness for encryption operations.  If one is not configured, and the underlying
+     *         algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     */
+    public SecureRandom getSecureRandom() {
+        return secureRandom;
+    }
+
+    /**
+     * Sets a source of randomness for encryption operations.  If one is not configured, and the underlying
+     * algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     *
+     * @param secureRandom a source of randomness for encryption operations.  If one is not configured, and the
+     *                     underlying algorithm needs one, the JDK {@code SHA1PRNG} instance will be used by default.
+     */
+    public void setSecureRandom(SecureRandom secureRandom) {
+        this.secureRandom = secureRandom;
+    }
+
+    protected static SecureRandom getDefaultSecureRandom() {
+        try {
+            return java.security.SecureRandom.getInstance(RANDOM_NUM_GENERATOR_ALGORITHM_NAME);
+        } catch (java.security.NoSuchAlgorithmException e) {
+            log.debug("The SecureRandom SHA1PRNG algorithm is not available on the current platform.  Using the " +
+                    "platform's default SecureRandom algorithm.", e);
+            return new java.security.SecureRandom();
+        }
+    }
+
+    protected SecureRandom ensureSecureRandom() {
+        SecureRandom random = getSecureRandom();
+        if (random == null) {
+            random = getDefaultSecureRandom();
+        }
+        return random;
+    }
+
+    /**
+     * Returns the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
+     * creating a new {@code Cipher} instance.  This default implementation always returns
+     * {@link #getAlgorithmName() getAlgorithmName()}.  Block cipher implementations will want to override this method
+     * to support appending cipher operation modes and padding schemes.
+     *
+     * @param streaming if the transformation string is going to be used for a Cipher for stream-based encryption or not.
+     * @return the transformation string to use with the {@link javax.crypto.Cipher#getInstance} invocation when
+     *         creating a new {@code Cipher} instance.
+     */
+    protected String getTransformationString(boolean streaming) {
+        return getAlgorithmName();
+    }
+
+    protected byte[] generateInitializationVector(boolean streaming) {
+        int size = getInitializationVectorSize();
+        if (size <= 0) {
+            String msg = "initializationVectorSize property must be greater than zero.  This number is " +
+                    "typically set in the " + CipherService.class.getSimpleName() + " subclass constructor.  " +
+                    "Also check your configuration to ensure that if you are setting a value, it is positive.";
+            throw new IllegalStateException(msg);
+        }
+        if (size % BITS_PER_BYTE != 0) {
+            String msg = "initializationVectorSize property must be a multiple of 8 to represent as a byte array.";
+            throw new IllegalStateException(msg);
+        }
+        int sizeInBytes = size / BITS_PER_BYTE;
+        byte[] ivBytes = new byte[sizeInBytes];
+        SecureRandom random = ensureSecureRandom();
+        random.nextBytes(ivBytes);
+        return ivBytes;
+    }
+
+    public ByteSource encrypt(byte[] plaintext, byte[] key) {
+        byte[] ivBytes = null;
+        boolean generate = isGenerateInitializationVectors(false);
+        if (generate) {
+            ivBytes = generateInitializationVector(false);
+            if (ivBytes == null || ivBytes.length == 0) {
+                throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
+                        "cannot be null or empty.");
+            }
+        }
+        return encrypt(plaintext, key, ivBytes, generate);
+    }
+
+    private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
+
+        final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;
+
+        byte[] output;
+
+        if (prependIv && iv != null && iv.length > 0) {
+
+            byte[] encrypted = crypt(plaintext, key, iv, MODE);
+
+            output = new byte[iv.length + encrypted.length];
+
+            //now copy the iv bytes + encrypted bytes into one output array:
+
+            // iv bytes:
+            System.arraycopy(iv, 0, output, 0, iv.length);
+
+            // + encrypted bytes:
+            System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
+        } else {
+            output = crypt(plaintext, key, iv, MODE);
+        }
+
+        if (log.isTraceEnabled()) {
+            log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
+                    "byte array is size " + (output != null ? output.length : 0));
+        }
+
+        return new SimpleByteSource(output);
+
+    }
+
+    public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
+
+        byte[] encrypted = ciphertext;
+
+        //No IV, check if we need to read the IV from the stream:
+        byte[] iv = null;
+
+        if (isGenerateInitializationVectors(false)) {
+            try {
+                //We are generating IVs, so the ciphertext argument array is not actually 100% cipher text.  Instead, it
+                //is:
+                // - the first N bytes is the initialization vector, where N equals the value of the
+                // 'initializationVectorSize' attribute.
+                // - the remaining bytes in the method argument (arg.length - N) is the real cipher text.
+
+                //So we need to chunk the method argument into its constituent parts to find the IV and then use
+                //the IV to decrypt the real ciphertext:
+
+                int ivSize = getInitializationVectorSize();
+                int ivByteSize = ivSize / BITS_PER_BYTE;
+
+                //now we know how large the iv is, so extract the iv bytes:
+                iv = new byte[ivByteSize];
+                System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);
+
+                //remaining data is the actual encrypted ciphertext.  Isolate it:
+                int encryptedSize = ciphertext.length - ivByteSize;
+                encrypted = new byte[encryptedSize];
+                System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize);
+            } catch (Exception e) {
+                String msg = "Unable to correctly extract the Initialization Vector or ciphertext.";
+                throw new CryptoException(msg, e);
+            }
+        }
+
+        return decrypt(encrypted, key, iv);
+    }
+
+    private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) throws CryptoException {
+        if (log.isTraceEnabled()) {
+            log.trace("Attempting to decrypt incoming byte array of length " +
+                    (ciphertext != null ? ciphertext.length : 0));
+        }
+        byte[] decrypted = crypt(ciphertext, key, iv, javax.crypto.Cipher.DECRYPT_MODE);
+        return decrypted == null ? null : new SimpleByteSource(decrypted);
+    }
+
+    /**
+     * Returns a new {@link javax.crypto.Cipher Cipher} instance to use for encryption/decryption operations.  The
+     * Cipher's {@code transformationString} for the {@code Cipher}.{@link javax.crypto.Cipher#getInstance getInstance}
+     * call is obtaind via the {@link #getTransformationString(boolean) getTransformationString} method.
+     *
+     * @param streaming {@code true} if the cipher instance will be used as a stream cipher, {@code false} if it will be
+     *                  used as a block cipher.
+     * @return a new JDK {@code Cipher} instance.
+     * @throws CryptoException if a new Cipher instance cannot be constructed based on the
+     *                         {@link #getTransformationString(boolean) getTransformationString} value.
+     */
+    private javax.crypto.Cipher newCipherInstance(boolean streaming) throws CryptoException {
+        String transformationString = getTransformationString(streaming);
+        try {
+            return javax.crypto.Cipher.getInstance(transformationString);
+        } catch (Exception e) {
+            String msg = "Unable to acquire a Java JCA Cipher instance using " +
+                    javax.crypto.Cipher.class.getName() + ".getInstance( \"" + transformationString + "\" ). " +
+                    getAlgorithmName() + " under this configuration is required for the " +
+                    getClass().getName() + " instance to function.";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+    /**
+     * Functions as follows:
+     * <ol>
+     * <li>Creates a {@link #newCipherInstance(boolean) new JDK cipher instance}</li>
+     * <li>Converts the specified key bytes into an {@link #getAlgorithmName() algorithm}-compatible JDK
+     * {@link Key key} instance</li>
+     * <li>{@link #init(javax.crypto.Cipher, int, java.security.Key, AlgorithmParameterSpec, SecureRandom) Initializes}
+     * the JDK cipher instance with the JDK key</li>
+     * <li>Calls the {@link #crypt(javax.crypto.Cipher, byte[]) crypt(cipher,bytes)} method to either encrypt or
+     * decrypt the data based on the specified Cipher behavior mode
+     * ({@link javax.crypto.Cipher#ENCRYPT_MODE Cipher.ENCRYPT_MODE} or
+     * {@link javax.crypto.Cipher#DECRYPT_MODE Cipher.DECRYPT_MODE})</li>
+     * </ol>
+     *
+     * @param bytes the bytes to crypt
+     * @param key   the key to use to perform the encryption or decryption.
+     * @param iv    the initialization vector to use for the crypt operation (optional, may be {@code null}).
+     * @param mode  the JDK Cipher behavior mode (Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE).
+     * @return the resulting crypted byte array
+     * @throws IllegalArgumentException if {@code bytes} are null or empty.
+     * @throws CryptoException          if Cipher initialization or the crypt operation fails
+     */
+    private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, CryptoException {
+        if (key == null || key.length == 0) {
+            throw new IllegalArgumentException("key argument cannot be null or empty.");
+        }
+        javax.crypto.Cipher cipher = initNewCipher(mode, key, iv, false);
+        return crypt(cipher, bytes);
+    }
+
+    /**
+     * Calls the {@link javax.crypto.Cipher#doFinal(byte[]) doFinal(bytes)} method, propagating any exception that
+     * might arise in an {@link CryptoException}
+     *
+     * @param cipher the JDK Cipher to finalize (perform the actual cryption)
+     * @param bytes  the bytes to crypt
+     * @return the resulting crypted byte array.
+     * @throws CryptoException if there is an illegal block size or bad padding
+     */
+    private byte[] crypt(javax.crypto.Cipher cipher, byte[] bytes) throws CryptoException {
+        try {
+            return cipher.doFinal(bytes);
+        } catch (Exception e) {
+            String msg = "Unable to execute 'doFinal' with cipher instance [" + cipher + "].";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+    /**
+     * Initializes the JDK Cipher with the specified mode and key.  This is primarily a utility method to catch any
+     * potential {@link java.security.InvalidKeyException InvalidKeyException} that might arise.
+     *
+     * @param cipher the JDK Cipher to {@link javax.crypto.Cipher#init(int, java.security.Key) init}.
+     * @param mode   the Cipher mode
+     * @param key    the Cipher's Key
+     * @param spec   the JDK AlgorithmParameterSpec for cipher initialization (optional, may be null).
+     * @param random the SecureRandom to use for cipher initialization (optional, may be null).
+     * @throws CryptoException if the key is invalid
+     */
+    private void init(javax.crypto.Cipher cipher, int mode, java.security.Key key,
+                      AlgorithmParameterSpec spec, SecureRandom random) throws CryptoException {
+        try {
+            if (random != null) {
+                if (spec != null) {
+                    cipher.init(mode, key, spec, random);
+                } else {
+                    cipher.init(mode, key, random);
+                }
+            } else {
+                if (spec != null) {
+                    cipher.init(mode, key, spec);
+                } else {
+                    cipher.init(mode, key);
+                }
+            }
+        } catch (Exception e) {
+            String msg = "Unable to init cipher instance.";
+            throw new CryptoException(msg, e);
+        }
+    }
+
+
+    public void encrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
+        byte[] iv = null;
+        boolean generate = isGenerateInitializationVectors(true);
+        if (generate) {
+            iv = generateInitializationVector(true);
+            if (iv == null || iv.length == 0) {
+                throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
+                        "cannot be null or empty.");
+            }
+        }
+        encrypt(in, out, key, iv, generate);
+    }
+
+    private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {
+        if (prependIv && iv != null && iv.length > 0) {
+            try {
+                //first write the IV:
+                out.write(iv);
+            } catch (IOException e) {
+                throw new CryptoException(e);
+            }
+        }
+
+        crypt(in, out, key, iv, javax.crypto.Cipher.ENCRYPT_MODE);
+    }
+
+    public void decrypt(InputStream in, OutputStream out, byte[] key) throws CryptoException {
+        decrypt(in, out, key, isGenerateInitializationVectors(true));
+    }
+
+    private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws CryptoException {
+
+        byte[] iv = null;
+        //No Initialization Vector provided as a method argument - check if we need to read the IV from the stream:
+        if (ivPrepended) {
+            //we are generating IVs, so we need to read the previously-generated IV from the stream before
+            //we decrypt the rest of the stream (we need the IV to decrypt):
+            int ivSize = getInitializationVectorSize();
+            int ivByteSize = ivSize / BITS_PER_BYTE;
+            iv = new byte[ivByteSize];
+            int read;
+
+            try {
+                read = in.read(iv);
+            } catch (IOException e) {
+                String msg = "Unable to correctly read the Initialization Vector from the input stream.";
+                throw new CryptoException(msg, e);
+            }
+
+            if (read != ivByteSize) {
+                throw new CryptoException("Unable to read initialization vector bytes from the InputStream.  " +
+                        "This is required when initialization vectors are autogenerated during an encryption " +
+                        "operation.");
+            }
+        }
+
+        decrypt(in, out, key, iv);
+    }
+
+    private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws CryptoException {
+        crypt(in, out, decryptionKey, iv, javax.crypto.Cipher.DECRYPT_MODE);
+    }
+
+    private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws CryptoException {
+        if (in == null) {
+            throw new NullPointerException("InputStream argument cannot be null.");
+        }
+        if (out == null) {
+            throw new NullPointerException("OutputStream argument cannot be null.");
+        }
+
+        javax.crypto.Cipher cipher = initNewCipher(cryptMode, keyBytes, iv, true);
+
+        CipherInputStream cis = new CipherInputStream(in, cipher);
+
+        int bufSize = getStreamingBufferSize();
+        byte[] buffer = new byte[bufSize];
+
+        int bytesRead;
+        try {
+            while ((bytesRead = cis.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            throw new CryptoException(e);
+        }
+    }
+
+    private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[] iv, boolean streaming)
+            throws CryptoException {
+
+        javax.crypto.Cipher cipher = newCipherInstance(streaming);
+        java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
+        IvParameterSpec ivSpec = null;
+        if (iv != null && iv.length > 0) {
+            ivSpec = new IvParameterSpec(iv);
+        }
+
+        init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
+
+        return cipher;
+    }
+}