You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/01/14 19:51:52 UTC

[commons-crypto] branch master updated (eefdbd2 -> 9ee9c7e)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-crypto.git


    from eefdbd2  Organize and better descriptions
     new 363abe9  Sort members
     new 81f16b6  Pick up maven-checkstyle-plugin version from parent POM
     new 9ee9c7e  Pick up spotbugs-maven-plugin version from parent POM

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   4 -
 .../java/org/apache/commons/crypto/Crypto.java     |   4 +-
 .../apache/commons/crypto/OpenSslInfoNative.java   |  32 +-
 .../cipher/AbstractOpenSslFeedbackCipher.java      |  28 +-
 .../apache/commons/crypto/cipher/CryptoCipher.java | 114 ++--
 .../commons/crypto/cipher/CryptoCipherFactory.java | 102 +--
 .../apache/commons/crypto/cipher/JceCipher.java    | 154 ++---
 .../org/apache/commons/crypto/cipher/OpenSsl.java  | 184 +++---
 .../commons/crypto/cipher/OpenSslCipher.java       | 152 ++---
 .../commons/crypto/cipher/OpenSslCommonMode.java   |  60 +-
 .../crypto/cipher/OpenSslGaloisCounterMode.java    | 194 +++---
 .../commons/crypto/jna/OpenSsl10XNativeJna.java    | 308 ++++-----
 .../commons/crypto/jna/OpenSsl11XNativeJna.java    | 260 ++++----
 .../commons/crypto/jna/OpenSsl20XNativeJna.java    | 296 ++++-----
 .../crypto/jna/OpenSslInterfaceNativeJna.java      |  24 +-
 .../commons/crypto/jna/OpenSslJnaCipher.java       | 278 ++++----
 .../commons/crypto/jna/OpenSslJnaCryptoRandom.java |  50 +-
 .../commons/crypto/jna/OpenSslNativeJna.java       |  34 +-
 .../commons/crypto/random/CryptoRandomFactory.java |  88 +--
 .../commons/crypto/random/JavaCryptoRandom.java    |  22 +-
 .../crypto/random/OpenSslCryptoRandomNative.java   |  12 +-
 .../commons/crypto/random/OsCryptoRandom.java      |  54 +-
 .../commons/crypto/stream/CryptoInputStream.java   | 736 ++++++++++-----------
 .../commons/crypto/stream/CryptoOutputStream.java  | 370 +++++------
 .../crypto/stream/CtrCryptoInputStream.java        | 632 +++++++++---------
 .../crypto/stream/CtrCryptoOutputStream.java       | 172 ++---
 .../commons/crypto/stream/input/ChannelInput.java  | 116 ++--
 .../apache/commons/crypto/stream/input/Input.java  |  92 +--
 .../commons/crypto/stream/input/StreamInput.java   |  74 +--
 .../crypto/stream/output/ChannelOutput.java        |  24 +-
 .../commons/crypto/stream/output/Output.java       |  42 +-
 .../commons/crypto/stream/output/StreamOutput.java |  56 +-
 .../org/apache/commons/crypto/utils/IoUtils.java   |  70 +-
 .../commons/crypto/utils/ReflectionUtils.java      |  90 +--
 .../org/apache/commons/crypto/utils/Utils.java     | 130 ++--
 .../java/org/apache/commons/crypto/CryptoTest.java |  16 +-
 .../commons/crypto/NativeCodeLoaderTest.java       |  22 +-
 .../commons/crypto/cipher/AbstractCipherTest.java  | 346 +++++-----
 .../commons/crypto/cipher/DefaultCryptoCipher.java |  22 +-
 .../commons/crypto/cipher/GcmCipherTest.java       | 570 ++++++++--------
 .../commons/crypto/cipher/JceCipherTest.java       |  18 +-
 .../commons/crypto/cipher/OpenSslCipherTest.java   | 148 ++---
 .../crypto/examples/CipherByteArrayExample.java    |  20 +-
 .../crypto/examples/CipherByteBufferExample.java   |  46 +-
 .../commons/crypto/examples/StreamExample.java     |  20 +-
 .../crypto/jna/AbstractCipherJnaStreamTest.java    |  20 +-
 .../crypto/jna/OpenSslJnaCryptoRandomTest.java     |  10 +-
 .../jna/PositionedCryptoInputStreamJnaTest.java    |  10 +-
 .../commons/crypto/random/AbstractRandomTest.java  |  28 +-
 .../commons/crypto/random/FailingRandom.java       |   4 +-
 .../commons/crypto/stream/CtrCryptoStreamTest.java | 128 ++--
 .../stream/PositionedCryptoInputStreamTest.java    | 386 +++++------
 .../org/apache/commons/crypto/utils/UtilsTest.java |  30 +-
 53 files changed, 3449 insertions(+), 3453 deletions(-)


[commons-crypto] 02/03: Pick up maven-checkstyle-plugin version from parent POM

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-crypto.git

commit 81f16b61011e5a08c2c06710441fccea675cfccf
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jan 14 14:47:15 2023 -0500

    Pick up maven-checkstyle-plugin version from parent POM
---
 pom.xml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index fc20a41..3e9bc2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,7 +139,6 @@ The following provides more details on the included cryptographic software:
     <!-- This is used by reporting plugins -->
     <project.reporting.outputEncoding>${commons.encoding}</project.reporting.outputEncoding>
 
-    <checkstyle.version>3.2.0</checkstyle.version>
     <checkstyle.header.file>${basedir}/LICENSE-header.txt</checkstyle.header.file>
     <checkstyle.resourceExcludes>LICENSE.txt, NOTICE.txt, **/maven-archiver/pom.properties</checkstyle.resourceExcludes>
 
@@ -513,7 +512,6 @@ The following provides more details on the included cryptographic software:
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
-        <version>${checkstyle.version}</version>
         <configuration>
           <configLocation>${basedir}/checkstyle.xml</configLocation>
           <enableRulesSummary>false</enableRulesSummary>
@@ -635,7 +633,6 @@ The following provides more details on the included cryptographic software:
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
-        <version>${checkstyle.version}</version>
         <configuration>
           <configLocation>${basedir}/checkstyle.xml</configLocation>
           <enableRulesSummary>false</enableRulesSummary>


[commons-crypto] 01/03: Sort members

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-crypto.git

commit 363abe9c6e8c9a06cc95e235813dda881ba445ed
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jan 14 14:36:34 2023 -0500

    Sort members
---
 .../java/org/apache/commons/crypto/Crypto.java     |   4 +-
 .../apache/commons/crypto/OpenSslInfoNative.java   |  32 +-
 .../cipher/AbstractOpenSslFeedbackCipher.java      |  28 +-
 .../apache/commons/crypto/cipher/CryptoCipher.java | 114 ++--
 .../commons/crypto/cipher/CryptoCipherFactory.java | 102 +--
 .../apache/commons/crypto/cipher/JceCipher.java    | 154 ++---
 .../org/apache/commons/crypto/cipher/OpenSsl.java  | 184 +++---
 .../commons/crypto/cipher/OpenSslCipher.java       | 152 ++---
 .../commons/crypto/cipher/OpenSslCommonMode.java   |  60 +-
 .../crypto/cipher/OpenSslGaloisCounterMode.java    | 194 +++---
 .../commons/crypto/jna/OpenSsl10XNativeJna.java    | 308 ++++-----
 .../commons/crypto/jna/OpenSsl11XNativeJna.java    | 260 ++++----
 .../commons/crypto/jna/OpenSsl20XNativeJna.java    | 296 ++++-----
 .../crypto/jna/OpenSslInterfaceNativeJna.java      |  24 +-
 .../commons/crypto/jna/OpenSslJnaCipher.java       | 278 ++++----
 .../commons/crypto/jna/OpenSslJnaCryptoRandom.java |  50 +-
 .../commons/crypto/jna/OpenSslNativeJna.java       |  34 +-
 .../commons/crypto/random/CryptoRandomFactory.java |  88 +--
 .../commons/crypto/random/JavaCryptoRandom.java    |  22 +-
 .../crypto/random/OpenSslCryptoRandomNative.java   |  12 +-
 .../commons/crypto/random/OsCryptoRandom.java      |  54 +-
 .../commons/crypto/stream/CryptoInputStream.java   | 736 ++++++++++-----------
 .../commons/crypto/stream/CryptoOutputStream.java  | 370 +++++------
 .../crypto/stream/CtrCryptoInputStream.java        | 632 +++++++++---------
 .../crypto/stream/CtrCryptoOutputStream.java       | 172 ++---
 .../commons/crypto/stream/input/ChannelInput.java  | 116 ++--
 .../apache/commons/crypto/stream/input/Input.java  |  92 +--
 .../commons/crypto/stream/input/StreamInput.java   |  74 +--
 .../crypto/stream/output/ChannelOutput.java        |  24 +-
 .../commons/crypto/stream/output/Output.java       |  42 +-
 .../commons/crypto/stream/output/StreamOutput.java |  56 +-
 .../org/apache/commons/crypto/utils/IoUtils.java   |  70 +-
 .../commons/crypto/utils/ReflectionUtils.java      |  90 +--
 .../org/apache/commons/crypto/utils/Utils.java     | 130 ++--
 .../java/org/apache/commons/crypto/CryptoTest.java |  16 +-
 .../commons/crypto/NativeCodeLoaderTest.java       |  22 +-
 .../commons/crypto/cipher/AbstractCipherTest.java  | 346 +++++-----
 .../commons/crypto/cipher/DefaultCryptoCipher.java |  22 +-
 .../commons/crypto/cipher/GcmCipherTest.java       | 570 ++++++++--------
 .../commons/crypto/cipher/JceCipherTest.java       |  18 +-
 .../commons/crypto/cipher/OpenSslCipherTest.java   | 148 ++---
 .../crypto/examples/CipherByteArrayExample.java    |  20 +-
 .../crypto/examples/CipherByteBufferExample.java   |  46 +-
 .../commons/crypto/examples/StreamExample.java     |  20 +-
 .../crypto/jna/AbstractCipherJnaStreamTest.java    |  20 +-
 .../crypto/jna/OpenSslJnaCryptoRandomTest.java     |  10 +-
 .../jna/PositionedCryptoInputStreamJnaTest.java    |  10 +-
 .../commons/crypto/random/AbstractRandomTest.java  |  28 +-
 .../commons/crypto/random/FailingRandom.java       |   4 +-
 .../commons/crypto/stream/CtrCryptoStreamTest.java | 128 ++--
 .../stream/PositionedCryptoInputStreamTest.java    | 386 +++++------
 .../org/apache/commons/crypto/utils/UtilsTest.java |  30 +-
 52 files changed, 3449 insertions(+), 3449 deletions(-)

diff --git a/src/main/java/org/apache/commons/crypto/Crypto.java b/src/main/java/org/apache/commons/crypto/Crypto.java
index 5780129..ba2bd80 100644
--- a/src/main/java/org/apache/commons/crypto/Crypto.java
+++ b/src/main/java/org/apache/commons/crypto/Crypto.java
@@ -79,6 +79,8 @@ public final class Crypto {
      */
     public static final String LIB_TEMPDIR_KEY = Crypto.CONF_PREFIX + "lib.tempdir";
 
+    private static boolean quiet = false;
+
     /**
      * Gets the component version of Apache Commons Crypto.
      * <p>
@@ -120,8 +122,6 @@ public final class Crypto {
         return NativeCodeLoader.getLoadingError();
     }
 
-    private static boolean quiet = false;
-
     /**
      * Logs info-level messages.
      *
diff --git a/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java b/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java
index d5f9e78..51bae35 100644
--- a/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java
+++ b/src/main/java/org/apache/commons/crypto/OpenSslInfoNative.java
@@ -29,15 +29,18 @@ import org.apache.commons.crypto.random.CryptoRandom;
 final class OpenSslInfoNative {
 
     /**
-     * Makes the constructor private.
+     * Return the name used to load the dynamic linked library.
+     *
+     * @return the name used to load the library (e.g. crypto.dll)
      */
-    private OpenSslInfoNative() {
-    }
+    public static native String DLLName();
 
     /**
-     * @return version of native
+     * Return the path to the loaded dynamic linked library.
+     * [Currently not implemented on Windows]
+     * @return the path to the library that was loaded; may be null.
      */
-    public static native String NativeVersion();
+    public static native String DLLPath();
 
     /**
      * @return name of native
@@ -50,6 +53,11 @@ final class OpenSslInfoNative {
     public static native String NativeTimeStamp();
 
 
+    /**
+     * @return version of native
+     */
+    public static native String NativeVersion();
+
     /**
      * @return the value of OPENSSL_VERSION_NUMBER.
      */
@@ -64,16 +72,8 @@ final class OpenSslInfoNative {
     public static native String OpenSSLVersion(int type);
 
     /**
-     * Return the name used to load the dynamic linked library.
-     *
-     * @return the name used to load the library (e.g. crypto.dll)
-     */
-    public static native String DLLName();
-
-    /**
-     * Return the path to the loaded dynamic linked library.
-     * [Currently not implemented on Windows]
-     * @return the path to the library that was loaded; may be null.
+     * Makes the constructor private.
      */
-    public static native String DLLPath();
+    private OpenSslInfoNative() {
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/AbstractOpenSslFeedbackCipher.java b/src/main/java/org/apache/commons/crypto/cipher/AbstractOpenSslFeedbackCipher.java
index 8abbf56..969d06b 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/AbstractOpenSslFeedbackCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/AbstractOpenSslFeedbackCipher.java
@@ -44,27 +44,27 @@ abstract class AbstractOpenSslFeedbackCipher {
         this.padding = padding;
     }
 
-    abstract void init(int mode, byte[] key, AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
-
-    abstract int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException;
+    public void checkState() {
+        Utils.checkState(context != 0, "Cipher context is invalid.");
+    }
 
-    abstract int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException;
+    public void clean() {
+        if (context != 0) {
+            OpenSslNative.clean(context);
+            context = 0;
+        }
+    }
 
     abstract int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException;
 
     abstract int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException;
 
-    abstract void updateAAD(byte[] aad);
+    abstract void init(int mode, byte[] key, AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException;
 
-    public void clean() {
-        if (context != 0) {
-            OpenSslNative.clean(context);
-            context = 0;
-        }
-    }
+    abstract int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException;
 
-    public void checkState() {
-        Utils.checkState(context != 0, "Cipher context is invalid.");
-    }
+    abstract int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException;
+
+    abstract void updateAAD(byte[] aad);
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
index e2688e6..4c7fd67 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
@@ -38,12 +38,51 @@ import javax.crypto.ShortBufferException;
 public interface CryptoCipher extends Closeable {
 
     /**
-     * Returns the block size (in bytes).
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation.
      *
-     * @return the block size (in bytes), or 0 if the underlying algorithm is
-     * not a block cipher
+     * @param input the input byte array
+     * @param inputOffset the offset in input where the input starts
+     * @param inputLen the input length
+     * @param output the byte array for the result
+     * @param outputOffset the offset in output where the result is stored
+     * @return the number of bytes stored in output
+     * @throws ShortBufferException if the given output byte array is too small
+     *         to hold the result
+     * @throws BadPaddingException if this cipher is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this cipher is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
      */
-    int getBlockSize();
+    int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+            int outputOffset) throws ShortBufferException,
+            IllegalBlockSizeException, BadPaddingException;
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation.
+     *
+     * @param inBuffer the input ByteBuffer
+     * @param outBuffer the output ByteBuffer
+     * @return int number of bytes stored in {@code output}
+     * @throws BadPaddingException if this cipher is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this cipher is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
+     * @throws ShortBufferException if the given output buffer is too small to
+     *         hold the result
+     */
+    int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException;
 
     /**
      * Returns the algorithm name of this {@code CryptoCipher} object.
@@ -58,6 +97,14 @@ public interface CryptoCipher extends Closeable {
      */
     String getAlgorithm();
 
+    /**
+     * Returns the block size (in bytes).
+     *
+     * @return the block size (in bytes), or 0 if the underlying algorithm is
+     * not a block cipher
+     */
+    int getBlockSize();
+
     /**
      * Initializes the cipher with mode, key and iv.
      *
@@ -79,19 +126,6 @@ public interface CryptoCipher extends Closeable {
     void init(int mode, Key key, AlgorithmParameterSpec params)
             throws InvalidKeyException, InvalidAlgorithmParameterException;
 
-    /**
-     * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
-     *
-     * @param inBuffer the input ByteBuffer
-     * @param outBuffer the output ByteBuffer
-     * @return int number of bytes stored in {@code output}
-     * @throws ShortBufferException if there is insufficient space in the output
-     *         buffer
-     */
-    int update(ByteBuffer inBuffer, ByteBuffer outBuffer)
-            throws ShortBufferException;
-
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
      * encrypted or decrypted, depending on how this cipher was initialized.
@@ -109,51 +143,17 @@ public interface CryptoCipher extends Closeable {
             int outputOffset) throws ShortBufferException;
 
     /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
+     * Continues a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
      *
      * @param inBuffer the input ByteBuffer
      * @param outBuffer the output ByteBuffer
      * @return int number of bytes stored in {@code output}
-     * @throws BadPaddingException if this cipher is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this cipher is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
-     * @throws ShortBufferException if the given output buffer is too small to
-     *         hold the result
-     */
-    int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
-            throws ShortBufferException, IllegalBlockSizeException,
-            BadPaddingException;
-
-    /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
-     *
-     * @param input the input byte array
-     * @param inputOffset the offset in input where the input starts
-     * @param inputLen the input length
-     * @param output the byte array for the result
-     * @param outputOffset the offset in output where the result is stored
-     * @return the number of bytes stored in output
-     * @throws ShortBufferException if the given output byte array is too small
-     *         to hold the result
-     * @throws BadPaddingException if this cipher is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this cipher is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
+     * @throws ShortBufferException if there is insufficient space in the output
+     *         buffer
      */
-    int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
-            int outputOffset) throws ShortBufferException,
-            IllegalBlockSizeException, BadPaddingException;
+    int update(ByteBuffer inBuffer, ByteBuffer outBuffer)
+            throws ShortBufferException;
 
     /**
      * Continues a multi-part update of the Additional Authentication
diff --git a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java
index 96578c7..5da9680 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipherFactory.java
@@ -30,28 +30,6 @@ import org.apache.commons.crypto.utils.Utils;
  */
 public class CryptoCipherFactory {
 
-    /**
-     * The configuration key of the provider class for JCE cipher.
-     */
-    public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX + "cipher.jce.provider";
-
-    /**
-     * The configuration key of the CryptoCipher implementation class.
-     * <p>
-     * The value of CLASSES_KEY needs to be the full name of a
-     * class that implements the
-     * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface
-     * The internal classes are listed in the enum
-     * {@link CipherProvider CipherProvider}
-     * which can be used to obtain the full class name.
-     * </p>
-     * <p>
-     * The value can also be a comma-separated list of class names in
-     * order of descending priority.
-     * </p>
-     */
-    public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "cipher.classes";
-
     /**
      * Defines the internal CryptoCipher implementations.
      * <p>
@@ -116,6 +94,28 @@ public class CryptoCipherFactory {
         }
     }
 
+    /**
+     * The configuration key of the provider class for JCE cipher.
+     */
+    public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX + "cipher.jce.provider";
+
+    /**
+     * The configuration key of the CryptoCipher implementation class.
+     * <p>
+     * The value of CLASSES_KEY needs to be the full name of a
+     * class that implements the
+     * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface
+     * The internal classes are listed in the enum
+     * {@link CipherProvider CipherProvider}
+     * which can be used to obtain the full class name.
+     * </p>
+     * <p>
+     * The value can also be a comma-separated list of class names in
+     * order of descending priority.
+     * </p>
+     */
+    public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "cipher.classes";
+
     /**
      * For AES, the algorithm block is fixed size of 128 bits.
      *
@@ -133,9 +133,34 @@ public class CryptoCipherFactory {
             .concat(CipherProvider.JCE.getClassName());
 
     /**
-     * The private Constructor of {@link CryptoCipherFactory}.
+     * Gets the cipher class.
+     *
+     * @param props The {@code Properties} class represents a set of
+     *        properties.
+     * @return the cipher class based on the props.
      */
-    private CryptoCipherFactory() {
+    private static String getCipherClassString(final Properties props) {
+        String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT);
+        if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
+            cipherClassString = CLASSES_DEFAULT;
+        }
+        return cipherClassString;
+    }
+
+    /**
+     * Gets a cipher for algorithm/mode/padding in config value
+     * commons.crypto.cipher.transformation
+     *
+     * @param transformation the name of the transformation, e.g.,
+     * <i>AES/CBC/PKCS5Padding</i>.
+     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
+     * for information about standard transformation names.
+     * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher)
+     * @throws GeneralSecurityException if JCE cipher initialize failed
+     */
+    public static CryptoCipher getCryptoCipher(final String transformation)
+            throws GeneralSecurityException {
+        return getCryptoCipher(transformation, new Properties());
     }
 
     /**
@@ -175,34 +200,9 @@ public class CryptoCipherFactory {
     }
 
     /**
-     * Gets a cipher for algorithm/mode/padding in config value
-     * commons.crypto.cipher.transformation
-     *
-     * @param transformation the name of the transformation, e.g.,
-     * <i>AES/CBC/PKCS5Padding</i>.
-     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
-     * for information about standard transformation names.
-     * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher)
-     * @throws GeneralSecurityException if JCE cipher initialize failed
-     */
-    public static CryptoCipher getCryptoCipher(final String transformation)
-            throws GeneralSecurityException {
-        return getCryptoCipher(transformation, new Properties());
-    }
-
-    /**
-     * Gets the cipher class.
-     *
-     * @param props The {@code Properties} class represents a set of
-     *        properties.
-     * @return the cipher class based on the props.
+     * The private Constructor of {@link CryptoCipherFactory}.
      */
-    private static String getCipherClassString(final Properties props) {
-        String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT);
-        if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
-            cipherClassString = CLASSES_DEFAULT;
-        }
-        return cipherClassString;
+    private CryptoCipherFactory() {
     }
 
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
index 9caf3cc..5f895e2 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
@@ -55,14 +55,66 @@ class JceCipher implements CryptoCipher {
     }
 
     /**
-     * Returns the block size (in bytes).
+     * Closes Jce cipher.
+     */
+    @Override
+    public void close() {
+        // Do nothing
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation.
      *
-     * @return the block size (in bytes), or 0 if the underlying algorithm is
-     * not a block cipher
+     * @param input the input byte array
+     * @param inputOffset the offset in input where the input starts
+     * @param inputLen the input length
+     * @param output the byte array for the result
+     * @param outputOffset the offset in output where the result is stored
+     * @return the number of bytes stored in output
+     * @throws ShortBufferException if the given output byte array is too small
+     *         to hold the result
+     * @throws BadPaddingException if this cipher is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this cipher is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
      */
     @Override
-    public final int getBlockSize() {
-        return cipher.getBlockSize();
+    public int doFinal(final byte[] input, final int inputOffset, final int inputLen,
+            final byte[] output, final int outputOffset) throws ShortBufferException,
+            IllegalBlockSizeException, BadPaddingException {
+        return cipher.doFinal(input, inputOffset, inputLen, output,
+                outputOffset);
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on
+     * how this cipher was initialized.
+     *
+     * @param inBuffer the input ByteBuffer
+     * @param outBuffer the output ByteBuffer
+     * @return int number of bytes stored in {@code output}
+     * @throws BadPaddingException if this cipher is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this cipher is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
+     * @throws ShortBufferException if the given output buffer is too small to
+     *         hold the result
+     */
+    @Override
+    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
+        return cipher.doFinal(inBuffer, outBuffer);
     }
 
     /**
@@ -81,6 +133,17 @@ class JceCipher implements CryptoCipher {
         return cipher.getAlgorithm();
     }
 
+    /**
+     * Returns the block size (in bytes).
+     *
+     * @return the block size (in bytes), or 0 if the underlying algorithm is
+     * not a block cipher
+     */
+    @Override
+    public final int getBlockSize() {
+        return cipher.getBlockSize();
+    }
+
     /**
      * Initializes the cipher with mode, key and iv.
      *
@@ -104,22 +167,6 @@ class JceCipher implements CryptoCipher {
         cipher.init(mode, key, params);
     }
 
-    /**
-     * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
-     *
-     * @param inBuffer the input ByteBuffer
-     * @param outBuffer the output ByteBuffer
-     * @return int number of bytes stored in {@code output}
-     * @throws ShortBufferException if there is insufficient space in the output
-     *         buffer
-     */
-    @Override
-    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
-            throws ShortBufferException {
-        return cipher.update(inBuffer, outBuffer);
-    }
-
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
      * encrypted or decrypted, depending on how this cipher was initialized.
@@ -140,62 +187,23 @@ class JceCipher implements CryptoCipher {
                 .update(input, inputOffset, inputLen, output, outputOffset);
     }
 
+
     /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation. The data is encrypted or decrypted, depending on
-     * how this cipher was initialized.
+     * Continues a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
      *
      * @param inBuffer the input ByteBuffer
      * @param outBuffer the output ByteBuffer
      * @return int number of bytes stored in {@code output}
-     * @throws BadPaddingException if this cipher is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this cipher is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
-     * @throws ShortBufferException if the given output buffer is too small to
-     *         hold the result
-     */
-    @Override
-    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
-            throws ShortBufferException, IllegalBlockSizeException,
-            BadPaddingException {
-        return cipher.doFinal(inBuffer, outBuffer);
-    }
-
-    /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
-     *
-     * @param input the input byte array
-     * @param inputOffset the offset in input where the input starts
-     * @param inputLen the input length
-     * @param output the byte array for the result
-     * @param outputOffset the offset in output where the result is stored
-     * @return the number of bytes stored in output
-     * @throws ShortBufferException if the given output byte array is too small
-     *         to hold the result
-     * @throws BadPaddingException if this cipher is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this cipher is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
+     * @throws ShortBufferException if there is insufficient space in the output
+     *         buffer
      */
     @Override
-    public int doFinal(final byte[] input, final int inputOffset, final int inputLen,
-            final byte[] output, final int outputOffset) throws ShortBufferException,
-            IllegalBlockSizeException, BadPaddingException {
-        return cipher.doFinal(input, inputOffset, inputLen, output,
-                outputOffset);
+    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
+            throws ShortBufferException {
+        return cipher.update(inBuffer, outBuffer);
     }
 
-
     /**
      * Continues a multi-part update of the Additional Authentication
      * Data (AAD).
@@ -224,6 +232,7 @@ class JceCipher implements CryptoCipher {
         cipher.updateAAD(aad);
     }
 
+
     /**
      * Continues a multi-part update of the Additional Authentication
      * Data (AAD).
@@ -251,13 +260,4 @@ class JceCipher implements CryptoCipher {
     public void updateAAD(final ByteBuffer aad) {
         cipher.updateAAD(aad);
     }
-
-
-    /**
-     * Closes Jce cipher.
-     */
-    @Override
-    public void close() {
-        // Do nothing
-    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
index c97f392..c3ba228 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
@@ -37,12 +37,6 @@ import org.apache.commons.crypto.utils.Utils;
  */
 final class OpenSsl {
 
-    // Mode constant defined by OpenSsl JNI
-    public static final int ENCRYPT_MODE = 1;
-    public static final int DECRYPT_MODE = 0;
-
-    private final AbstractOpenSslFeedbackCipher opensslBlockCipher;
-
     /** Currently only support AES/CTR/NoPadding. */
     private enum AlgorithmMode {
         AES_CTR, AES_CBC, AES_GCM;
@@ -63,6 +57,10 @@ final class OpenSsl {
             }
         }
     }
+    // Mode constant defined by OpenSsl JNI
+    public static final int ENCRYPT_MODE = 1;
+
+    public static final int DECRYPT_MODE = 0;
 
     private static final Throwable loadingFailureReason;
 
@@ -81,30 +79,6 @@ final class OpenSsl {
         }
     }
 
-    /**
-     * Gets the failure reason when loading OpenSsl native.
-     *
-     * @return the failure reason; null if it was loaded and initialized successfully
-     */
-    public static Throwable getLoadingFailureReason() {
-        return loadingFailureReason;
-    }
-
-    /**
-     * Constructs a {@link OpenSsl} instance based on context, algorithm and padding.
-     *
-     * @param context the context.
-     * @param algorithm the algorithm.
-     * @param padding the padding.
-     */
-    private OpenSsl(final long context, final int algorithm, final int padding) {
-        if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
-            opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding);
-        } else {
-            opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding);
-        }
-    }
-
     /**
      * Gets an {@code OpenSslCipher} that implements the specified
      * transformation.
@@ -132,64 +106,36 @@ final class OpenSsl {
     }
 
     /**
-     * Initializes this cipher with a key and IV.
+     * Gets the failure reason when loading OpenSsl native.
      *
-     * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
-     * @param key crypto key
-     * @param params the algorithm parameters
-     * @throws InvalidAlgorithmParameterException if IV length is wrong
+     * @return the failure reason; null if it was loaded and initialized successfully
      */
-    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
-        opensslBlockCipher.init(mode, key, params);
+    public static Throwable getLoadingFailureReason() {
+        return loadingFailureReason;
     }
 
+    private final AbstractOpenSslFeedbackCipher opensslBlockCipher;
+
     /**
-     * Updates a multiple-part encryption or decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
-     *
-     * <p>
-     * All {@code input.remaining()} bytes starting at
-     * {@code input.position()} are processed. The result is stored in the
-     * output buffer.
-     * </p>
-     *
-     * <p>
-     * Upon return, the input buffer's position will be equal to its limit; its
-     * limit will not have changed. The output buffer's position will have
-     * advanced by n, when n is the value returned by this method; the output
-     * buffer's limit will not have changed.
-     * </p>
-     *
-     * If {@code output.remaining()} bytes are insufficient to hold the
-     * result, a {@code ShortBufferException} is thrown.
+     * Constructs a {@link OpenSsl} instance based on context, algorithm and padding.
      *
-     * @param input the input ByteBuffer
-     * @param output the output ByteBuffer
-     * @return int number of bytes stored in {@code output}
-     * @throws ShortBufferException if there is insufficient space in the output
-     *         buffer
+     * @param context the context.
+     * @param algorithm the algorithm.
+     * @param padding the padding.
      */
-    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
-        Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required.");
-        return opensslBlockCipher.update(input, output);
+    private OpenSsl(final long context, final int algorithm, final int padding) {
+        if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
+            opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding);
+        } else {
+            opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding);
+        }
     }
 
-    /**
-     * Updates a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
-     *
-     * @param input the input byte array
-     * @param inputOffset the offset in input where the input starts
-     * @param inputLen the input length
-     * @param output the byte array for the result
-     * @param outputOffset the offset in output where the result is stored
-     * @return the number of bytes stored in output
-     * @throws ShortBufferException if there is insufficient space in the output
-     *         byte array
-     */
-    public int update(final byte[] input, final int inputOffset, final int inputLen,
-            final byte[] output, final int outputOffset) throws ShortBufferException {
-        return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset);
+    /** Forcibly clean the context. */
+    public void clean() {
+        if (opensslBlockCipher != null) {
+            opensslBlockCipher.clean();
+        }
     }
 
     /**
@@ -262,6 +208,73 @@ final class OpenSsl {
         return opensslBlockCipher.doFinal(input, output);
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        clean();
+    }
+
+    /**
+     * Initializes this cipher with a key and IV.
+     *
+     * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
+     * @param key crypto key
+     * @param params the algorithm parameters
+     * @throws InvalidAlgorithmParameterException if IV length is wrong
+     */
+    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
+        opensslBlockCipher.init(mode, key, params);
+    }
+
+    /**
+     * Updates a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
+     *
+     * @param input the input byte array
+     * @param inputOffset the offset in input where the input starts
+     * @param inputLen the input length
+     * @param output the byte array for the result
+     * @param outputOffset the offset in output where the result is stored
+     * @return the number of bytes stored in output
+     * @throws ShortBufferException if there is insufficient space in the output
+     *         byte array
+     */
+    public int update(final byte[] input, final int inputOffset, final int inputLen,
+            final byte[] output, final int outputOffset) throws ShortBufferException {
+        return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset);
+    }
+
+
+    /**
+     * Updates a multiple-part encryption or decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
+     *
+     * <p>
+     * All {@code input.remaining()} bytes starting at
+     * {@code input.position()} are processed. The result is stored in the
+     * output buffer.
+     * </p>
+     *
+     * <p>
+     * Upon return, the input buffer's position will be equal to its limit; its
+     * limit will not have changed. The output buffer's position will have
+     * advanced by n, when n is the value returned by this method; the output
+     * buffer's limit will not have changed.
+     * </p>
+     *
+     * If {@code output.remaining()} bytes are insufficient to hold the
+     * result, a {@code ShortBufferException} is thrown.
+     *
+     * @param input the input ByteBuffer
+     * @param output the output ByteBuffer
+     * @return int number of bytes stored in {@code output}
+     * @throws ShortBufferException if there is insufficient space in the output
+     *         buffer
+     */
+    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
+        Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required.");
+        return opensslBlockCipher.update(input, output);
+    }
+
     /**
      * Continues a multi-part update of the Additional Authentication
      * Data (AAD).
@@ -279,17 +292,4 @@ final class OpenSsl {
         this.opensslBlockCipher.updateAAD(aad);
     }
 
-
-    /** Forcibly clean the context. */
-    public void clean() {
-        if (opensslBlockCipher != null) {
-            opensslBlockCipher.clean();
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        clean();
-    }
-
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
index 3b0b829..71f7fd2 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
@@ -65,14 +65,65 @@ final class OpenSslCipher implements CryptoCipher {
     }
 
     /**
-     * Returns the block size (in bytes).
+     * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context.
+     */
+    @Override
+    public void close() {
+        openSslEngine.clean();
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation.
      *
-     * @return the block size (in bytes), or 0 if the underlying algorithm is
-     * not a block openSslEngine
+     * @param input the input byte array
+     * @param inputOffset the offset in input where the input starts
+     * @param inputLen the input length
+     * @param output the byte array for the result
+     * @param outputOffset the offset in output where the result is stored
+     * @return the number of bytes stored in output
+     * @throws ShortBufferException if the given output byte array is too small
+     *         to hold the result
+     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this openSslEngine is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
      */
     @Override
-    public int getBlockSize() {
-        return CryptoCipherFactory.AES_BLOCK_SIZE;
+    public int doFinal(final byte[] input, final int inputOffset, final int inputLen,
+            final byte[] output, final int outputOffset) throws ShortBufferException,
+            IllegalBlockSizeException, BadPaddingException {
+        return openSslEngine.doFinal(input, inputOffset, inputLen, output,outputOffset);
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on
+     * how this openSslEngine was initialized.
+     *
+     * @param inBuffer the input ByteBuffer
+     * @param outBuffer the output ByteBuffer
+     * @return int number of bytes stored in {@code output}
+     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
+     *         (un)padding has been requested, but the decrypted data is not
+     *         bounded by the appropriate padding bytes
+     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
+     *         padding has been requested (only in encryption mode), and the
+     *         total input length of the data processed by this openSslEngine is not a
+     *         multiple of block size; or if this encryption algorithm is unable
+     *         to process the input data provided.
+     * @throws ShortBufferException if the given output buffer is too small to
+     *         hold the result
+     */
+    @Override
+    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
+        return openSslEngine.doFinal(inBuffer, outBuffer);
     }
 
     /**
@@ -91,6 +142,17 @@ final class OpenSslCipher implements CryptoCipher {
         return transformation;
     }
 
+    /**
+     * Returns the block size (in bytes).
+     *
+     * @return the block size (in bytes), or 0 if the underlying algorithm is
+     * not a block openSslEngine
+     */
+    @Override
+    public int getBlockSize() {
+        return CryptoCipherFactory.AES_BLOCK_SIZE;
+    }
+
     /**
      * Initializes the openSslEngine with mode, key and iv.
      *
@@ -111,22 +173,6 @@ final class OpenSslCipher implements CryptoCipher {
         initialized = true;
     }
 
-    /**
-     * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this openSslEngine was initialized.
-     *
-     * @param inBuffer the input ByteBuffer
-     * @param outBuffer the output ByteBuffer
-     * @return int number of bytes stored in {@code output}
-     * @throws ShortBufferException if there is insufficient space in the output
-     *         buffer
-     */
-    @Override
-    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
-            throws ShortBufferException {
-        return openSslEngine.update(inBuffer, outBuffer);
-    }
-
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
      * encrypted or decrypted, depending on how this openSslEngine was initialized.
@@ -147,61 +193,23 @@ final class OpenSslCipher implements CryptoCipher {
                 .update(input, inputOffset, inputLen, output, outputOffset);
     }
 
+
     /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation. The data is encrypted or decrypted, depending on
-     * how this openSslEngine was initialized.
+     * Continues a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this openSslEngine was initialized.
      *
      * @param inBuffer the input ByteBuffer
      * @param outBuffer the output ByteBuffer
      * @return int number of bytes stored in {@code output}
-     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this openSslEngine is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
-     * @throws ShortBufferException if the given output buffer is too small to
-     *         hold the result
-     */
-    @Override
-    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
-            throws ShortBufferException, IllegalBlockSizeException,
-            BadPaddingException {
-        return openSslEngine.doFinal(inBuffer, outBuffer);
-    }
-
-    /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
-     *
-     * @param input the input byte array
-     * @param inputOffset the offset in input where the input starts
-     * @param inputLen the input length
-     * @param output the byte array for the result
-     * @param outputOffset the offset in output where the result is stored
-     * @return the number of bytes stored in output
-     * @throws ShortBufferException if the given output byte array is too small
-     *         to hold the result
-     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
-     *         (un)padding has been requested, but the decrypted data is not
-     *         bounded by the appropriate padding bytes
-     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
-     *         padding has been requested (only in encryption mode), and the
-     *         total input length of the data processed by this openSslEngine is not a
-     *         multiple of block size; or if this encryption algorithm is unable
-     *         to process the input data provided.
+     * @throws ShortBufferException if there is insufficient space in the output
+     *         buffer
      */
     @Override
-    public int doFinal(final byte[] input, final int inputOffset, final int inputLen,
-            final byte[] output, final int outputOffset) throws ShortBufferException,
-            IllegalBlockSizeException, BadPaddingException {
-        return openSslEngine.doFinal(input, inputOffset, inputLen, output,outputOffset);
+    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
+            throws ShortBufferException {
+        return openSslEngine.update(inBuffer, outBuffer);
     }
 
-
     /**
      * Continues a multi-part update of the Additional Authentication
      * Data (AAD).
@@ -241,6 +249,7 @@ final class OpenSslCipher implements CryptoCipher {
         openSslEngine.updateAAD(aad);
     }
 
+
     /**
      * Continues a multi-part update of the Additional Authentication
      * Data (AAD).
@@ -282,13 +291,4 @@ final class OpenSslCipher implements CryptoCipher {
         aad.get(aadBytes);
         openSslEngine.updateAAD(aadBytes);
     }
-
-
-    /**
-     * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context.
-     */
-    @Override
-    public void close() {
-        openSslEngine.clean();
-    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
index e8cc1e5..367da36 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
@@ -38,36 +38,6 @@ final class OpenSslCommonMode extends AbstractOpenSslFeedbackCipher {
         super(context, algorithmMode, padding);
     }
 
-    @Override
-    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
-        this.cipherMode = mode;
-        final byte[] iv;
-        if (!(params instanceof IvParameterSpec)) {
-            // other AlgorithmParameterSpec is not supported now.
-            throw new InvalidAlgorithmParameterException("Illegal parameters");
-        }
-        iv = ((IvParameterSpec) params).getIV();
-        context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv);
-    }
-
-    @Override
-    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
-        checkState();
-
-        final int len = OpenSslNative.update(context, input, input.position(), input.remaining(), output, output.position(), output.remaining());
-        input.position(input.limit());
-        output.position(output.position() + len);
-
-        return len;
-    }
-
-    @Override
-    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException {
-        checkState();
-
-        return OpenSslNative.updateByteArray(context, input, inputOffset, inputLen, output, outputOffset, output.length - outputOffset);
-    }
-
     @Override
     public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
@@ -100,6 +70,36 @@ final class OpenSslCommonMode extends AbstractOpenSslFeedbackCipher {
         return totalLen;
     }
 
+    @Override
+    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
+        this.cipherMode = mode;
+        final byte[] iv;
+        if (!(params instanceof IvParameterSpec)) {
+            // other AlgorithmParameterSpec is not supported now.
+            throw new InvalidAlgorithmParameterException("Illegal parameters");
+        }
+        iv = ((IvParameterSpec) params).getIV();
+        context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv);
+    }
+
+    @Override
+    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException {
+        checkState();
+
+        return OpenSslNative.updateByteArray(context, input, inputOffset, inputLen, output, outputOffset, output.length - outputOffset);
+    }
+
+    @Override
+    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
+        checkState();
+
+        final int len = OpenSslNative.update(context, input, input.position(), input.remaining(), output, output.position(), output.remaining());
+        input.position(input.limit());
+        output.position(output.position() + len);
+
+        return len;
+    }
+
     @Override
     public void updateAAD(final byte[] aad) {
         throw new UnsupportedOperationException("The underlying Cipher implementation does not support this method");
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
index b07000b..ba73fe6 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
@@ -38,11 +38,11 @@ import javax.crypto.spec.GCMParameterSpec;
  */
 final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher {
 
+    static final int DEFAULT_TAG_LEN = 16;
     // buffer for AAD data; if consumed, set as null
     private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
-    private int tagBitLen = -1;
 
-    static final int DEFAULT_TAG_LEN = 16;
+    private int tagBitLen = -1;
 
     // buffer for storing input in decryption, not used for encryption
     private ByteArrayOutputStream inBuffer;
@@ -52,74 +52,9 @@ final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher {
     }
 
     @Override
-    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params)
-            throws InvalidAlgorithmParameterException {
-
-        if (aadBuffer == null) {
-            aadBuffer = new ByteArrayOutputStream();
-        } else {
-            aadBuffer.reset();
-        }
-
-        this.cipherMode = mode;
-        final byte[] iv;
-        if (!(params instanceof GCMParameterSpec)) {
-            // other AlgorithmParameterSpec is not supported now.
-            throw new InvalidAlgorithmParameterException("Illegal parameters");
-        }
-        final GCMParameterSpec gcmParam = (GCMParameterSpec) params;
-        iv = gcmParam.getIV();
-        this.tagBitLen = gcmParam.getTLen();
-
-        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
-            inBuffer = new ByteArrayOutputStream();
-        }
-
-        context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv);
-    }
-
-    @Override
-    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
-        checkState();
-
-        processAAD();
-
-        final int len;
-        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
-            // store internally until doFinal(decrypt) is called because
-            // spec mentioned that only return recovered data after tag
-            // is successfully verified
-            final int inputLen = input.remaining();
-            final byte[] inputBuf = new byte[inputLen];
-            input.get(inputBuf, 0, inputLen);
-            inBuffer.write(inputBuf, 0, inputLen);
-            return 0;
-        }
-        len = OpenSslNative.update(context, input, input.position(),
-                input.remaining(), output, output.position(),
-                output.remaining());
-        input.position(input.limit());
-        output.position(output.position() + len);
-
-        return len;
-    }
-
-    @Override
-    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
-            throws ShortBufferException {
-        checkState();
-
-        processAAD();
-
-        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
-            // store internally until doFinal(decrypt) is called because
-            // spec mentioned that only return recovered data after tag
-            // is successfully verified
-            inBuffer.write(input, inputOffset, inputLen);
-            return 0;
-        }
-        return OpenSslNative.updateByteArray(context, input, inputOffset,
-                inputLen, output, outputOffset, output.length - outputOffset);
+    public void clean() {
+        super.clean();
+        aadBuffer = null;
     }
 
     @Override
@@ -259,33 +194,6 @@ final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher {
         return totalLen;
     }
 
-    @Override
-    public void clean() {
-        super.clean();
-        aadBuffer = null;
-    }
-
-    @Override
-    public void updateAAD(final byte[] aad) {
-        // must be called after initialized.
-        if (aadBuffer == null) {
-            // update has already been called
-            throw new IllegalStateException("Update has been called; no more AAD data");
-        }
-        aadBuffer.write(aad, 0, aad.length);
-    }
-
-    private void processAAD() {
-        if (aadBuffer != null && aadBuffer.size() > 0) {
-            OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), 0, aadBuffer.size(), null, 0, 0);
-            aadBuffer = null;
-        }
-    }
-
-    private int getTagLen() {
-        return tagBitLen < 0 ? DEFAULT_TAG_LEN : tagBitLen >> 3;
-    }
-
     /**
      * Wraps of OpenSslNative.ctrl(long context, int type, int arg, byte[] data)
      * Since native interface EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) is generic,
@@ -311,4 +219,96 @@ final class OpenSslGaloisCounterMode extends AbstractOpenSslFeedbackCipher {
             return 0;
         }
     }
+
+    private int getTagLen() {
+        return tagBitLen < 0 ? DEFAULT_TAG_LEN : tagBitLen >> 3;
+    }
+
+    @Override
+    public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+
+        if (aadBuffer == null) {
+            aadBuffer = new ByteArrayOutputStream();
+        } else {
+            aadBuffer.reset();
+        }
+
+        this.cipherMode = mode;
+        final byte[] iv;
+        if (!(params instanceof GCMParameterSpec)) {
+            // other AlgorithmParameterSpec is not supported now.
+            throw new InvalidAlgorithmParameterException("Illegal parameters");
+        }
+        final GCMParameterSpec gcmParam = (GCMParameterSpec) params;
+        iv = gcmParam.getIV();
+        this.tagBitLen = gcmParam.getTLen();
+
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            inBuffer = new ByteArrayOutputStream();
+        }
+
+        context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv);
+    }
+
+    private void processAAD() {
+        if (aadBuffer != null && aadBuffer.size() > 0) {
+            OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), 0, aadBuffer.size(), null, 0, 0);
+            aadBuffer = null;
+        }
+    }
+
+    @Override
+    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
+            throws ShortBufferException {
+        checkState();
+
+        processAAD();
+
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            // store internally until doFinal(decrypt) is called because
+            // spec mentioned that only return recovered data after tag
+            // is successfully verified
+            inBuffer.write(input, inputOffset, inputLen);
+            return 0;
+        }
+        return OpenSslNative.updateByteArray(context, input, inputOffset,
+                inputLen, output, outputOffset, output.length - outputOffset);
+    }
+
+    @Override
+    public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
+        checkState();
+
+        processAAD();
+
+        final int len;
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            // store internally until doFinal(decrypt) is called because
+            // spec mentioned that only return recovered data after tag
+            // is successfully verified
+            final int inputLen = input.remaining();
+            final byte[] inputBuf = new byte[inputLen];
+            input.get(inputBuf, 0, inputLen);
+            inBuffer.write(inputBuf, 0, inputLen);
+            return 0;
+        }
+        len = OpenSslNative.update(context, input, input.position(),
+                input.remaining(), output, output.position(),
+                output.remaining());
+        input.position(input.limit());
+        output.position(output.position() + len);
+
+        return len;
+    }
+
+    @Override
+    public void updateAAD(final byte[] aad) {
+        // must be called after initialized.
+        if (aadBuffer == null) {
+            // update has already been called
+            throw new IllegalStateException("Update has been called; no more AAD data");
+        }
+        aadBuffer.write(aad, 0, aad.length);
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java
index e8941a9..f29ed36 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSsl10XNativeJna.java
@@ -51,35 +51,64 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
     // Try to keep methods aligned across versions
 
     /**
-     * @see <a href="https://www.openssl.org/docs/man1.0.2/man3/SSLeay.html">Version Number</a>
-     * TODO (does not appear to be used yet)
-     * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier
+     * Gets engine by id
+     *
+     * @param id
+     *            engine id
+     * @return engine instance
      */
-    public static native NativeLong SSLeay();
+    public static native PointerByReference ENGINE_by_id(String id);
 
     /**
-     * Retrieves version/build information about OpenSSL library.
-     * This is returned by {@link OpenSslNativeJna#OpenSSLVersion(int)}
+     * Cleanups before program exit, it will avoid memory leaks.
      *
-     * @see <a href="https://www.openssl.org/docs/man1.0.2/man3/SSLeay_version.html">Version Info</a>
+     * @return 0 on success, 1 otherwise.
+     */
+    public static native int ENGINE_cleanup();
+
+    /**
+     * Releases all functional references.
      *
-     * @param type
-     *            type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON...
-     * @return A pointer to a constant string describing the version of the OpenSSL library or
-     *         giving information about the library build.
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native String SSLeay_version(int type);
+    public static native int ENGINE_finish(PointerByReference e);
 
-    // TODO: NOT USED?
     /**
-     * Registers the error strings for all libcrypto functions.
+     * Frees the structural reference
+     *
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native void ERR_load_crypto_strings();
+    public static native int ENGINE_free(PointerByReference e);
 
     /**
-     * @return the earliest error code from the thread's error queue without modifying it.
+     * Obtains a functional reference from an existing structural reference.
+     *
+     * @param e
+     *            engine reference
+     * @return zero if the ENGINE was not already operational and couldn't be successfully
+     *         initialized
      */
-    public static native NativeLong ERR_peek_error();
+    public static native int ENGINE_init(PointerByReference e);
+
+    /**
+     * Initializes the engine.
+     */
+    public static native void ENGINE_load_rdrand();
+
+    /**
+     * Sets the engine as the default for random number generation.
+     *
+     * @param e
+     *            engine reference
+     * @param flags
+     *            ENGINE_METHOD_RAND
+     * @return zero if failed.
+     */
+    public static native int ENGINE_set_default(PointerByReference e, int flags);
 
     /**
      * Generates a human-readable string representing the error code e.
@@ -94,32 +123,16 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native String ERR_error_string(NativeLong err, char[] null_);
 
-    /**
-     * Creates a cipher context.
-     *
-     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
-     */
-    public static native PointerByReference EVP_CIPHER_CTX_new();
-
     // TODO: NOT USED?
     /**
-     * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset
-     *
-     * @param p
-     *            cipher context
+     * Registers the error strings for all libcrypto functions.
      */
-    public static native void EVP_CIPHER_CTX_init(PointerByReference p);
+    public static native void ERR_load_crypto_strings();
 
     /**
-     * Enables or disables padding
-     *
-     * @param c
-     *            cipher context
-     * @param pad
-     *            If the pad parameter is zero then no padding is performed
-     * @return always returns 1
+     * @return the earliest error code from the thread's error queue without modifying it.
      */
-    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+    public static native NativeLong ERR_peek_error();
 
     /**
      * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode
@@ -151,6 +164,67 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native PointerByReference EVP_aes_256_ctr();
 
+    /**
+     * Clears all information from a cipher context and free up any allocated * memory associate
+     * with it.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+    public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c);
+
+    /**
+     * Clears all information from a cipher context and free up any allocated memory associate with
+     * it, including ctx itself.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
+
+    // TODO: NOT USED?
+    /**
+     * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset
+     *
+     * @param p
+     *            cipher context
+     */
+    public static native void EVP_CIPHER_CTX_init(PointerByReference p);
+
+    /**
+     * Creates a cipher context.
+     *
+     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
+     */
+    public static native PointerByReference EVP_CIPHER_CTX_new();
+
+    /**
+     * Enables or disables padding
+     *
+     * @param c
+     *            cipher context
+     * @param pad
+     *            If the pad parameter is zero then no padding is performed
+     * @return always returns 1
+     */
+    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+
+    /**
+     * Finishes a multiple-part operation.
+     *
+     * @param ctx
+     *            cipher context
+     * @param bout
+     *            output byte buffer
+     * @param outl
+     *            output length
+     * @return 1 for success and 0 for failure.
+     */
+    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
+            int[] outl);
+
+    // ENGINE API: https://www.openssl.org/docs/man1.0.2/man3/engine.html
+
     /**
      * Init a cipher.
      *
@@ -189,53 +263,6 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
     public static native int EVP_CipherUpdate(PointerByReference ctx, ByteBuffer bout, int[] outl,
             ByteBuffer in, int inl);
 
-    /**
-     * Finishes a multiple-part operation.
-     *
-     * @param ctx
-     *            cipher context
-     * @param bout
-     *            output byte buffer
-     * @param outl
-     *            output length
-     * @return 1 for success and 0 for failure.
-     */
-    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
-            int[] outl);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated memory associate with
-     * it, including ctx itself.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated * memory associate
-     * with it.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-    public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c);
-
-    // Random generator
-    /**
-     * OpenSSL uses for random number generation
-     *
-     * @return pointers to the respective methods
-     */
-    public static native PointerByReference RAND_get_rand_method();
-
-    /**
-     * OpenSSL uses for random number generation.
-     *
-     * @return pointers to the respective methods
-     */
-    public static native PointerByReference RAND_SSLeay();
-
     /**
      * Generates random data
      *
@@ -247,84 +274,52 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native int RAND_bytes(ByteBuffer buf, int num);
 
-    // ENGINE API: https://www.openssl.org/docs/man1.0.2/man3/engine.html
-
-    /**
-     * Releases all functional references.
-     *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
-     */
-    public static native int ENGINE_finish(PointerByReference e);
-
+    // Random generator
     /**
-     * Frees the structural reference
+     * OpenSSL uses for random number generation
      *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
+     * @return pointers to the respective methods
      */
-    public static native int ENGINE_free(PointerByReference e);
+    public static native PointerByReference RAND_get_rand_method();
 
     /**
-     * Cleanups before program exit, it will avoid memory leaks.
+     * OpenSSL uses for random number generation.
      *
-     * @return 0 on success, 1 otherwise.
+     * @return pointers to the respective methods
      */
-    public static native int ENGINE_cleanup();
+    public static native PointerByReference RAND_SSLeay();
 
     /**
-     * Obtains a functional reference from an existing structural reference.
-     *
-     * @param e
-     *            engine reference
-     * @return zero if the ENGINE was not already operational and couldn't be successfully
-     *         initialized
+     * @see <a href="https://www.openssl.org/docs/man1.0.2/man3/SSLeay.html">Version Number</a>
+     * TODO (does not appear to be used yet)
+     * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier
      */
-    public static native int ENGINE_init(PointerByReference e);
+    public static native NativeLong SSLeay();
 
     /**
-     * Sets the engine as the default for random number generation.
+     * Retrieves version/build information about OpenSSL library.
+     * This is returned by {@link OpenSslNativeJna#OpenSSLVersion(int)}
      *
-     * @param e
-     *            engine reference
-     * @param flags
-     *            ENGINE_METHOD_RAND
-     * @return zero if failed.
-     */
-    public static native int ENGINE_set_default(PointerByReference e, int flags);
-
-    /**
-     * Gets engine by id
+     * @see <a href="https://www.openssl.org/docs/man1.0.2/man3/SSLeay_version.html">Version Info</a>
      *
-     * @param id
-     *            engine id
-     * @return engine instance
-     */
-    public static native PointerByReference ENGINE_by_id(String id);
-
-    /**
-     * Initializes the engine.
+     * @param type
+     *            type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON...
+     * @return A pointer to a constant string describing the version of the OpenSSL library or
+     *         giving information about the library build.
      */
-    public static native void ENGINE_load_rdrand();
+    public static native String SSLeay_version(int type);
 
 
     // ================== instance interface methods ==================
 
     @Override
-    public boolean _INIT_OK() {
-        return INIT_OK;
-    }
-
-    @Override
-    public Throwable _INIT_ERROR() {
-        return INIT_ERROR;
+    public PointerByReference _ENGINE_by_id(final String string) {
+        return ENGINE_by_id(string);
     }
 
     @Override
-    public PointerByReference _ENGINE_by_id(final String string) {
-        return ENGINE_by_id(string);
+    public int _ENGINE_cleanup() {
+        return ENGINE_cleanup();
     }
 
     @Override
@@ -342,6 +337,11 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
         return ENGINE_init(rdrandEngine);
     }
 
+    @Override
+    public void _ENGINE_load_rdrand() {
+        ENGINE_load_rdrand();
+    }
+
     @Override
     public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) {
         return ENGINE_set_default(rdrandEngine, flags);
@@ -387,6 +387,11 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
         return EVP_aes_256_ctr();
     }
 
+    @Override
+    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
+        EVP_CIPHER_CTX_cleanup(context);
+    }
+
     @Override
     public void _EVP_CIPHER_CTX_free(final PointerByReference context) {
         EVP_CIPHER_CTX_free(context);
@@ -420,18 +425,13 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public int _RAND_bytes(final ByteBuffer buf, final int length) {
-        return RAND_bytes(buf, length) ;
-    }
-
-    @Override
-    public PointerByReference _RAND_get_rand_method() {
-        return RAND_get_rand_method();
+    public Throwable _INIT_ERROR() {
+        return INIT_ERROR;
     }
 
     @Override
-    public PointerByReference _RAND_SSLeay() {
-        return RAND_SSLeay();
+    public boolean _INIT_OK() {
+        return INIT_OK;
     }
 
     @Override
@@ -440,17 +440,17 @@ final class OpenSsl10XNativeJna implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public void _ENGINE_load_rdrand() {
-        ENGINE_load_rdrand();
+    public int _RAND_bytes(final ByteBuffer buf, final int length) {
+        return RAND_bytes(buf, length) ;
     }
 
     @Override
-    public int _ENGINE_cleanup() {
-        return ENGINE_cleanup();
+    public PointerByReference _RAND_get_rand_method() {
+        return RAND_get_rand_method();
     }
 
     @Override
-    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
-        EVP_CIPHER_CTX_cleanup(context);
+    public PointerByReference _RAND_SSLeay() {
+        return RAND_SSLeay();
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java
index 6a52e20..0c6f64a 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSsl11XNativeJna.java
@@ -51,20 +51,52 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
     // Try to keep methods aligned across versions
 
     /**
-     * Retrieves version/build information about OpenSSL library.
+     * Gets engine by id
      *
-     * @see <a href="https://www.openssl.org/docs/man1.1.1/man3/OpenSSL_version.html">OpenSSL_version</a>
-     * @param type
-     *            type can be OPENSSL_VERSION, OPENSSL_CFLAGS, OPENSSL_BUILT_ON...
-     * @return A pointer to a constant string describing the version of the OpenSSL library or
-     *         giving information about the library build.
+     * @param id
+     *            engine id
+     * @return engine instance
      */
-    public static native String OpenSSL_version(int type);
+    public static native PointerByReference ENGINE_by_id(String id);
 
     /**
-     * @return the earliest error code from the thread's error queue without modifying it.
+     * Releases all functional references.
+     *
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native NativeLong ERR_peek_error();
+    public static native int ENGINE_finish(PointerByReference e);
+
+    /**
+     * Frees the structural reference
+     *
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
+     */
+    public static native int ENGINE_free(PointerByReference e);
+
+    /**
+     * Obtains a functional reference from an existing structural reference.
+     *
+     * @param e
+     *            engine reference
+     * @return zero if the ENGINE was not already operational and couldn't be successfully
+     *         initialized
+     */
+    public static native int ENGINE_init(PointerByReference e);
+
+    /**
+     * Sets the engine as the default for random number generation.
+     *
+     * @param e
+     *            engine reference
+     * @param flags
+     *            ENGINE_METHOD_RAND
+     * @return zero if failed.
+     */
+    public static native int ENGINE_set_default(PointerByReference e, int flags);
 
     /**
      * Generates a human-readable string representing the error code e.
@@ -80,22 +112,9 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
     public static native String ERR_error_string(NativeLong err, char[] null_);
 
     /**
-     * Creates a cipher context.
-     *
-     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
-     */
-    public static native PointerByReference EVP_CIPHER_CTX_new();
-
-    /**
-     * Enables or disables padding
-     *
-     * @param c
-     *            cipher context
-     * @param pad
-     *            If the pad parameter is zero then no padding is performed
-     * @return always returns 1
+     * @return the earliest error code from the thread's error queue without modifying it.
      */
-    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+    public static native NativeLong ERR_peek_error();
 
     /**
      * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode
@@ -127,6 +146,58 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
      */
     public static native PointerByReference EVP_aes_256_ctr();
 
+    /**
+     * Clears all information from a cipher context and free up any allocated memory associate with
+     * it, including ctx itself.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
+
+    /**
+     * Creates a cipher context.
+     *
+     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
+     */
+    public static native PointerByReference EVP_CIPHER_CTX_new();
+
+    /**
+     * Clears all information from a cipher context and free up any allocated * memory associate
+     * with it.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+
+    /**
+     * Enables or disables padding
+     *
+     * @param c
+     *            cipher context
+     * @param pad
+     *            If the pad parameter is zero then no padding is performed
+     * @return always returns 1
+     */
+    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+
+    /**
+     * Finishes a multiple-part operation.
+     *
+     * @param ctx
+     *            cipher context
+     * @param bout
+     *            output byte buffer
+     * @param outl
+     *            output length
+     * @return 1 for success and 0 for failure.
+     */
+    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
+            int[] outl);
+
+    // ENGINE API: https://www.openssl.org/docs/man1.1.1/man3/ENGINE_add.html
+    // (The above page includes all the ENGINE functions used below)
+
     /**
      * Init a cipher.
      *
@@ -166,43 +237,15 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
             ByteBuffer in, int inl);
 
     /**
-     * Finishes a multiple-part operation.
-     *
-     * @param ctx
-     *            cipher context
-     * @param bout
-     *            output byte buffer
-     * @param outl
-     *            output length
-     * @return 1 for success and 0 for failure.
-     */
-    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
-            int[] outl);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated memory associate with
-     * it, including ctx itself.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated * memory associate
-     * with it.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-
-    // Random generator
-    /**
-     * OpenSSL uses for random number generation
+     * Retrieves version/build information about OpenSSL library.
      *
-     * @return pointers to the respective methods
+     * @see <a href="https://www.openssl.org/docs/man1.1.1/man3/OpenSSL_version.html">OpenSSL_version</a>
+     * @param type
+     *            type can be OPENSSL_VERSION, OPENSSL_CFLAGS, OPENSSL_BUILT_ON...
+     * @return A pointer to a constant string describing the version of the OpenSSL library or
+     *         giving information about the library build.
      */
-    public static native PointerByReference RAND_get_rand_method();
+    public static native String OpenSSL_version(int type);
 
     /**
      * Generates random data
@@ -215,72 +258,24 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
      */
     public static native int RAND_bytes(ByteBuffer buf, int num);
 
-    // ENGINE API: https://www.openssl.org/docs/man1.1.1/man3/ENGINE_add.html
-    // (The above page includes all the ENGINE functions used below)
-
-    /**
-     * Releases all functional references.
-     *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
-     */
-    public static native int ENGINE_finish(PointerByReference e);
-
-    /**
-     * Frees the structural reference
-     *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
-     */
-    public static native int ENGINE_free(PointerByReference e);
-
-    /**
-     * Obtains a functional reference from an existing structural reference.
-     *
-     * @param e
-     *            engine reference
-     * @return zero if the ENGINE was not already operational and couldn't be successfully
-     *         initialized
-     */
-    public static native int ENGINE_init(PointerByReference e);
-
-    /**
-     * Sets the engine as the default for random number generation.
-     *
-     * @param e
-     *            engine reference
-     * @param flags
-     *            ENGINE_METHOD_RAND
-     * @return zero if failed.
-     */
-    public static native int ENGINE_set_default(PointerByReference e, int flags);
-
+    // Random generator
     /**
-     * Gets engine by id
+     * OpenSSL uses for random number generation
      *
-     * @param id
-     *            engine id
-     * @return engine instance
+     * @return pointers to the respective methods
      */
-    public static native PointerByReference ENGINE_by_id(String id);
+    public static native PointerByReference RAND_get_rand_method();
 
     // ================== instance interface methods ==================
 
     @Override
-    public boolean _INIT_OK() {
-        return INIT_OK;
-    }
-
-    @Override
-    public Throwable _INIT_ERROR() {
-        return INIT_ERROR;
+    public PointerByReference _ENGINE_by_id(final String string) {
+        return ENGINE_by_id(string);
     }
 
     @Override
-    public PointerByReference _ENGINE_by_id(final String string) {
-        return ENGINE_by_id(string);
+    public int _ENGINE_cleanup() {
+        return 0; // Not available
     }
 
     @Override
@@ -298,6 +293,11 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
         return ENGINE_init(rdrandEngine);
     }
 
+    @Override
+    public void _ENGINE_load_rdrand() {
+        // Not available
+    }
+
     @Override
     public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) {
         return ENGINE_set_default(rdrandEngine, flags);
@@ -343,6 +343,11 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
         return EVP_aes_256_ctr();
     }
 
+    @Override
+    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
+        // Not available
+    }
+
     @Override
     public void _EVP_CIPHER_CTX_free(final PointerByReference context) {
         EVP_CIPHER_CTX_free(context);
@@ -376,18 +381,13 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public int _RAND_bytes(final ByteBuffer buf, final int length) {
-        return RAND_bytes(buf, length) ;
-    }
-
-    @Override
-    public PointerByReference _RAND_get_rand_method() {
-        return RAND_get_rand_method();
+    public Throwable _INIT_ERROR() {
+        return INIT_ERROR;
     }
 
     @Override
-    public PointerByReference _RAND_SSLeay() {
-        return null; // Not available
+    public boolean _INIT_OK() {
+        return INIT_OK;
     }
 
     @Override
@@ -396,18 +396,18 @@ final class OpenSsl11XNativeJna  implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public void _ENGINE_load_rdrand() {
-        // Not available
+    public int _RAND_bytes(final ByteBuffer buf, final int length) {
+        return RAND_bytes(buf, length) ;
     }
 
     @Override
-    public int _ENGINE_cleanup() {
-        return 0; // Not available
+    public PointerByReference _RAND_get_rand_method() {
+        return RAND_get_rand_method();
     }
 
     @Override
-    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
-        // Not available
+    public PointerByReference _RAND_SSLeay() {
+        return null; // Not available
     }
 
 }
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSsl20XNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSsl20XNativeJna.java
index eda7056..b8370ae 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSsl20XNativeJna.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSsl20XNativeJna.java
@@ -51,32 +51,59 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
     // Try to keep methods aligned across versions
 
     /**
-     * TODO (does not appear to be used yet)
-     * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier
+     * Gets engine by id.
+     *
+     * @param id
+     *            engine id.
+     * @return engine instance
      */
-    public static native NativeLong SSLeay();
+    public static native PointerByReference ENGINE_by_id(String id);
 
     /**
-     * Retrieves version/build information about OpenSSL library.
-     * This is returned by {@link OpenSslNativeJna#OpenSSLVersion(int)}
+     * Cleanups before program exit, it will avoid memory leaks.
      *
-     * @param type
-     *            type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON...
-     * @return A pointer to a constant string describing the version of the OpenSSL library or
-     *         giving information about the library build.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native String SSLeay_version(int type);
+    public static native int ENGINE_cleanup();
 
-    // TODO: NOT USED?
     /**
-     * Registers the error strings for all libcrypto functions.
+     * Releases all functional references.
+     *
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native void ERR_load_crypto_strings();
+    public static native int ENGINE_finish(PointerByReference e);
 
     /**
-     * @return the earliest error code from the thread's error queue without modifying it.
+     * Frees the structural reference
+     *
+     * @param e
+     *            engine reference.
+     * @return 0 on success, 1 otherwise.
      */
-    public static native NativeLong ERR_peek_error();
+    public static native int ENGINE_free(PointerByReference e);
+
+    /**
+     * Obtains a functional reference from an existing structural reference.
+     *
+     * @param e
+     *            engine reference
+     * @return zero if the ENGINE was not already operational and couldn't be successfully
+     *         initialized
+     */
+    public static native int ENGINE_init(PointerByReference e);
+
+    /**
+     * Sets the engine as the default for random number generation.
+     *
+     * @param e
+     *            engine reference.
+     * @param flags
+     *            ENGINE_METHOD_RAND.
+     * @return zero if failed.
+     */
+    public static native int ENGINE_set_default(PointerByReference e, int flags);
 
     /**
      * Generates a human-readable string representing the error code e.
@@ -91,32 +118,16 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native String ERR_error_string(NativeLong err, char[] null_);
 
-    /**
-     * Creates a cipher context.
-     *
-     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
-     */
-    public static native PointerByReference EVP_CIPHER_CTX_new();
-
     // TODO: NOT USED?
     /**
-     * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset.
-     *
-     * @param p
-     *            cipher context
+     * Registers the error strings for all libcrypto functions.
      */
-    public static native void EVP_CIPHER_CTX_init(PointerByReference p);
+    public static native void ERR_load_crypto_strings();
 
     /**
-     * Enables or disables padding.
-     *
-     * @param c
-     *            cipher context.
-     * @param pad
-     *            If the pad parameter is zero then no padding is performed.
-     * @return always returns 1
+     * @return the earliest error code from the thread's error queue without modifying it.
      */
-    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+    public static native NativeLong ERR_peek_error();
 
     /**
      * @return an OpenSSL AES EVP cipher instance with a 128-bit key CBC mode.
@@ -148,6 +159,65 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native PointerByReference EVP_aes_256_ctr();
 
+    /**
+     * Clears all information from a cipher context and free up any allocated * memory associate
+     * with it.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+    public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c);
+
+    /**
+     * Clears all information from a cipher context and free up any allocated memory associate with
+     * it, including ctx itself.
+     *
+     * @param c
+     *            openssl evp cipher
+     */
+    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
+
+    // TODO: NOT USED?
+    /**
+     * EVP_CIPHER_CTX_init() remains as an alias for EVP_CIPHER_CTX_reset.
+     *
+     * @param p
+     *            cipher context
+     */
+    public static native void EVP_CIPHER_CTX_init(PointerByReference p);
+
+    /**
+     * Creates a cipher context.
+     *
+     * @return a pointer to a newly created EVP_CIPHER_CTX for success and NULL for failure.
+     */
+    public static native PointerByReference EVP_CIPHER_CTX_new();
+
+    /**
+     * Enables or disables padding.
+     *
+     * @param c
+     *            cipher context.
+     * @param pad
+     *            If the pad parameter is zero then no padding is performed.
+     * @return always returns 1
+     */
+    public static native int EVP_CIPHER_CTX_set_padding(PointerByReference c, int pad);
+
+    /**
+     * Finishes a multiple-part operation.
+     *
+     * @param ctx
+     *            cipher context
+     * @param bout
+     *            output byte buffer
+     * @param outl
+     *            output length
+     * @return 1 for success and 0 for failure.
+     */
+    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
+            int[] outl);
+
     /**
      * Init a cipher.
      *
@@ -168,6 +238,8 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
     public static native int EVP_CipherInit_ex(PointerByReference ctx, PointerByReference cipher,
             PointerByReference impl, byte[] key, byte[] iv, int enc);
 
+    // ENGINE API: https://www.openssl.org/docs/man1.0.2/man3/engine.html
+
     /**
      * Continues a multiple-part encryption/decryption operation.
      *
@@ -186,53 +258,6 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
     public static native int EVP_CipherUpdate(PointerByReference ctx, ByteBuffer bout, int[] outl,
             ByteBuffer in, int inl);
 
-    /**
-     * Finishes a multiple-part operation.
-     *
-     * @param ctx
-     *            cipher context
-     * @param bout
-     *            output byte buffer
-     * @param outl
-     *            output length
-     * @return 1 for success and 0 for failure.
-     */
-    public static native int EVP_CipherFinal_ex(PointerByReference ctx, ByteBuffer bout,
-            int[] outl);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated memory associate with
-     * it, including ctx itself.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-    public static native void EVP_CIPHER_CTX_free(PointerByReference c);
-
-    /**
-     * Clears all information from a cipher context and free up any allocated * memory associate
-     * with it.
-     *
-     * @param c
-     *            openssl evp cipher
-     */
-    public static native void EVP_CIPHER_CTX_cleanup(PointerByReference c);
-
-    // Random generator
-    /**
-     * OpenSSL uses for random number generation.
-     *
-     * @return pointers to the respective methods.
-     */
-    public static native PointerByReference RAND_get_rand_method();
-
-    /**
-     * OpenSSL uses for random number generation.
-     *
-     * @return pointers to the respective methods.
-     */
-    public static native PointerByReference RAND_SSLeay();
-
     /**
      * Generates random data.
      *
@@ -244,78 +269,48 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
      */
     public static native int RAND_bytes(ByteBuffer buf, int num);
 
-    // ENGINE API: https://www.openssl.org/docs/man1.0.2/man3/engine.html
-
-    /**
-     * Releases all functional references.
-     *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
-     */
-    public static native int ENGINE_finish(PointerByReference e);
-
-    /**
-     * Frees the structural reference
-     *
-     * @param e
-     *            engine reference.
-     * @return 0 on success, 1 otherwise.
-     */
-    public static native int ENGINE_free(PointerByReference e);
-
+    // Random generator
     /**
-     * Cleanups before program exit, it will avoid memory leaks.
+     * OpenSSL uses for random number generation.
      *
-     * @return 0 on success, 1 otherwise.
+     * @return pointers to the respective methods.
      */
-    public static native int ENGINE_cleanup();
+    public static native PointerByReference RAND_get_rand_method();
 
     /**
-     * Obtains a functional reference from an existing structural reference.
+     * OpenSSL uses for random number generation.
      *
-     * @param e
-     *            engine reference
-     * @return zero if the ENGINE was not already operational and couldn't be successfully
-     *         initialized
+     * @return pointers to the respective methods.
      */
-    public static native int ENGINE_init(PointerByReference e);
+    public static native PointerByReference RAND_SSLeay();
 
     /**
-     * Sets the engine as the default for random number generation.
-     *
-     * @param e
-     *            engine reference.
-     * @param flags
-     *            ENGINE_METHOD_RAND.
-     * @return zero if failed.
+     * TODO (does not appear to be used yet)
+     * @return OPENSSL_VERSION_NUMBER which is a numeric release version identifier
      */
-    public static native int ENGINE_set_default(PointerByReference e, int flags);
+    public static native NativeLong SSLeay();
 
     /**
-     * Gets engine by id.
+     * Retrieves version/build information about OpenSSL library.
+     * This is returned by {@link OpenSslNativeJna#OpenSSLVersion(int)}
      *
-     * @param id
-     *            engine id.
-     * @return engine instance
+     * @param type
+     *            type can be SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON...
+     * @return A pointer to a constant string describing the version of the OpenSSL library or
+     *         giving information about the library build.
      */
-    public static native PointerByReference ENGINE_by_id(String id);
+    public static native String SSLeay_version(int type);
 
     // ================== instance interface methods ==================
 
     @Override
-    public boolean _INIT_OK() {
-        return INIT_OK;
-    }
-
-    @Override
-    public Throwable _INIT_ERROR() {
-        return INIT_ERROR;
+    public PointerByReference _ENGINE_by_id(final String string) {
+        return ENGINE_by_id(string);
     }
 
     @Override
-    public PointerByReference _ENGINE_by_id(final String string) {
-        return ENGINE_by_id(string);
+    public int _ENGINE_cleanup() {
+        return ENGINE_cleanup();
     }
 
     @Override
@@ -333,6 +328,11 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
         return ENGINE_init(rdrandEngine);
     }
 
+    @Override
+    public void _ENGINE_load_rdrand() {
+        // Not available
+    }
+
     @Override
     public int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags) {
         return ENGINE_set_default(rdrandEngine, flags);
@@ -378,6 +378,11 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
         return EVP_aes_256_ctr();
     }
 
+    @Override
+    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
+        EVP_CIPHER_CTX_cleanup(context);
+    }
+
     @Override
     public void _EVP_CIPHER_CTX_free(final PointerByReference context) {
         EVP_CIPHER_CTX_free(context);
@@ -411,18 +416,13 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public int _RAND_bytes(final ByteBuffer buf, final int length) {
-        return RAND_bytes(buf, length) ;
-    }
-
-    @Override
-    public PointerByReference _RAND_get_rand_method() {
-        return RAND_get_rand_method();
+    public Throwable _INIT_ERROR() {
+        return INIT_ERROR;
     }
 
     @Override
-    public PointerByReference _RAND_SSLeay() {
-        return RAND_SSLeay();
+    public boolean _INIT_OK() {
+        return INIT_OK;
     }
 
     @Override
@@ -431,17 +431,17 @@ final class OpenSsl20XNativeJna implements OpenSslInterfaceNativeJna {
     }
 
     @Override
-    public void _ENGINE_load_rdrand() {
-        // Not available
+    public int _RAND_bytes(final ByteBuffer buf, final int length) {
+        return RAND_bytes(buf, length) ;
     }
 
     @Override
-    public int _ENGINE_cleanup() {
-        return ENGINE_cleanup();
+    public PointerByReference _RAND_get_rand_method() {
+        return RAND_get_rand_method();
     }
 
     @Override
-    public void _EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
-        EVP_CIPHER_CTX_cleanup(context);
+    public PointerByReference _RAND_SSLeay() {
+        return RAND_SSLeay();
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java
index 010e70d..e680348 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslInterfaceNativeJna.java
@@ -29,18 +29,19 @@ import com.sun.jna.ptr.PointerByReference;
  */
 interface OpenSslInterfaceNativeJna {
 
-    boolean _INIT_OK();
-
-    Throwable _INIT_ERROR();
-
     PointerByReference _ENGINE_by_id(final String string);
 
+    /** TODO Appears to be deprecated as of OpenSSL 1.1.0. */
+    int _ENGINE_cleanup();
+
     int _ENGINE_finish(final PointerByReference rdrandEngine);
 
     int _ENGINE_free(final PointerByReference rdrandEngine);
 
     int _ENGINE_init(final PointerByReference rdrandEngine);
 
+    void _ENGINE_load_rdrand();
+
     int _ENGINE_set_default(final PointerByReference rdrandEngine, final int flags);
 
     String _ERR_error_string(final NativeLong err, final char[] buff);
@@ -59,6 +60,8 @@ interface OpenSslInterfaceNativeJna {
 
     PointerByReference _EVP_aes_256_ctr();
 
+    void _EVP_CIPHER_CTX_cleanup(final PointerByReference context);
+
     void _EVP_CIPHER_CTX_free(final PointerByReference context);
 
     PointerByReference _EVP_CIPHER_CTX_new();
@@ -74,18 +77,15 @@ interface OpenSslInterfaceNativeJna {
     int _EVP_CipherUpdate(final PointerByReference context, final ByteBuffer outBuffer,
             final int[] outlen, final ByteBuffer inBuffer, final int remaining);
 
-    int _RAND_bytes(final ByteBuffer buf, final int length);
-
-    PointerByReference _RAND_get_rand_method();
+    Throwable _INIT_ERROR();
 
-    PointerByReference _RAND_SSLeay();
+    boolean _INIT_OK();
 
     String _OpenSSL_version(final int i);
 
-    void _ENGINE_load_rdrand();
+    int _RAND_bytes(final ByteBuffer buf, final int length);
 
-    /** TODO Appears to be deprecated as of OpenSSL 1.1.0. */
-    int _ENGINE_cleanup();
+    PointerByReference _RAND_get_rand_method();
 
-    void _EVP_CIPHER_CTX_cleanup(final PointerByReference context);
+    PointerByReference _RAND_SSLeay();
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
index e88afc4..4f8c22a 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
@@ -45,11 +45,34 @@ import com.sun.jna.ptr.PointerByReference;
  */
 final class OpenSslJnaCipher implements CryptoCipher {
 
+    /**
+     * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding.
+     */
+    private enum AlgorithmMode {
+        AES_CTR, AES_CBC;
+
+        /**
+         * Gets the AlgorithmMode instance.
+         *
+         * @param algorithm the algorithm name
+         * @param mode      the mode name
+         * @return the AlgorithmMode instance
+         * @throws NoSuchAlgorithmException if the algorithm is not support
+         */
+        static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
+            try {
+                return AlgorithmMode.valueOf(algorithm + "_" + mode);
+            } catch (final Exception e) {
+                throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
+            }
+        }
+    }
     private PointerByReference algo;
     private final PointerByReference context;
     private final AlgorithmMode algorithmMode;
     private final int padding;
     private final String transformation;
+
     private final int IV_LENGTH = 16;
 
     /**
@@ -77,6 +100,101 @@ final class OpenSslJnaCipher implements CryptoCipher {
 
     }
 
+    /**
+     * Closes the OpenSSL cipher. Clean the OpenSsl native context.
+     */
+    @Override
+    public void close() {
+        if (context != null) {
+            OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
+            // Freeing the context multiple times causes a JVM crash
+            // A work-round is to only free it at finalize time
+            // TODO is that sufficient?
+            // OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
+        }
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation.
+     *
+     * @param input        the input byte array
+     * @param inputOffset  the offset in input where the input starts
+     * @param inputLen     the input length
+     * @param output       the byte array for the result
+     * @param outputOffset the offset in output where the result is stored
+     * @return the number of bytes stored in output
+     * @throws ShortBufferException      if the given output byte array is too small
+     *                                   to hold the result
+     * @throws BadPaddingException       if this cipher is in decryption mode, and
+     *                                   (un)padding has been requested, but the
+     *                                   decrypted data is not bounded by the
+     *                                   appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *                                   padding has been requested (only in
+     *                                   encryption mode), and the total input
+     *                                   length of the data processed by this cipher
+     *                                   is not a multiple of block size; or if this
+     *                                   encryption algorithm is unable to process
+     *                                   the input data provided.
+     */
+    @Override
+    public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
+            final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
+        final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
+        return doFinal(inputBuf, outputBuf);
+    }
+
+    /**
+     * Encrypts or decrypts data in a single-part operation, or finishes a
+     * multiple-part operation. The data is encrypted or decrypted, depending on how
+     * this cipher was initialized.
+     *
+     * @param inBuffer  the input ByteBuffer
+     * @param outBuffer the output ByteBuffer
+     * @return int number of bytes stored in {@code output}
+     * @throws BadPaddingException       if this cipher is in decryption mode, and
+     *                                   (un)padding has been requested, but the
+     *                                   decrypted data is not bounded by the
+     *                                   appropriate padding bytes
+     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+     *                                   padding has been requested (only in
+     *                                   encryption mode), and the total input
+     *                                   length of the data processed by this cipher
+     *                                   is not a multiple of block size; or if this
+     *                                   encryption algorithm is unable to process
+     *                                   the input data provided.
+     * @throws ShortBufferException      if the given output buffer is too small to
+     *                                   hold the result
+     */
+    @Override
+    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        final int uptLen = update(inBuffer, outBuffer);
+        final int[] outlen = new int[1];
+        throwOnError(OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen));
+        final int len = uptLen + outlen[0];
+        outBuffer.position(outBuffer.position() + outlen[0]);
+        return len;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
+        super.finalize();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return transformation;
+    }
+
+    @Override
+    public int getBlockSize() {
+        return CryptoCipherFactory.AES_BLOCK_SIZE;
+    }
+
     /**
      * Initializes the cipher with mode, key and iv.
      *
@@ -140,23 +258,19 @@ final class OpenSslJnaCipher implements CryptoCipher {
     }
 
     /**
-     * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
-     *
-     * @param inBuffer  the input ByteBuffer
-     * @param outBuffer the output ByteBuffer
-     * @return int number of bytes stored in {@code output}
-     * @throws ShortBufferException if there is insufficient space in the output
-     *                              buffer
+     * @param retVal the result value of error.
      */
-    @Override
-    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
-        final int[] outlen = new int[1];
-        throwOnError(OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, inBuffer.remaining()));
-        final int len = outlen[0];
-        inBuffer.position(inBuffer.limit());
-        outBuffer.position(outBuffer.position() + len);
-        return len;
+    private void throwOnError(final int retVal) {
+        if (retVal != 1) {
+            final NativeLong err = OpenSslNativeJna.ERR_peek_error();
+            final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);
+
+            if (context != null) {
+                OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
+            }
+            throw new IllegalStateException(
+                    "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
+        }
     }
 
     /**
@@ -181,70 +295,25 @@ final class OpenSslJnaCipher implements CryptoCipher {
     }
 
     /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation. The data is encrypted or decrypted, depending on how
-     * this cipher was initialized.
+     * Continues a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
      *
      * @param inBuffer  the input ByteBuffer
      * @param outBuffer the output ByteBuffer
      * @return int number of bytes stored in {@code output}
-     * @throws BadPaddingException       if this cipher is in decryption mode, and
-     *                                   (un)padding has been requested, but the
-     *                                   decrypted data is not bounded by the
-     *                                   appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *                                   padding has been requested (only in
-     *                                   encryption mode), and the total input
-     *                                   length of the data processed by this cipher
-     *                                   is not a multiple of block size; or if this
-     *                                   encryption algorithm is unable to process
-     *                                   the input data provided.
-     * @throws ShortBufferException      if the given output buffer is too small to
-     *                                   hold the result
+     * @throws ShortBufferException if there is insufficient space in the output
+     *                              buffer
      */
     @Override
-    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
-            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
-        final int uptLen = update(inBuffer, outBuffer);
+    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
         final int[] outlen = new int[1];
-        throwOnError(OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen));
-        final int len = uptLen + outlen[0];
-        outBuffer.position(outBuffer.position() + outlen[0]);
+        throwOnError(OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, inBuffer.remaining()));
+        final int len = outlen[0];
+        inBuffer.position(inBuffer.limit());
+        outBuffer.position(outBuffer.position() + len);
         return len;
     }
 
-    /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
-     *
-     * @param input        the input byte array
-     * @param inputOffset  the offset in input where the input starts
-     * @param inputLen     the input length
-     * @param output       the byte array for the result
-     * @param outputOffset the offset in output where the result is stored
-     * @return the number of bytes stored in output
-     * @throws ShortBufferException      if the given output byte array is too small
-     *                                   to hold the result
-     * @throws BadPaddingException       if this cipher is in decryption mode, and
-     *                                   (un)padding has been requested, but the
-     *                                   decrypted data is not bounded by the
-     *                                   appropriate padding bytes
-     * @throws IllegalBlockSizeException if this cipher is a block cipher, no
-     *                                   padding has been requested (only in
-     *                                   encryption mode), and the total input
-     *                                   length of the data processed by this cipher
-     *                                   is not a multiple of block size; or if this
-     *                                   encryption algorithm is unable to process
-     *                                   the input data provided.
-     */
-    @Override
-    public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
-            final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
-        final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
-        final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
-        return doFinal(inputBuf, outputBuf);
-    }
-
     /**
      * Continues a multi-part update of the Additional Authentication Data (AAD).
      * <p>
@@ -304,73 +373,4 @@ final class OpenSslJnaCipher implements CryptoCipher {
         // TODO: implement GCM mode using Jna
         throw new UnsupportedOperationException("This is unsupported in Jna Cipher");
     }
-
-    /**
-     * Closes the OpenSSL cipher. Clean the OpenSsl native context.
-     */
-    @Override
-    public void close() {
-        if (context != null) {
-            OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
-            // Freeing the context multiple times causes a JVM crash
-            // A work-round is to only free it at finalize time
-            // TODO is that sufficient?
-            // OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
-        }
-    }
-
-    /**
-     * @param retVal the result value of error.
-     */
-    private void throwOnError(final int retVal) {
-        if (retVal != 1) {
-            final NativeLong err = OpenSslNativeJna.ERR_peek_error();
-            final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);
-
-            if (context != null) {
-                OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
-            }
-            throw new IllegalStateException(
-                    "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
-        }
-    }
-
-    /**
-     * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding.
-     */
-    private enum AlgorithmMode {
-        AES_CTR, AES_CBC;
-
-        /**
-         * Gets the AlgorithmMode instance.
-         *
-         * @param algorithm the algorithm name
-         * @param mode      the mode name
-         * @return the AlgorithmMode instance
-         * @throws NoSuchAlgorithmException if the algorithm is not support
-         */
-        static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
-            try {
-                return AlgorithmMode.valueOf(algorithm + "_" + mode);
-            } catch (final Exception e) {
-                throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
-            }
-        }
-    }
-
-    @Override
-    public int getBlockSize() {
-        return CryptoCipherFactory.AES_BLOCK_SIZE;
-    }
-
-    @Override
-    public String getAlgorithm() {
-        return transformation;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
-        super.finalize();
-    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java
index f1ed19e..b420723 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandom.java
@@ -89,31 +89,6 @@ final class OpenSslJnaCryptoRandom implements CryptoRandom {
         }
     }
 
-    /**
-     * Generates a user-specified number of random bytes. It's thread-safe.
-     *
-     * @param bytes the array to be filled in with random bytes.
-     */
-    @Override
-    public void nextBytes(final byte[] bytes) {
-
-        synchronized (OpenSslJnaCryptoRandom.class) {
-            // this method is synchronized for now
-            // to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done
-
-            if (rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) {
-                close();
-                throw new IllegalStateException("rdrand should be used but default is detected");
-            }
-
-            final int byteLength = bytes.length;
-            final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength);
-            throwOnError(OpenSslNativeJna.RAND_bytes(buf, byteLength), false);
-            buf.rewind();
-            buf.get(bytes, 0, byteLength);
-        }
-    }
-
     /**
      * Overrides {@link java.lang.AutoCloseable#close()}. Closes OpenSSL context
      * if native enabled.
@@ -149,6 +124,31 @@ final class OpenSslJnaCryptoRandom implements CryptoRandom {
         return rdrandEnabled;
     }
 
+    /**
+     * Generates a user-specified number of random bytes. It's thread-safe.
+     *
+     * @param bytes the array to be filled in with random bytes.
+     */
+    @Override
+    public void nextBytes(final byte[] bytes) {
+
+        synchronized (OpenSslJnaCryptoRandom.class) {
+            // this method is synchronized for now
+            // to support multithreading https://wiki.openssl.org/index.php/Manual:Threads(3) needs to be done
+
+            if (rdrandEnabled && OpenSslNativeJna.RAND_get_rand_method().equals(OpenSslNativeJna.RAND_SSLeay())) {
+                close();
+                throw new IllegalStateException("rdrand should be used but default is detected");
+            }
+
+            final int byteLength = bytes.length;
+            final ByteBuffer buf = ByteBuffer.allocateDirect(byteLength);
+            throwOnError(OpenSslNativeJna.RAND_bytes(buf, byteLength), false);
+            buf.rewind();
+            buf.get(bytes, 0, byteLength);
+        }
+    }
+
     /**
      * @param retVal the result value of error.
      * @param closing true when called while closing.
diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java
index d00069e..ccd87a6 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslNativeJna.java
@@ -95,13 +95,14 @@ final class OpenSslNativeJna {
         OpenSslJna.debug("OpenSslNativeJna static init end");
     }
 
-    private OpenSslNativeJna() {
-    }
-
     public static PointerByReference ENGINE_by_id(final String string) {
         return JnaImplementation._ENGINE_by_id(string);
     }
 
+    public static int ENGINE_cleanup() {
+        return JnaImplementation._ENGINE_cleanup();
+    }
+
     public static int ENGINE_finish(final PointerByReference rdrandEngine) {
         return JnaImplementation._ENGINE_finish(rdrandEngine);
     }
@@ -114,6 +115,10 @@ final class OpenSslNativeJna {
         return JnaImplementation._ENGINE_init(rdrandEngine);
     }
 
+    public static void ENGINE_load_rdrand() {
+        JnaImplementation._ENGINE_load_rdrand();
+    }
+
     public static int ENGINE_set_default(final PointerByReference rdrandEngine, final int eNGINE_METHOD_RAND) {
         return JnaImplementation._ENGINE_set_default(rdrandEngine, eNGINE_METHOD_RAND);
     }
@@ -150,6 +155,10 @@ final class OpenSslNativeJna {
         return JnaImplementation._EVP_aes_256_ctr();
     }
 
+    public static void EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
+        JnaImplementation._EVP_CIPHER_CTX_cleanup(context);
+    }
+
     public static void EVP_CIPHER_CTX_free(final PointerByReference context) {
         JnaImplementation._EVP_CIPHER_CTX_free(context);
     }
@@ -177,6 +186,10 @@ final class OpenSslNativeJna {
         return JnaImplementation._EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, remaining);
     }
 
+    public static String OpenSSLVersion(final int i) {
+        return JnaImplementation._OpenSSL_version(i);
+    }
+
     public static int RAND_bytes(final ByteBuffer buf, final int length) {
         return JnaImplementation._RAND_bytes(buf, length);
     }
@@ -189,19 +202,6 @@ final class OpenSslNativeJna {
         return JnaImplementation._RAND_SSLeay();
     }
 
-    public static String OpenSSLVersion(final int i) {
-        return JnaImplementation._OpenSSL_version(i);
-    }
-
-    public static void ENGINE_load_rdrand() {
-        JnaImplementation._ENGINE_load_rdrand();
-    }
-
-    public static int ENGINE_cleanup() {
-        return JnaImplementation._ENGINE_cleanup();
-    }
-
-    public static void EVP_CIPHER_CTX_cleanup(final PointerByReference context) {
-        JnaImplementation._EVP_CIPHER_CTX_cleanup(context);
+    private OpenSslNativeJna() {
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java b/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java
index 312cb9f..cad051a 100644
--- a/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java
+++ b/src/main/java/org/apache/commons/crypto/random/CryptoRandomFactory.java
@@ -30,44 +30,6 @@ import org.apache.commons.crypto.utils.Utils;
  */
 public class CryptoRandomFactory {
 
-    // security random related configuration keys
-    /**
-     * The configuration key of the file path for secure random device.
-     */
-    public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX + "secure.random.device.file.path";
-
-    /**
-     * The default value ({@value}) of the file path for secure random device.
-     */
-    // Note: this is public mainly for use by the Javadoc
-    public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom";
-
-    /**
-     * The configuration key of the algorithm of secure random.
-     */
-    public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX + "secure.random.java.algorithm";
-
-    /**
-     * The default value ({@value}) of the algorithm of secure random.
-     */
-    // Note: this is public mainly for use by the Javadoc
-    public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG";
-
-    /**
-     * The configuration key of the CryptoRandom implementation class.
-     * <p>
-     * The value of the CLASSES_KEY needs to be the full name of a
-     * class that implements the
-     * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface
-     * The internal classes are listed in the enum
-     * {@link RandomProvider RandomProvider}
-     * which can be used to obtain the full class name.
-     * <p>
-     * The value can also be a comma-separated list of class names in
-     * order of descending priority.
-     */
-    public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "secure.random.classes";
-
     /**
      * Defines the internal CryptoRandom implementations.
      * <p>
@@ -142,6 +104,44 @@ public class CryptoRandomFactory {
         }
     }
 
+    // security random related configuration keys
+    /**
+     * The configuration key of the file path for secure random device.
+     */
+    public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX + "secure.random.device.file.path";
+
+    /**
+     * The default value ({@value}) of the file path for secure random device.
+     */
+    // Note: this is public mainly for use by the Javadoc
+    public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom";
+
+    /**
+     * The configuration key of the algorithm of secure random.
+     */
+    public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX + "secure.random.java.algorithm";
+
+    /**
+     * The default value ({@value}) of the algorithm of secure random.
+     */
+    // Note: this is public mainly for use by the Javadoc
+    public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG";
+
+    /**
+     * The configuration key of the CryptoRandom implementation class.
+     * <p>
+     * The value of the CLASSES_KEY needs to be the full name of a
+     * class that implements the
+     * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface
+     * The internal classes are listed in the enum
+     * {@link RandomProvider RandomProvider}
+     * which can be used to obtain the full class name.
+     * <p>
+     * The value can also be a comma-separated list of class names in
+     * order of descending priority.
+     */
+    public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "secure.random.classes";
+
     /**
      * The default value (OPENSSL,JAVA) used when creating a {@link org.apache.commons.crypto.cipher.CryptoCipher}.
      */
@@ -150,12 +150,6 @@ public class CryptoRandomFactory {
         .concat(",")
         .concat(RandomProvider.JAVA.getClassName());
 
-    /**
-     * The private constructor of {@link CryptoRandomFactory}.
-     */
-    private CryptoRandomFactory() {
-    }
-
     /**
      * Gets a CryptoRandom instance using the default implementation
      * as defined by {@link #CLASSES_DEFAULT}
@@ -227,4 +221,10 @@ public class CryptoRandomFactory {
         }
         return randomClassString;
     }
+
+    /**
+     * The private constructor of {@link CryptoRandomFactory}.
+     */
+    private CryptoRandomFactory() {
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java
index d81b7d1..ab7852c 100644
--- a/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java
+++ b/src/main/java/org/apache/commons/crypto/random/JavaCryptoRandom.java
@@ -58,17 +58,6 @@ final class JavaCryptoRandom implements CryptoRandom {
         // do nothing
     }
 
-    /**
-     * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes and places them into a user-supplied byte array. The number of random bytes
-     * produced is equal to the length of the byte array.
-     *
-     * @param bytes the array to be filled in with random bytes.
-     */
-    @Override
-    public void nextBytes(final byte[] bytes) {
-        instance.nextBytes(bytes);
-    }
-
     /**
      * Overrides Random#next(). Generates an integer containing the user-specified number of random bits(right justified, with leading zeros).
      *
@@ -81,4 +70,15 @@ final class JavaCryptoRandom implements CryptoRandom {
         // But, this should do.
         return instance.nextInt() >>> (Integer.SIZE - numBits);
     }
+
+    /**
+     * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes and places them into a user-supplied byte array. The number of random bytes
+     * produced is equal to the length of the byte array.
+     *
+     * @param bytes the array to be filled in with random bytes.
+     */
+    @Override
+    public void nextBytes(final byte[] bytes) {
+        instance.nextBytes(bytes);
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java
index 76d07ea..7a3e4ab 100644
--- a/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java
+++ b/src/main/java/org/apache/commons/crypto/random/OpenSslCryptoRandomNative.java
@@ -26,12 +26,6 @@ package org.apache.commons.crypto.random;
  */
 final class OpenSslCryptoRandomNative {
 
-    /**
-     * The private constructor of {@link OpenSslCryptoRandomNative}.
-     */
-    private OpenSslCryptoRandomNative() {
-    }
-
     /**
      * Declares a native method to initialize SR.
      */
@@ -46,4 +40,10 @@ final class OpenSslCryptoRandomNative {
      *         user-specified number of random bits.
      */
     public static native boolean nextRandBytes(byte[] bytes);
+
+    /**
+     * The private constructor of {@link OpenSslCryptoRandomNative}.
+     */
+    private OpenSslCryptoRandomNative() {
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java b/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java
index f79bb15..6e45923 100644
--- a/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java
+++ b/src/main/java/org/apache/commons/crypto/random/OsCryptoRandom.java
@@ -41,22 +41,6 @@ final class OsCryptoRandom implements CryptoRandom {
 
     private int pos = reservoir.length;
 
-    /**
-     * Fills the reservoir.
-     *
-     * @param min the length.
-     */
-    private void fillReservoir(final int min) {
-        if (pos >= reservoir.length - min) {
-            try {
-                IoUtils.readFully(stream, reservoir, 0, reservoir.length);
-            } catch (final IOException e) {
-                throw new IllegalStateException("failed to fill reservoir", e);
-            }
-            pos = 0;
-        }
-    }
-
     /**
      * Constructs a {@link OsCryptoRandom}.
      *
@@ -83,6 +67,33 @@ final class OsCryptoRandom implements CryptoRandom {
         }
     }
 
+    /**
+     * Overrides {@link java.lang.AutoCloseable#close()}. Closes the OS stream.
+     */
+    @Override
+    synchronized public void close() {
+        if (stream != null) {
+            IoUtils.closeQuietly(stream);
+            stream = null;
+        }
+    }
+
+    /**
+     * Fills the reservoir.
+     *
+     * @param min the length.
+     */
+    private void fillReservoir(final int min) {
+        if (pos >= reservoir.length - min) {
+            try {
+                IoUtils.readFully(stream, reservoir, 0, reservoir.length);
+            } catch (final IOException e) {
+                throw new IllegalStateException("failed to fill reservoir", e);
+            }
+            pos = 0;
+        }
+    }
+
     /**
      * Overrides {@link CryptoRandom#nextBytes(byte[])}. Generates random bytes
      * and places them into a user-supplied byte array. The number of random
@@ -103,15 +114,4 @@ final class OsCryptoRandom implements CryptoRandom {
         }
     }
 
-    /**
-     * Overrides {@link java.lang.AutoCloseable#close()}. Closes the OS stream.
-     */
-    @Override
-    synchronized public void close() {
-        if (stream != null) {
-            IoUtils.closeQuietly(stream);
-            stream = null;
-        }
-    }
-
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java
index 5c4b46e..1c422fd 100644
--- a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java
+++ b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java
@@ -51,14 +51,98 @@ import org.apache.commons.crypto.utils.Utils;
 
 public class CryptoInputStream extends InputStream implements ReadableByteChannel {
 
-    private final byte[] oneByteBuf = new byte[1];
-
     /**
      * The configuration key of the buffer size for stream.
      */
     public static final String STREAM_BUFFER_SIZE_KEY = Crypto.CONF_PREFIX
             + "stream.buffer.size";
 
+    // stream related configuration keys
+    /**
+     * The default value of the buffer size for stream.
+     */
+    private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192;
+
+    private static final int MIN_BUFFER_SIZE = 512;
+
+    /**
+     * The index value when the end of the stream has been reached {@code -1}.
+     *
+     * @since 1.1
+     */
+    public static final int EOS = -1;
+
+    /**
+     * Checks and floors buffer size.
+     *
+     * @param cipher the {@link CryptoCipher} instance.
+     * @param bufferSize the buffer size.
+     * @return the remaining buffer size.
+     */
+    static int checkBufferSize(final CryptoCipher cipher, final int bufferSize) {
+        Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE,
+                "Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + ".");
+        return bufferSize - bufferSize % cipher.getBlockSize();
+    }
+
+    /**
+     * Checks whether the cipher is supported streaming.
+     *
+     * @param cipher the {@link CryptoCipher} instance.
+     * @throws IOException if an I/O error occurs.
+     */
+    static void checkStreamCipher(final CryptoCipher cipher) throws IOException {
+        if (!cipher.getAlgorithm().equals(AES.CTR_NO_PADDING)) {
+            throw new IOException(AES.CTR_NO_PADDING + " is required");
+        }
+    }
+
+    /**
+     * Forcibly free the direct buffer.
+     *
+     * @param buffer the bytebuffer to be freed.
+     */
+    static void freeDirectBuffer(final ByteBuffer buffer) {
+        if (buffer != null) {
+            try {
+                /*
+                 * Using reflection to implement sun.nio.ch.DirectBuffer.cleaner() .clean();
+                 */
+                final String SUN_CLASS = "sun.nio.ch.DirectBuffer";
+                final Class<?>[] interfaces = buffer.getClass().getInterfaces();
+                final Object[] EMPTY_OBJECT_ARRAY = {};
+
+                for (final Class<?> clazz : interfaces) {
+                    if (clazz.getName().equals(SUN_CLASS)) {
+                        /* DirectBuffer#cleaner() */
+                        final Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner");
+                        final Object cleaner = getCleaner.invoke(buffer, EMPTY_OBJECT_ARRAY);
+                        /* Cleaner#clean() */
+                        final Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
+                        cleanMethod.invoke(cleaner, EMPTY_OBJECT_ARRAY);
+                        return;
+                    }
+                }
+            } catch (final ReflectiveOperationException e) { // NOPMD
+                // Ignore the Reflection exception.
+            }
+        }
+    }
+
+    /**
+     * Reads crypto buffer size.
+     *
+     * @param props The {@code Properties} class represents a set of
+     *        properties.
+     * @return the buffer size.
+     * */
+    static int getBufferSize(final Properties props) {
+        final String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, "");
+        return bufferSizeStr.isEmpty() ? CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT : Integer.parseInt(bufferSizeStr);
+    }
+
+    private final byte[] oneByteBuf = new byte[1];
+
     /** The CryptoCipher instance. */
     final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further
 
@@ -94,20 +178,70 @@ public class CryptoInputStream extends InputStream implements ReadableByteChanne
      */
     ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further
 
-    // stream related configuration keys
     /**
-     * The default value of the buffer size for stream.
+     * Constructs a {@link CryptoInputStream}.
+     *
+     * @param input the input data.
+     * @param cipher the cipher instance.
+     * @param bufferSize the bufferSize.
+     * @param key crypto key for the cipher.
+     * @param params the algorithm parameters.
+     * @throws IOException if an I/O error occurs.
      */
-    private static final int STREAM_BUFFER_SIZE_DEFAULT = 8192;
+    protected CryptoInputStream(final Input input, final CryptoCipher cipher, final int bufferSize,
+            final Key key, final AlgorithmParameterSpec params) throws IOException {
+        this.input = input;
+        this.cipher = cipher;
+        this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
 
-    private static final int MIN_BUFFER_SIZE = 512;
+        this.key = key;
+        this.params = params;
+        if (!(params instanceof IvParameterSpec)) {
+            // other AlgorithmParameterSpec such as GCMParameterSpec is not
+            // supported now.
+            throw new IOException("Illegal parameters");
+        }
+
+        inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
+        outBuffer = ByteBuffer.allocateDirect(this.bufferSize + cipher.getBlockSize());
+        outBuffer.limit(0);
+
+        initCipher();
+    }
 
     /**
-     * The index value when the end of the stream has been reached {@code -1}.
+     * Constructs a {@link CryptoInputStream}.
      *
-     * @since 1.1
+     * @param cipher the cipher instance.
+     * @param inputStream the input stream.
+     * @param bufferSize the bufferSize.
+     * @param key crypto key for the cipher.
+     * @param params the algorithm parameters.
+     * @throws IOException if an I/O error occurs.
      */
-    public static final int EOS = -1;
+    @SuppressWarnings("resource") // Closing the instance closes the StreamInput
+    protected CryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
+            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
+            throws IOException {
+        this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, params);
+    }
+
+    /**
+     * Constructs a {@link CryptoInputStream}.
+     *
+     * @param channel the ReadableByteChannel instance.
+     * @param cipher the cipher instance.
+     * @param bufferSize the bufferSize.
+     * @param key crypto key for the cipher.
+     * @param params the algorithm parameters.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("resource") // Closing the instance closes the ChannelInput
+    protected CryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
+            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
+            throws IOException {
+        this(new ChannelInput(channel), cipher, bufferSize, key, params);
+    }
 
     /**
      * Constructs a {@link CryptoInputStream}.
@@ -154,284 +288,166 @@ public class CryptoInputStream extends InputStream implements ReadableByteChanne
     }
 
     /**
-     * Constructs a {@link CryptoInputStream}.
+     * Overrides the {@link InputStream#available()}. Returns an estimate of the
+     * number of bytes that can be read (or skipped over) from this input stream
+     * without blocking by the next invocation of a method for this input
+     * stream.
      *
-     * @param cipher the cipher instance.
-     * @param inputStream the input stream.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param params the algorithm parameters.
+     * @return an estimate of the number of bytes that can be read (or skipped
+     *         over) from this input stream without blocking or {@code 0} when
+     *         it reaches the end of the input stream.
      * @throws IOException if an I/O error occurs.
      */
-    @SuppressWarnings("resource") // Closing the instance closes the StreamInput
-    protected CryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
-            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
-            throws IOException {
-        this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, params);
+    @Override
+    public int available() throws IOException {
+        checkStream();
+
+        return input.available() + outBuffer.remaining();
     }
 
     /**
-     * Constructs a {@link CryptoInputStream}.
+     * Checks whether the stream is closed.
      *
-     * @param channel the ReadableByteChannel instance.
-     * @param cipher the cipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param params the algorithm parameters.
      * @throws IOException if an I/O error occurs.
      */
-    @SuppressWarnings("resource") // Closing the instance closes the ChannelInput
-    protected CryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
-            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
-            throws IOException {
-        this(new ChannelInput(channel), cipher, bufferSize, key, params);
+    protected void checkStream() throws IOException {
+        if (closed) {
+            throw new IOException("Stream closed");
+        }
     }
 
     /**
-     * Constructs a {@link CryptoInputStream}.
+     * Overrides the {@link InputStream#close()}. Closes this input stream and
+     * releases any system resources associated with the stream.
      *
-     * @param input the input data.
-     * @param cipher the cipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param params the algorithm parameters.
      * @throws IOException if an I/O error occurs.
      */
-    protected CryptoInputStream(final Input input, final CryptoCipher cipher, final int bufferSize,
-            final Key key, final AlgorithmParameterSpec params) throws IOException {
-        this.input = input;
-        this.cipher = cipher;
-        this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
-
-        this.key = key;
-        this.params = params;
-        if (!(params instanceof IvParameterSpec)) {
-            // other AlgorithmParameterSpec such as GCMParameterSpec is not
-            // supported now.
-            throw new IOException("Illegal parameters");
+    @Override
+    public void close() throws IOException {
+        if (closed) {
+            return;
         }
 
-        inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
-        outBuffer = ByteBuffer.allocateDirect(this.bufferSize + cipher.getBlockSize());
-        outBuffer.limit(0);
-
-        initCipher();
+        input.close();
+        freeBuffers();
+        cipher.close();
+        super.close();
+        closed = true;
     }
 
     /**
-     * Overrides the {@link java.io.InputStream#read()}. Reads the next byte of
-     * data from the input stream.
+     * Does the decryption using inBuffer as input and outBuffer as output. Upon
+     * return, inBuffer is cleared; the decrypted data starts at
+     * outBuffer.position() and ends at outBuffer.limit().
      *
-     * @return the next byte of data, or {@code EOS (-1)} if the end of the
-     *         stream is reached.
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public int read() throws IOException {
-        int n;
-        while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD
-            /* no op */
+    protected void decrypt() throws IOException {
+        // Prepare the input buffer and clear the out buffer
+        inBuffer.flip();
+        outBuffer.clear();
+
+        try {
+            cipher.update(inBuffer, outBuffer);
+        } catch (final ShortBufferException e) {
+            throw new IOException(e);
         }
-        return n == EOS ? EOS : oneByteBuf[0] & 0xff;
+
+        // Clear the input buffer and prepare out buffer
+        inBuffer.clear();
+        outBuffer.flip();
     }
 
     /**
-     * Overrides the {@link java.io.InputStream#read(byte[], int, int)}.
-     * Decryption is buffer based. If there is data in {@link #outBuffer}, then
-     * read it out of this buffer. If there is no data in {@link #outBuffer},
-     * then read more from the underlying stream and do the decryption.
+     * Does final of the cipher to end the decrypting stream.
      *
-     * @param array the buffer into which the decrypted data is read.
-     * @param off the buffer offset.
-     * @param len the maximum number of decrypted data bytes to read.
-     * @return int the total number of decrypted data bytes read into the
-     *         buffer.
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public int read(final byte[] array, final int off, final int len) throws IOException {
-        checkStream();
-        Objects.requireNonNull(array, "array");
-        if (off < 0 || len < 0 || len > array.length - off) {
-            throw new IndexOutOfBoundsException();
-        }
-        if (len == 0) {
-            return 0;
-        }
+    protected void decryptFinal() throws IOException {
+        // Prepare the input buffer and clear the out buffer
+        inBuffer.flip();
+        outBuffer.clear();
 
-        final int remaining = outBuffer.remaining();
-        if (remaining > 0) {
-            // Satisfy the read with the existing data
-            final int n = Math.min(len, remaining);
-            outBuffer.get(array, off, n);
-            return n;
-        }
-        // No data in the out buffer, try read new data and decrypt it
-        // we loop for new data
-        int nd = 0;
-        while (nd == 0) {
-            nd = decryptMore();
-        }
-        if (nd < 0) {
-            return nd;
+        try {
+            cipher.doFinal(inBuffer, outBuffer);
+            finalDone = true;
+        } catch (final ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
+            throw new IOException(e);
         }
 
-        final int n = Math.min(len, outBuffer.remaining());
-        outBuffer.get(array, off, n);
-        return n;
+        // Clear the input buffer and prepare out buffer
+        inBuffer.clear();
+        outBuffer.flip();
     }
 
     /**
-     * Overrides the {@link java.io.InputStream#skip(long)}. Skips over and
-     * discards {@code n} bytes of data from this input stream.
+     * Decrypts more data by reading the under layer stream. The decrypted data
+     * will be put in the output buffer. If the end of the under stream reached,
+     * we will do final of the cipher to finish all the decrypting of data.
      *
-     * @param n the number of bytes to be skipped.
-     * @return the actual number of bytes skipped.
+     * @return The number of decrypted data.
+     *           return -1 (if end of the decrypted stream)
+     *           return 0 (no data now, but could have more later)
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public long skip(final long n) throws IOException {
-        Utils.checkArgument(n >= 0, "Negative skip length.");
-        checkStream();
-
-        if (n == 0) {
-            return 0;
+    protected int decryptMore() throws IOException {
+        if (finalDone) {
+            return EOS;
         }
 
-        long remaining = n;
-        int nd;
-
-        while (remaining > 0) {
-            if (remaining <= outBuffer.remaining()) {
-                // Skip in the remaining buffer
-                final int pos = outBuffer.position() + (int) remaining;
-                outBuffer.position(pos);
+        final int n = input.read(inBuffer);
+        if (n < 0) {
+            // The stream is end, finalize the cipher stream
+            decryptFinal();
 
-                remaining = 0;
-                break;
+            // Satisfy the read with the remaining
+            final int remaining = outBuffer.remaining();
+            if (remaining > 0) {
+                return remaining;
             }
-            remaining -= outBuffer.remaining();
-            outBuffer.clear();
 
-            // we loop for new data
-            nd = 0;
-            while (nd == 0) {
-                nd = decryptMore();
-            }
-            if (nd < 0) {
-                break;
-            }
+            // End of the stream
+            return EOS;
         }
-
-        return n - remaining;
-    }
-
-    /**
-     * Overrides the {@link InputStream#available()}. Returns an estimate of the
-     * number of bytes that can be read (or skipped over) from this input stream
-     * without blocking by the next invocation of a method for this input
-     * stream.
-     *
-     * @return an estimate of the number of bytes that can be read (or skipped
-     *         over) from this input stream without blocking or {@code 0} when
-     *         it reaches the end of the input stream.
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public int available() throws IOException {
-        checkStream();
-
-        return input.available() + outBuffer.remaining();
-    }
-
-    /**
-     * Overrides the {@link InputStream#close()}. Closes this input stream and
-     * releases any system resources associated with the stream.
-     *
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public void close() throws IOException {
-        if (closed) {
-            return;
+        if (n == 0) {
+            // No data is read, but the stream is not end yet
+            return 0;
         }
-
-        input.close();
-        freeBuffers();
-        cipher.close();
-        super.close();
-        closed = true;
+        decrypt();
+        return outBuffer.remaining();
     }
 
-    /**
-     * Overrides the {@link InputStream#markSupported()}.
-     *
-     * @return false,the {@link CtrCryptoInputStream} don't support the mark
-     *         method.
-     */
-    @Override
-    public boolean markSupported() {
-        return false;
+    /** Forcibly free the direct buffers. */
+    protected void freeBuffers() {
+        CryptoInputStream.freeDirectBuffer(inBuffer);
+        CryptoInputStream.freeDirectBuffer(outBuffer);
     }
 
     /**
-     * Overrides the {@link java.nio.channels.Channel#isOpen()}.
+     * Gets the buffer size.
      *
-     * @return {@code true} if, and only if, this channel is open.
+     * @return the bufferSize.
      */
-    @Override
-    public boolean isOpen() {
-        return !closed;
+    protected int getBufferSize() {
+        return bufferSize;
     }
 
     /**
-     * Overrides the
-     * {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a
-     * sequence of bytes from this channel into the given buffer.
+     * Gets the internal CryptoCipher.
      *
-     * @param dst The buffer into which bytes are to be transferred.
-     * @return The number of bytes read, possibly zero, or {@code EOS (-1)} if the
-     *         channel has reached end-of-stream.
-     * @throws IOException if an I/O error occurs.
+     * @return the cipher instance.
      */
-    @Override
-    public int read(final ByteBuffer dst) throws IOException {
-        checkStream();
-        int remaining = outBuffer.remaining();
-        if (remaining <= 0) {
-            // Decrypt more data
-            // we loop for new data
-            int nd = 0;
-            while (nd == 0) {
-                nd = decryptMore();
-            }
-
-            if (nd < 0) {
-                return EOS;
-            }
-        }
-
-        // Copy decrypted data from outBuffer to dst
-        remaining = outBuffer.remaining();
-        final int toRead = dst.remaining();
-        if (toRead <= remaining) {
-            final int limit = outBuffer.limit();
-            outBuffer.limit(outBuffer.position() + toRead);
-            dst.put(outBuffer);
-            outBuffer.limit(limit);
-            return toRead;
-        }
-        dst.put(outBuffer);
-        return remaining;
+    protected CryptoCipher getCipher() {
+        return cipher;
     }
 
     /**
-     * Gets the buffer size.
+     * Gets the input.
      *
-     * @return the bufferSize.
+     * @return the input.
      */
-    protected int getBufferSize() {
-        return bufferSize;
+    protected Input getInput() {
+        return input;
     }
 
     /**
@@ -443,15 +459,6 @@ public class CryptoInputStream extends InputStream implements ReadableByteChanne
         return key;
     }
 
-    /**
-     * Gets the internal CryptoCipher.
-     *
-     * @return the cipher instance.
-     */
-    protected CryptoCipher getCipher() {
-        return cipher;
-    }
-
     /**
      * Gets the specification of cryptographic parameters.
      *
@@ -461,15 +468,6 @@ public class CryptoInputStream extends InputStream implements ReadableByteChanne
         return params;
     }
 
-    /**
-     * Gets the input.
-     *
-     * @return the input.
-     */
-    protected Input getInput() {
-        return input;
-    }
-
     /**
      * Initializes the cipher.
      *
@@ -484,170 +482,172 @@ public class CryptoInputStream extends InputStream implements ReadableByteChanne
     }
 
     /**
-     * Decrypts more data by reading the under layer stream. The decrypted data
-     * will be put in the output buffer. If the end of the under stream reached,
-     * we will do final of the cipher to finish all the decrypting of data.
+     * Overrides the {@link java.nio.channels.Channel#isOpen()}.
      *
-     * @return The number of decrypted data.
-     *           return -1 (if end of the decrypted stream)
-     *           return 0 (no data now, but could have more later)
-     * @throws IOException if an I/O error occurs.
+     * @return {@code true} if, and only if, this channel is open.
      */
-    protected int decryptMore() throws IOException {
-        if (finalDone) {
-            return EOS;
-        }
-
-        final int n = input.read(inBuffer);
-        if (n < 0) {
-            // The stream is end, finalize the cipher stream
-            decryptFinal();
-
-            // Satisfy the read with the remaining
-            final int remaining = outBuffer.remaining();
-            if (remaining > 0) {
-                return remaining;
-            }
-
-            // End of the stream
-            return EOS;
-        }
-        if (n == 0) {
-            // No data is read, but the stream is not end yet
-            return 0;
-        }
-        decrypt();
-        return outBuffer.remaining();
+    @Override
+    public boolean isOpen() {
+        return !closed;
     }
 
     /**
-     * Does the decryption using inBuffer as input and outBuffer as output. Upon
-     * return, inBuffer is cleared; the decrypted data starts at
-     * outBuffer.position() and ends at outBuffer.limit().
+     * Overrides the {@link InputStream#markSupported()}.
      *
-     * @throws IOException if an I/O error occurs.
+     * @return false,the {@link CtrCryptoInputStream} don't support the mark
+     *         method.
      */
-    protected void decrypt() throws IOException {
-        // Prepare the input buffer and clear the out buffer
-        inBuffer.flip();
-        outBuffer.clear();
-
-        try {
-            cipher.update(inBuffer, outBuffer);
-        } catch (final ShortBufferException e) {
-            throw new IOException(e);
-        }
-
-        // Clear the input buffer and prepare out buffer
-        inBuffer.clear();
-        outBuffer.flip();
+    @Override
+    public boolean markSupported() {
+        return false;
     }
 
     /**
-     * Does final of the cipher to end the decrypting stream.
+     * Overrides the {@link java.io.InputStream#read()}. Reads the next byte of
+     * data from the input stream.
      *
+     * @return the next byte of data, or {@code EOS (-1)} if the end of the
+     *         stream is reached.
      * @throws IOException if an I/O error occurs.
      */
-    protected void decryptFinal() throws IOException {
-        // Prepare the input buffer and clear the out buffer
-        inBuffer.flip();
-        outBuffer.clear();
-
-        try {
-            cipher.doFinal(inBuffer, outBuffer);
-            finalDone = true;
-        } catch (final ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
-            throw new IOException(e);
+    @Override
+    public int read() throws IOException {
+        int n;
+        while ((n = read(oneByteBuf, 0, 1)) == 0) { //NOPMD
+            /* no op */
         }
-
-        // Clear the input buffer and prepare out buffer
-        inBuffer.clear();
-        outBuffer.flip();
+        return n == EOS ? EOS : oneByteBuf[0] & 0xff;
     }
 
     /**
-     * Checks whether the stream is closed.
+     * Overrides the {@link java.io.InputStream#read(byte[], int, int)}.
+     * Decryption is buffer based. If there is data in {@link #outBuffer}, then
+     * read it out of this buffer. If there is no data in {@link #outBuffer},
+     * then read more from the underlying stream and do the decryption.
      *
+     * @param array the buffer into which the decrypted data is read.
+     * @param off the buffer offset.
+     * @param len the maximum number of decrypted data bytes to read.
+     * @return int the total number of decrypted data bytes read into the
+     *         buffer.
      * @throws IOException if an I/O error occurs.
      */
-    protected void checkStream() throws IOException {
-        if (closed) {
-            throw new IOException("Stream closed");
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        checkStream();
+        Objects.requireNonNull(array, "array");
+        if (off < 0 || len < 0 || len > array.length - off) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (len == 0) {
+            return 0;
         }
-    }
 
-    /** Forcibly free the direct buffers. */
-    protected void freeBuffers() {
-        CryptoInputStream.freeDirectBuffer(inBuffer);
-        CryptoInputStream.freeDirectBuffer(outBuffer);
+        final int remaining = outBuffer.remaining();
+        if (remaining > 0) {
+            // Satisfy the read with the existing data
+            final int n = Math.min(len, remaining);
+            outBuffer.get(array, off, n);
+            return n;
+        }
+        // No data in the out buffer, try read new data and decrypt it
+        // we loop for new data
+        int nd = 0;
+        while (nd == 0) {
+            nd = decryptMore();
+        }
+        if (nd < 0) {
+            return nd;
+        }
+
+        final int n = Math.min(len, outBuffer.remaining());
+        outBuffer.get(array, off, n);
+        return n;
     }
 
     /**
-     * Forcibly free the direct buffer.
+     * Overrides the
+     * {@link java.nio.channels.ReadableByteChannel#read(ByteBuffer)}. Reads a
+     * sequence of bytes from this channel into the given buffer.
      *
-     * @param buffer the bytebuffer to be freed.
+     * @param dst The buffer into which bytes are to be transferred.
+     * @return The number of bytes read, possibly zero, or {@code EOS (-1)} if the
+     *         channel has reached end-of-stream.
+     * @throws IOException if an I/O error occurs.
      */
-    static void freeDirectBuffer(final ByteBuffer buffer) {
-        if (buffer != null) {
-            try {
-                /*
-                 * Using reflection to implement sun.nio.ch.DirectBuffer.cleaner() .clean();
-                 */
-                final String SUN_CLASS = "sun.nio.ch.DirectBuffer";
-                final Class<?>[] interfaces = buffer.getClass().getInterfaces();
-                final Object[] EMPTY_OBJECT_ARRAY = {};
+    @Override
+    public int read(final ByteBuffer dst) throws IOException {
+        checkStream();
+        int remaining = outBuffer.remaining();
+        if (remaining <= 0) {
+            // Decrypt more data
+            // we loop for new data
+            int nd = 0;
+            while (nd == 0) {
+                nd = decryptMore();
+            }
 
-                for (final Class<?> clazz : interfaces) {
-                    if (clazz.getName().equals(SUN_CLASS)) {
-                        /* DirectBuffer#cleaner() */
-                        final Method getCleaner = Class.forName(SUN_CLASS).getMethod("cleaner");
-                        final Object cleaner = getCleaner.invoke(buffer, EMPTY_OBJECT_ARRAY);
-                        /* Cleaner#clean() */
-                        final Method cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
-                        cleanMethod.invoke(cleaner, EMPTY_OBJECT_ARRAY);
-                        return;
-                    }
-                }
-            } catch (final ReflectiveOperationException e) { // NOPMD
-                // Ignore the Reflection exception.
+            if (nd < 0) {
+                return EOS;
             }
         }
-    }
 
-    /**
-     * Reads crypto buffer size.
-     *
-     * @param props The {@code Properties} class represents a set of
-     *        properties.
-     * @return the buffer size.
-     * */
-    static int getBufferSize(final Properties props) {
-        final String bufferSizeStr = props.getProperty(CryptoInputStream.STREAM_BUFFER_SIZE_KEY, "");
-        return bufferSizeStr.isEmpty() ? CryptoInputStream.STREAM_BUFFER_SIZE_DEFAULT : Integer.parseInt(bufferSizeStr);
+        // Copy decrypted data from outBuffer to dst
+        remaining = outBuffer.remaining();
+        final int toRead = dst.remaining();
+        if (toRead <= remaining) {
+            final int limit = outBuffer.limit();
+            outBuffer.limit(outBuffer.position() + toRead);
+            dst.put(outBuffer);
+            outBuffer.limit(limit);
+            return toRead;
+        }
+        dst.put(outBuffer);
+        return remaining;
     }
 
     /**
-     * Checks whether the cipher is supported streaming.
+     * Overrides the {@link java.io.InputStream#skip(long)}. Skips over and
+     * discards {@code n} bytes of data from this input stream.
      *
-     * @param cipher the {@link CryptoCipher} instance.
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
      * @throws IOException if an I/O error occurs.
      */
-    static void checkStreamCipher(final CryptoCipher cipher) throws IOException {
-        if (!cipher.getAlgorithm().equals(AES.CTR_NO_PADDING)) {
-            throw new IOException(AES.CTR_NO_PADDING + " is required");
+    @Override
+    public long skip(final long n) throws IOException {
+        Utils.checkArgument(n >= 0, "Negative skip length.");
+        checkStream();
+
+        if (n == 0) {
+            return 0;
         }
-    }
 
-    /**
-     * Checks and floors buffer size.
-     *
-     * @param cipher the {@link CryptoCipher} instance.
-     * @param bufferSize the buffer size.
-     * @return the remaining buffer size.
-     */
-    static int checkBufferSize(final CryptoCipher cipher, final int bufferSize) {
-        Utils.checkArgument(bufferSize >= CryptoInputStream.MIN_BUFFER_SIZE,
-                "Minimum value of buffer size is " + CryptoInputStream.MIN_BUFFER_SIZE + ".");
-        return bufferSize - bufferSize % cipher.getBlockSize();
+        long remaining = n;
+        int nd;
+
+        while (remaining > 0) {
+            if (remaining <= outBuffer.remaining()) {
+                // Skip in the remaining buffer
+                final int pos = outBuffer.position() + (int) remaining;
+                outBuffer.position(pos);
+
+                remaining = 0;
+                break;
+            }
+            remaining -= outBuffer.remaining();
+            outBuffer.clear();
+
+            // we loop for new data
+            nd = 0;
+            while (nd == 0) {
+                nd = decryptMore();
+            }
+            if (nd < 0) {
+                break;
+            }
+        }
+
+        return n - remaining;
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java
index c2e49de..2c5fd92 100644
--- a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java
+++ b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java
@@ -82,6 +82,57 @@ public class CryptoOutputStream extends OutputStream implements
      */
     ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further
 
+    /**
+     * Constructs a {@link CryptoOutputStream}.
+     *
+     * @param output the output stream.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
+     * @param key crypto key for the cipher.
+     * @param params the algorithm parameters.
+     * @throws IOException if an I/O error occurs.
+     */
+    protected CryptoOutputStream(final Output output, final CryptoCipher cipher,
+            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
+            throws IOException {
+
+        this.output = output;
+        this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
+        this.cipher = cipher;
+
+        this.key = key;
+        this.params = params;
+
+        if (!(params instanceof IvParameterSpec)) {
+            // other AlgorithmParameterSpec such as GCMParameterSpec is not
+            // supported now.
+            throw new IOException("Illegal parameters");
+        }
+
+        inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
+        outBuffer = ByteBuffer.allocateDirect(this.bufferSize
+                + cipher.getBlockSize());
+
+        initCipher();
+    }
+
+    /**
+     * Constructs a {@link CryptoOutputStream}.
+     *
+     * @param outputStream the output stream.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
+     * @param key crypto key for the cipher.
+     * @param params the algorithm parameters.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
+    protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
+            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
+            throws IOException {
+        this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params);
+    }
+
     /**
      * Constructs a {@link CryptoOutputStream}.
      *
@@ -128,23 +179,6 @@ public class CryptoOutputStream extends OutputStream implements
 
     }
 
-    /**
-     * Constructs a {@link CryptoOutputStream}.
-     *
-     * @param outputStream the output stream.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param params the algorithm parameters.
-     * @throws IOException if an I/O error occurs.
-     */
-    @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
-    protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
-            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
-            throws IOException {
-        this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params);
-    }
-
     /**
      * Constructs a {@link CryptoOutputStream}.
      *
@@ -163,101 +197,16 @@ public class CryptoOutputStream extends OutputStream implements
     }
 
     /**
-     * Constructs a {@link CryptoOutputStream}.
-     *
-     * @param output the output stream.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param params the algorithm parameters.
-     * @throws IOException if an I/O error occurs.
-     */
-    protected CryptoOutputStream(final Output output, final CryptoCipher cipher,
-            final int bufferSize, final Key key, final AlgorithmParameterSpec params)
-            throws IOException {
-
-        this.output = output;
-        this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
-        this.cipher = cipher;
-
-        this.key = key;
-        this.params = params;
-
-        if (!(params instanceof IvParameterSpec)) {
-            // other AlgorithmParameterSpec such as GCMParameterSpec is not
-            // supported now.
-            throw new IOException("Illegal parameters");
-        }
-
-        inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
-        outBuffer = ByteBuffer.allocateDirect(this.bufferSize
-                + cipher.getBlockSize());
-
-        initCipher();
-    }
-
-    /**
-     * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the
-     * specified byte to this output stream.
-     *
-     * @param b the data.
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public void write(final int b) throws IOException {
-        oneByteBuf[0] = (byte) (b & 0xff);
-        write(oneByteBuf, 0, oneByteBuf.length);
-    }
-
-    /**
-     * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}.
-     * Encryption is buffer based. If there is enough room in {@link #inBuffer},
-     * then write to this buffer. If {@link #inBuffer} is full, then do
-     * encryption and write data to the underlying stream.
+     * Checks whether the stream is closed.
      *
-     * @param array the data.
-     * @param off the start offset in the data.
-     * @param len the number of bytes to write.
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public void write(final byte[] array, int off, int len) throws IOException {
-        checkStream();
-        Objects.requireNonNull(array, "array");
-        final int arrayLength = array.length;
-        if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) {
-            throw new IndexOutOfBoundsException();
-        }
-
-        while (len > 0) {
-            final int remaining = inBuffer.remaining();
-            if (len < remaining) {
-                inBuffer.put(array, off, len);
-                len = 0;
-            } else {
-                inBuffer.put(array, off, remaining);
-                off += remaining;
-                len -= remaining;
-                encrypt();
-            }
+    protected void checkStream() throws IOException {
+        if (closed) {
+            throw new IOException("Stream closed");
         }
     }
 
-    /**
-     * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt
-     * the data in the buffer and write to the underlying stream, then do the
-     * flush.
-     *
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public void flush() throws IOException {
-        checkStream();
-        encrypt();
-        output.flush();
-        super.flush();
-    }
-
     /**
      * Overrides the {@link OutputStream#close()}. Closes this output stream and
      * releases any system resources associated with this stream.
@@ -281,68 +230,6 @@ public class CryptoOutputStream extends OutputStream implements
         }
     }
 
-    /**
-     * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel
-     * is open.
-     *
-     * @return {@code true} if, and only if, this channel is open
-     */
-    @Override
-    public boolean isOpen() {
-        return !closed;
-    }
-
-    /**
-     * Overrides the
-     * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a
-     * sequence of bytes to this channel from the given buffer.
-     *
-     * @param src The buffer from which bytes are to be retrieved.
-     * @return The number of bytes written, possibly zero.
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public int write(final ByteBuffer src) throws IOException {
-        checkStream();
-        final int len = src.remaining();
-        int remaining = len;
-        while (remaining > 0) {
-            final int space = inBuffer.remaining();
-            if (remaining < space) {
-                inBuffer.put(src);
-                remaining = 0;
-            } else {
-                // to void copy twice, we set the limit to copy directly
-                final int oldLimit = src.limit();
-                final int newLimit = src.position() + space;
-                src.limit(newLimit);
-
-                inBuffer.put(src);
-
-                // restore the old limit
-                src.limit(oldLimit);
-
-                remaining -= space;
-                encrypt();
-            }
-        }
-
-        return len;
-    }
-
-    /**
-     * Initializes the cipher.
-     *
-     * @throws IOException if an I/O error occurs.
-     */
-    protected void initCipher() throws IOException {
-        try {
-            cipher.init(Cipher.ENCRYPT_MODE, key, params);
-        } catch (final GeneralSecurityException e) {
-            throw new IOException(e);
-        }
-    }
-
     /**
      * Does the encryption, input is {@link #inBuffer} and output is
      * {@link #outBuffer}.
@@ -394,14 +281,18 @@ public class CryptoOutputStream extends OutputStream implements
     }
 
     /**
-     * Checks whether the stream is closed.
+     * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt
+     * the data in the buffer and write to the underlying stream, then do the
+     * flush.
      *
      * @throws IOException if an I/O error occurs.
      */
-    protected void checkStream() throws IOException {
-        if (closed) {
-            throw new IOException("Stream closed");
-        }
+    @Override
+    public void flush() throws IOException {
+        checkStream();
+        encrypt();
+        output.flush();
+        super.flush();
     }
 
     /** Forcibly free the direct buffers. */
@@ -411,12 +302,12 @@ public class CryptoOutputStream extends OutputStream implements
     }
 
     /**
-     * Gets the outBuffer.
+     * Gets the buffer size.
      *
-     * @return the outBuffer.
+     * @return the buffer size.
      */
-    protected ByteBuffer getOutBuffer() {
-        return outBuffer;
+    protected int getBufferSize() {
+        return bufferSize;
     }
 
     /**
@@ -429,20 +320,129 @@ public class CryptoOutputStream extends OutputStream implements
     }
 
     /**
-     * Gets the buffer size.
+     * Gets the inBuffer.
      *
-     * @return the buffer size.
+     * @return the inBuffer.
      */
-    protected int getBufferSize() {
-        return bufferSize;
+    protected ByteBuffer getInBuffer() {
+        return inBuffer;
     }
 
     /**
-     * Gets the inBuffer.
+     * Gets the outBuffer.
      *
-     * @return the inBuffer.
+     * @return the outBuffer.
      */
-    protected ByteBuffer getInBuffer() {
-        return inBuffer;
+    protected ByteBuffer getOutBuffer() {
+        return outBuffer;
+    }
+
+    /**
+     * Initializes the cipher.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    protected void initCipher() throws IOException {
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        } catch (final GeneralSecurityException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel
+     * is open.
+     *
+     * @return {@code true} if, and only if, this channel is open
+     */
+    @Override
+    public boolean isOpen() {
+        return !closed;
+    }
+
+    /**
+     * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}.
+     * Encryption is buffer based. If there is enough room in {@link #inBuffer},
+     * then write to this buffer. If {@link #inBuffer} is full, then do
+     * encryption and write data to the underlying stream.
+     *
+     * @param array the data.
+     * @param off the start offset in the data.
+     * @param len the number of bytes to write.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public void write(final byte[] array, int off, int len) throws IOException {
+        checkStream();
+        Objects.requireNonNull(array, "array");
+        final int arrayLength = array.length;
+        if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        while (len > 0) {
+            final int remaining = inBuffer.remaining();
+            if (len < remaining) {
+                inBuffer.put(array, off, len);
+                len = 0;
+            } else {
+                inBuffer.put(array, off, remaining);
+                off += remaining;
+                len -= remaining;
+                encrypt();
+            }
+        }
+    }
+
+    /**
+     * Overrides the
+     * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a
+     * sequence of bytes to this channel from the given buffer.
+     *
+     * @param src The buffer from which bytes are to be retrieved.
+     * @return The number of bytes written, possibly zero.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public int write(final ByteBuffer src) throws IOException {
+        checkStream();
+        final int len = src.remaining();
+        int remaining = len;
+        while (remaining > 0) {
+            final int space = inBuffer.remaining();
+            if (remaining < space) {
+                inBuffer.put(src);
+                remaining = 0;
+            } else {
+                // to void copy twice, we set the limit to copy directly
+                final int oldLimit = src.limit();
+                final int newLimit = src.position() + space;
+                src.limit(newLimit);
+
+                inBuffer.put(src);
+
+                // restore the old limit
+                src.limit(oldLimit);
+
+                remaining -= space;
+                encrypt();
+            }
+        }
+
+        return len;
+    }
+
+    /**
+     * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the
+     * specified byte to this output stream.
+     *
+     * @param b the data.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public void write(final int b) throws IOException {
+        oneByteBuf[0] = (byte) (b & 0xff);
+        write(oneByteBuf, 0, oneByteBuf.length);
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java
index 2dcfec9..834673a 100644
--- a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java
+++ b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoInputStream.java
@@ -50,6 +50,48 @@ import org.apache.commons.crypto.utils.Utils;
  * The underlying stream offset is maintained as state. It is not thread-safe.
  */
 public class CtrCryptoInputStream extends CryptoInputStream {
+    /**
+     * <p>
+     * This method is only for Counter (CTR) mode. Generally the CryptoCipher
+     * calculates the IV and maintain encryption context internally.For example
+     * a Cipher will maintain its encryption context internally when we do
+     * encryption/decryption using the CryptoCipher#update interface.
+     * </p>
+     * <p>
+     * Encryption/Decryption is not always on the entire file. For example, in
+     * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In
+     * these situations, the counter is derived from the file position.
+     * </p>
+     * The IV can be calculated by combining the initial IV and the counter with
+     * a lossless operation (concatenation, addition, or XOR).
+     *
+     * @see <a
+     *      href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">
+     *      http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a>
+     *
+     * @param initIV initial IV
+     * @param counter counter for input stream position
+     * @param IV the IV for input stream position
+     */
+    static void calculateIV(final byte[] initIV, long counter, final byte[] IV) {
+        int i = IV.length; // IV length
+
+        Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
+        Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE);
+
+        int j = 0; // counter bytes index
+        int sum = 0;
+        while (i-- > 0) {
+            // (sum >>> Byte.SIZE) is the carry for addition
+            sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD
+            if (j++ < 8) { // Big-endian, and long is 8 bytes length
+                sum += (byte) counter & 0xff;
+                counter >>>= 8;
+            }
+            IV[i] = (byte) sum;
+        }
+    }
+
     /**
      * Underlying stream offset
      */
@@ -80,31 +122,41 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
-     * @param properties The {@code Properties} class represents a set of
-     *        properties.
-     * @param inputStream the input stream.
+     * @param input the input data.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
      * @throws IOException if an I/O error occurs.
      */
-    public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key,
-            final byte[] iv) throws IOException {
-        this(properties, inputStream, key, iv, 0);
+    protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
+        this(input, cipher, bufferSize, key, iv, 0);
     }
 
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
-     * @param properties The {@code Properties} class represents a set of
-     *        properties.
-     * @param channel the ReadableByteChannel instance.
+     * @param input the input data.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
+     * @param streamOffset the start offset in the stream.
      * @throws IOException if an I/O error occurs.
      */
-    public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel,
-            final byte[] key, final byte[] iv) throws IOException {
-        this(properties, channel, key, iv, 0);
+    protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
+            throws IOException {
+        super(input, cipher, bufferSize, AES.newSecretKeySpec(key),
+                new IvParameterSpec(iv));
+
+        this.initIV = iv.clone();
+        this.iv = iv.clone();
+
+        CryptoInputStream.checkStreamCipher(cipher);
+
+        resetStreamOffset(streamOffset);
     }
 
     /**
@@ -125,31 +177,35 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
-     * @param channel the ReadableByteChannel instance.
-     * @param cipher the cipher instance.
+     * @param inputStream the InputStream instance.
+     * @param cipher the CryptoCipher instance.
      * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
+     * @param streamOffset the start offset in the stream.
      * @throws IOException if an I/O error occurs.
      */
-    protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
-        this(channel, cipher, bufferSize, key, iv, 0);
+    @SuppressWarnings("resource") // Closing the instance closes the StreamInput
+    protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
+            throws IOException {
+        this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv,
+                streamOffset);
     }
 
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
-     * @param input the input data.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
+     * @param properties The {@code Properties} class represents a set of
+     *        properties.
+     * @param inputStream the input stream.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
      * @throws IOException if an I/O error occurs.
      */
-    protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
-        this(input, cipher, bufferSize, key, iv, 0);
+    public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key,
+            final byte[] iv) throws IOException {
+        this(properties, inputStream, key, iv, 0);
     }
 
     /**
@@ -171,6 +227,21 @@ public class CtrCryptoInputStream extends CryptoInputStream {
                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
     }
 
+    /**
+     * Constructs a {@link CtrCryptoInputStream}.
+     *
+     * @param properties The {@code Properties} class represents a set of
+     *        properties.
+     * @param channel the ReadableByteChannel instance.
+     * @param key crypto key for the cipher.
+     * @param iv Initialization vector for the cipher.
+     * @throws IOException if an I/O error occurs.
+     */
+    public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel,
+            final byte[] key, final byte[] iv) throws IOException {
+        this(properties, channel, key, iv, 0);
+    }
+
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
@@ -193,20 +264,16 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     /**
      * Constructs a {@link CtrCryptoInputStream}.
      *
-     * @param inputStream the InputStream instance.
-     * @param cipher the CryptoCipher instance.
+     * @param channel the ReadableByteChannel instance.
+     * @param cipher the cipher instance.
      * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
-     * @param streamOffset the start offset in the stream.
      * @throws IOException if an I/O error occurs.
      */
-    @SuppressWarnings("resource") // Closing the instance closes the StreamInput
-    protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
-            throws IOException {
-        this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv,
-                streamOffset);
+    protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
+        this(channel, cipher, bufferSize, key, iv, 0);
     }
 
     /**
@@ -228,164 +295,115 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     }
 
     /**
-     * Constructs a {@link CtrCryptoInputStream}.
+     * Does the decryption using inBuffer as input and outBuffer as output. Upon
+     * return, inBuffer is cleared; the decrypted data starts at
+     * outBuffer.position() and ends at outBuffer.limit().
      *
-     * @param input the input data.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param iv Initialization vector for the cipher.
-     * @param streamOffset the start offset in the stream.
      * @throws IOException if an I/O error occurs.
      */
-    protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
-            throws IOException {
-        super(input, cipher, bufferSize, AES.newSecretKeySpec(key),
-                new IvParameterSpec(iv));
-
-        this.initIV = iv.clone();
-        this.iv = iv.clone();
+    @Override
+    protected void decrypt() throws IOException {
+        Utils.checkState(inBuffer.position() >= padding);
+        if (inBuffer.position() == padding) {
+            // There is no real data in inBuffer.
+            return;
+        }
 
-        CryptoInputStream.checkStreamCipher(cipher);
+        inBuffer.flip();
+        outBuffer.clear();
+        decryptBuffer(outBuffer);
+        inBuffer.clear();
+        outBuffer.flip();
 
-        resetStreamOffset(streamOffset);
+        if (padding > 0) {
+            /*
+             * The plain text and cipher text have a 1:1 mapping, they start at
+             * the same position.
+             */
+            outBuffer.position(padding);
+        }
     }
 
     /**
-     * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and
-     * discards {@code n} bytes of data from this input stream.
+     * Decrypts all data in buf: total n bytes from given start position. Output
+     * is also buf and same start position. buf.position() and buf.limit()
+     * should be unchanged after decryption.
      *
-     * @param n the number of bytes to be skipped.
-     * @return the actual number of bytes skipped.
+     * @param buf The buffer into which bytes are to be transferred.
+     * @param offset the start offset in the data.
+     * @param len the maximum number of decrypted data bytes to read.
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public long skip(long n) throws IOException {
-        Utils.checkArgument(n >= 0, "Negative skip length.");
-        checkStream();
-
-        if (n == 0) {
-            return 0;
-        }
-        if (n <= outBuffer.remaining()) {
-            final int pos = outBuffer.position() + (int) n;
-            outBuffer.position(pos);
-            return n;
-        }
-        /*
-         * Subtract outBuffer.remaining() to see how many bytes we need to
-         * skip in the underlying stream. Add outBuffer.remaining() to the
-         * actual number of skipped bytes in the underlying stream to get
-         * the number of skipped bytes from the user's point of view.
-         */
-        n -= outBuffer.remaining();
-        long skipped = input.skip(n);
-        if (skipped < 0) {
-            skipped = 0;
+    protected void decrypt(final ByteBuffer buf, final int offset, final int len)
+            throws IOException {
+        final int pos = buf.position();
+        final int limit = buf.limit();
+        int n = 0;
+        while (n < len) {
+            buf.position(offset + n);
+            buf.limit(offset + n + Math.min(len - n, inBuffer.remaining()));
+            inBuffer.put(buf);
+            // Do decryption
+            try {
+                decrypt();
+                buf.position(offset + n);
+                buf.limit(limit);
+                n += outBuffer.remaining();
+                buf.put(outBuffer);
+            } finally {
+                padding = postDecryption(streamOffset - (len - n));
+            }
         }
-        final long pos = streamOffset + skipped;
-        skipped += outBuffer.remaining();
-        resetStreamOffset(pos);
-        return skipped;
+        buf.position(pos);
     }
 
     /**
-     * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a
-     * sequence of bytes from this channel into the given buffer.
+     * Does the decryption using out as output.
      *
-     * @param buf The buffer into which bytes are to be transferred.
-     * @return The number of bytes read, possibly zero, or {@code -1} if the
-     *         channel has reached end-of-stream.
+     * @param out the output ByteBuffer.
      * @throws IOException if an I/O error occurs.
      */
-    @Override
-    public int read(final ByteBuffer buf) throws IOException {
-        checkStream();
-        int unread = outBuffer.remaining();
-        if (unread <= 0) { // Fill the unread decrypted data buffer firstly
-            final int n = input.read(inBuffer);
-            if (n <= 0) {
-                return n;
+    protected void decryptBuffer(final ByteBuffer out) throws IOException {
+        final int inputSize = inBuffer.remaining();
+        try {
+            final int n = cipher.update(inBuffer, out);
+            if (n < inputSize) {
+                /**
+                 * Typically code will not get here. CryptoCipher#update will
+                 * consume all input data and put result in outBuffer.
+                 * CryptoCipher#doFinal will reset the cipher context.
+                 */
+                cipher.doFinal(inBuffer, out);
+                cipherReset = true;
             }
-
-            streamOffset += n; // Read n bytes
-            if (buf.isDirect() && buf.remaining() >= inBuffer.position()
-                    && padding == 0) {
-                // Use buf as the output buffer directly
-                decryptInPlace(buf);
-                padding = postDecryption(streamOffset);
-                return n;
-            }
-            // Use outBuffer as the output buffer
-            decrypt();
-            padding = postDecryption(streamOffset);
-        }
-
-        // Copy decrypted data from outBuffer to buf
-        unread = outBuffer.remaining();
-        final int toRead = buf.remaining();
-        if (toRead <= unread) {
-            final int limit = outBuffer.limit();
-            outBuffer.limit(outBuffer.position() + toRead);
-            buf.put(outBuffer);
-            outBuffer.limit(limit);
-            return toRead;
+        } catch (final GeneralSecurityException e) {
+            throw new IOException(e);
         }
-        buf.put(outBuffer);
-        return unread;
     }
 
     /**
-     * Seeks the stream to a specific position relative to start of the under
-     * layer stream.
+     * Does the decryption using inBuffer as input and buf as output. Upon
+     * return, inBuffer is cleared; the buf's position will be equal to
+     * <i>p</i>&nbsp;{@code +}&nbsp;<i>n</i> where <i>p</i> is the position
+     * before decryption, <i>n</i> is the number of bytes decrypted. The buf's
+     * limit will not have changed.
      *
-     * @param position the given position in the data.
+     * @param buf The buffer into which bytes are to be transferred.
      * @throws IOException if an I/O error occurs.
      */
-    public void seek(final long position) throws IOException {
-        Utils.checkArgument(position >= 0, "Cannot seek to negative offset.");
-        checkStream();
-        /*
-         * If data of target pos in the underlying stream has already been read
-         * and decrypted in outBuffer, we just need to re-position outBuffer.
-         */
-        if (position >= getStreamPosition() && position <= getStreamOffset()) {
-            final int forward = (int) (position - getStreamPosition());
-            if (forward > 0) {
-                outBuffer.position(outBuffer.position() + forward);
-            }
-        } else {
-            input.seek(position);
-            resetStreamOffset(position);
-        }
-    }
-
-    /**
-     * Gets the offset of the stream.
-     *
-     * @return the stream offset.
-     */
-    protected long getStreamOffset() {
-        return streamOffset;
-    }
-
-    /**
-     * Sets the offset of stream.
-     *
-     * @param streamOffset the stream offset.
-     */
-    protected void setStreamOffset(final long streamOffset) {
-        this.streamOffset = streamOffset;
-    }
+    protected void decryptInPlace(final ByteBuffer buf) throws IOException {
+        Utils.checkState(inBuffer.position() >= padding);
+        Utils.checkState(buf.isDirect());
+        Utils.checkState(buf.remaining() >= inBuffer.position());
+        Utils.checkState(padding == 0);
 
-    /**
-     * Gets the position of the stream.
-     *
-     * @return the position of the stream.
-     */
-    protected long getStreamPosition() {
-        return streamOffset - outBuffer.remaining();
+        if (inBuffer.position() == padding) {
+            // There is no real data in inBuffer.
+            return;
+        }
+        inBuffer.flip();
+        decryptBuffer(buf);
+        inBuffer.clear();
     }
 
     /**
@@ -409,91 +427,60 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     }
 
     /**
-     * Does the decryption using inBuffer as input and outBuffer as output. Upon
-     * return, inBuffer is cleared; the decrypted data starts at
-     * outBuffer.position() and ends at outBuffer.limit().
+     * Gets the counter for input stream position.
      *
-     * @throws IOException if an I/O error occurs.
+     * @param position the given position in the data.
+     * @return the counter for input stream position.
      */
-    @Override
-    protected void decrypt() throws IOException {
-        Utils.checkState(inBuffer.position() >= padding);
-        if (inBuffer.position() == padding) {
-            // There is no real data in inBuffer.
-            return;
-        }
-
-        inBuffer.flip();
-        outBuffer.clear();
-        decryptBuffer(outBuffer);
-        inBuffer.clear();
-        outBuffer.flip();
+    protected long getCounter(final long position) {
+        return position / cipher.getBlockSize();
+    }
 
-        if (padding > 0) {
-            /*
-             * The plain text and cipher text have a 1:1 mapping, they start at
-             * the same position.
-             */
-            outBuffer.position(padding);
-        }
+    /**
+     * Gets the initialization vector.
+     *
+     * @return the initIV.
+     */
+    protected byte[] getInitIV() {
+        return initIV;
     }
 
     /**
-     * Does the decryption using inBuffer as input and buf as output. Upon
-     * return, inBuffer is cleared; the buf's position will be equal to
-     * <i>p</i>&nbsp;{@code +}&nbsp;<i>n</i> where <i>p</i> is the position
-     * before decryption, <i>n</i> is the number of bytes decrypted. The buf's
-     * limit will not have changed.
+     * Gets the padding for input stream position.
      *
-     * @param buf The buffer into which bytes are to be transferred.
-     * @throws IOException if an I/O error occurs.
+     * @param position the given position in the data.
+     * @return the padding for input stream position.
      */
-    protected void decryptInPlace(final ByteBuffer buf) throws IOException {
-        Utils.checkState(inBuffer.position() >= padding);
-        Utils.checkState(buf.isDirect());
-        Utils.checkState(buf.remaining() >= inBuffer.position());
-        Utils.checkState(padding == 0);
+    protected byte getPadding(final long position) {
+        return (byte) (position % cipher.getBlockSize());
+    }
 
-        if (inBuffer.position() == padding) {
-            // There is no real data in inBuffer.
-            return;
-        }
-        inBuffer.flip();
-        decryptBuffer(buf);
-        inBuffer.clear();
+    /**
+     * Gets the offset of the stream.
+     *
+     * @return the stream offset.
+     */
+    protected long getStreamOffset() {
+        return streamOffset;
     }
 
     /**
-     * Decrypts all data in buf: total n bytes from given start position. Output
-     * is also buf and same start position. buf.position() and buf.limit()
-     * should be unchanged after decryption.
+     * Gets the position of the stream.
      *
-     * @param buf The buffer into which bytes are to be transferred.
-     * @param offset the start offset in the data.
-     * @param len the maximum number of decrypted data bytes to read.
-     * @throws IOException if an I/O error occurs.
+     * @return the position of the stream.
      */
-    protected void decrypt(final ByteBuffer buf, final int offset, final int len)
-            throws IOException {
-        final int pos = buf.position();
-        final int limit = buf.limit();
-        int n = 0;
-        while (n < len) {
-            buf.position(offset + n);
-            buf.limit(offset + n + Math.min(len - n, inBuffer.remaining()));
-            inBuffer.put(buf);
-            // Do decryption
-            try {
-                decrypt();
-                buf.position(offset + n);
-                buf.limit(limit);
-                n += outBuffer.remaining();
-                buf.put(outBuffer);
-            } finally {
-                padding = postDecryption(streamOffset - (len - n));
-            }
-        }
-        buf.position(pos);
+    protected long getStreamPosition() {
+        return streamOffset - outBuffer.remaining();
+    }
+
+    /**
+     * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the
+     * cipher.
+     */
+    @Override
+    protected void initCipher() {
+        // Do nothing for initCipher
+        // Will reset the cipher when reset the stream offset
     }
 
     /**
@@ -521,42 +508,49 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     }
 
     /**
-     * Gets the initialization vector.
-     *
-     * @return the initIV.
-     */
-    protected byte[] getInitIV() {
-        return initIV;
-    }
-
-    /**
-     * Gets the counter for input stream position.
+     * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a
+     * sequence of bytes from this channel into the given buffer.
      *
-     * @param position the given position in the data.
-     * @return the counter for input stream position.
+     * @param buf The buffer into which bytes are to be transferred.
+     * @return The number of bytes read, possibly zero, or {@code -1} if the
+     *         channel has reached end-of-stream.
+     * @throws IOException if an I/O error occurs.
      */
-    protected long getCounter(final long position) {
-        return position / cipher.getBlockSize();
-    }
+    @Override
+    public int read(final ByteBuffer buf) throws IOException {
+        checkStream();
+        int unread = outBuffer.remaining();
+        if (unread <= 0) { // Fill the unread decrypted data buffer firstly
+            final int n = input.read(inBuffer);
+            if (n <= 0) {
+                return n;
+            }
 
-    /**
-     * Gets the padding for input stream position.
-     *
-     * @param position the given position in the data.
-     * @return the padding for input stream position.
-     */
-    protected byte getPadding(final long position) {
-        return (byte) (position % cipher.getBlockSize());
-    }
+            streamOffset += n; // Read n bytes
+            if (buf.isDirect() && buf.remaining() >= inBuffer.position()
+                    && padding == 0) {
+                // Use buf as the output buffer directly
+                decryptInPlace(buf);
+                padding = postDecryption(streamOffset);
+                return n;
+            }
+            // Use outBuffer as the output buffer
+            decrypt();
+            padding = postDecryption(streamOffset);
+        }
 
-    /**
-     * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the
-     * cipher.
-     */
-    @Override
-    protected void initCipher() {
-        // Do nothing for initCipher
-        // Will reset the cipher when reset the stream offset
+        // Copy decrypted data from outBuffer to buf
+        unread = outBuffer.remaining();
+        final int toRead = buf.remaining();
+        if (toRead <= unread) {
+            final int limit = outBuffer.limit();
+            outBuffer.limit(outBuffer.position() + toRead);
+            buf.put(outBuffer);
+            outBuffer.limit(limit);
+            return toRead;
+        }
+        buf.put(outBuffer);
+        return unread;
     }
 
     /**
@@ -594,68 +588,74 @@ public class CtrCryptoInputStream extends CryptoInputStream {
     }
 
     /**
-     * Does the decryption using out as output.
+     * Seeks the stream to a specific position relative to start of the under
+     * layer stream.
      *
-     * @param out the output ByteBuffer.
+     * @param position the given position in the data.
      * @throws IOException if an I/O error occurs.
      */
-    protected void decryptBuffer(final ByteBuffer out) throws IOException {
-        final int inputSize = inBuffer.remaining();
-        try {
-            final int n = cipher.update(inBuffer, out);
-            if (n < inputSize) {
-                /**
-                 * Typically code will not get here. CryptoCipher#update will
-                 * consume all input data and put result in outBuffer.
-                 * CryptoCipher#doFinal will reset the cipher context.
-                 */
-                cipher.doFinal(inBuffer, out);
-                cipherReset = true;
+    public void seek(final long position) throws IOException {
+        Utils.checkArgument(position >= 0, "Cannot seek to negative offset.");
+        checkStream();
+        /*
+         * If data of target pos in the underlying stream has already been read
+         * and decrypted in outBuffer, we just need to re-position outBuffer.
+         */
+        if (position >= getStreamPosition() && position <= getStreamOffset()) {
+            final int forward = (int) (position - getStreamPosition());
+            if (forward > 0) {
+                outBuffer.position(outBuffer.position() + forward);
             }
-        } catch (final GeneralSecurityException e) {
-            throw new IOException(e);
+        } else {
+            input.seek(position);
+            resetStreamOffset(position);
         }
     }
 
     /**
-     * <p>
-     * This method is only for Counter (CTR) mode. Generally the CryptoCipher
-     * calculates the IV and maintain encryption context internally.For example
-     * a Cipher will maintain its encryption context internally when we do
-     * encryption/decryption using the CryptoCipher#update interface.
-     * </p>
-     * <p>
-     * Encryption/Decryption is not always on the entire file. For example, in
-     * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In
-     * these situations, the counter is derived from the file position.
-     * </p>
-     * The IV can be calculated by combining the initial IV and the counter with
-     * a lossless operation (concatenation, addition, or XOR).
-     *
-     * @see <a
-     *      href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">
-     *      http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a>
+     * Sets the offset of stream.
      *
-     * @param initIV initial IV
-     * @param counter counter for input stream position
-     * @param IV the IV for input stream position
+     * @param streamOffset the stream offset.
      */
-    static void calculateIV(final byte[] initIV, long counter, final byte[] IV) {
-        int i = IV.length; // IV length
+    protected void setStreamOffset(final long streamOffset) {
+        this.streamOffset = streamOffset;
+    }
 
-        Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
-        Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE);
+    /**
+     * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and
+     * discards {@code n} bytes of data from this input stream.
+     *
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public long skip(long n) throws IOException {
+        Utils.checkArgument(n >= 0, "Negative skip length.");
+        checkStream();
 
-        int j = 0; // counter bytes index
-        int sum = 0;
-        while (i-- > 0) {
-            // (sum >>> Byte.SIZE) is the carry for addition
-            sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD
-            if (j++ < 8) { // Big-endian, and long is 8 bytes length
-                sum += (byte) counter & 0xff;
-                counter >>>= 8;
-            }
-            IV[i] = (byte) sum;
+        if (n == 0) {
+            return 0;
+        }
+        if (n <= outBuffer.remaining()) {
+            final int pos = outBuffer.position() + (int) n;
+            outBuffer.position(pos);
+            return n;
         }
+        /*
+         * Subtract outBuffer.remaining() to see how many bytes we need to
+         * skip in the underlying stream. Add outBuffer.remaining() to the
+         * actual number of skipped bytes in the underlying stream to get
+         * the number of skipped bytes from the user's point of view.
+         */
+        n -= outBuffer.remaining();
+        long skipped = input.skip(n);
+        if (skipped < 0) {
+            skipped = 0;
+        }
+        final long pos = streamOffset + skipped;
+        skipped += outBuffer.remaining();
+        resetStreamOffset(pos);
+        return skipped;
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java
index 8a16e5d..72e8f79 100644
--- a/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java
+++ b/src/main/java/org/apache/commons/crypto/stream/CtrCryptoOutputStream.java
@@ -84,31 +84,41 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
-     * @param props The {@code Properties} class represents a set of
-     *        properties.
-     * @param out the output stream.
+     * @param output the Output instance.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
      * @throws IOException if an I/O error occurs.
      */
-    public CtrCryptoOutputStream(final Properties props, final OutputStream out,
-            final byte[] key, final byte[] iv) throws IOException {
-        this(props, out, key, iv, 0);
+    protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
+        this(output, cipher, bufferSize, key, iv, 0);
     }
 
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
-     * @param props The {@code Properties} class represents a set of
-     *        properties.
-     * @param out the WritableByteChannel instance.
+     * @param output the output stream.
+     * @param cipher the CryptoCipher instance.
+     * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
+     * @param streamOffset the start offset in the data.
      * @throws IOException if an I/O error occurs.
      */
-    public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out,
-            final byte[] key, final byte[] iv) throws IOException {
-        this(props, out, key, iv, 0);
+    protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
+            throws IOException {
+        super(output, cipher, bufferSize, AES.newSecretKeySpec(key),
+                new IvParameterSpec(iv));
+
+        CryptoInputStream.checkStreamCipher(cipher);
+        this.streamOffset = streamOffset;
+        this.initIV = iv.clone();
+        this.iv = iv.clone();
+
+        resetCipher();
     }
 
     /**
@@ -129,32 +139,34 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
-     * @param channel the WritableByteChannel instance.
+     * @param outputStream the output stream.
      * @param cipher the CryptoCipher instance.
      * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
+     * @param streamOffset the start offset in the data.
      * @throws IOException if an I/O error occurs.
      */
-    protected CtrCryptoOutputStream(final WritableByteChannel channel,
-            final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv)
+    @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
+    protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
+            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
             throws IOException {
-        this(channel, cipher, bufferSize, key, iv, 0);
+        this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset);
     }
 
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
-     * @param output the Output instance.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
+     * @param props The {@code Properties} class represents a set of
+     *        properties.
+     * @param out the output stream.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
      * @throws IOException if an I/O error occurs.
      */
-    protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
-        this(output, cipher, bufferSize, key, iv, 0);
+    public CtrCryptoOutputStream(final Properties props, final OutputStream out,
+            final byte[] key, final byte[] iv) throws IOException {
+        this(props, out, key, iv, 0);
     }
 
     /**
@@ -176,6 +188,21 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
     }
 
+    /**
+     * Constructs a {@link CtrCryptoOutputStream}.
+     *
+     * @param props The {@code Properties} class represents a set of
+     *        properties.
+     * @param out the WritableByteChannel instance.
+     * @param key crypto key for the cipher.
+     * @param iv Initialization vector for the cipher.
+     * @throws IOException if an I/O error occurs.
+     */
+    public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out,
+            final byte[] key, final byte[] iv) throws IOException {
+        this(props, out, key, iv, 0);
+    }
+
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
@@ -198,19 +225,17 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
     /**
      * Constructs a {@link CtrCryptoOutputStream}.
      *
-     * @param outputStream the output stream.
+     * @param channel the WritableByteChannel instance.
      * @param cipher the CryptoCipher instance.
      * @param bufferSize the bufferSize.
      * @param key crypto key for the cipher.
      * @param iv Initialization vector for the cipher.
-     * @param streamOffset the start offset in the data.
      * @throws IOException if an I/O error occurs.
      */
-    @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
-    protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
+    protected CtrCryptoOutputStream(final WritableByteChannel channel,
+            final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv)
             throws IOException {
-        this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset);
+        this(channel, cipher, bufferSize, key, iv, 0);
     }
 
     /**
@@ -231,31 +256,6 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
        this(new ChannelOutput(channel), cipher, bufferSize, key, iv, streamOffset);
     }
 
-    /**
-     * Constructs a {@link CtrCryptoOutputStream}.
-     *
-     * @param output the output stream.
-     * @param cipher the CryptoCipher instance.
-     * @param bufferSize the bufferSize.
-     * @param key crypto key for the cipher.
-     * @param iv Initialization vector for the cipher.
-     * @param streamOffset the start offset in the data.
-     * @throws IOException if an I/O error occurs.
-     */
-    protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
-            final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
-            throws IOException {
-        super(output, cipher, bufferSize, AES.newSecretKeySpec(key),
-                new IvParameterSpec(iv));
-
-        CryptoInputStream.checkStreamCipher(cipher);
-        this.streamOffset = streamOffset;
-        this.initIV = iv.clone();
-        this.iv = iv.clone();
-
-        resetCipher();
-    }
-
     /**
      * Does the encryption, input is {@link #inBuffer} and output is
      * {@link #outBuffer}.
@@ -298,6 +298,30 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
         }
     }
 
+    /**
+     * Does the encryption if the ByteBuffer data.
+     *
+     * @param out the output ByteBuffer.
+     * @throws IOException if an I/O error occurs.
+     */
+    private void encryptBuffer(final ByteBuffer out) throws IOException {
+        final int inputSize = inBuffer.remaining();
+        try {
+            final int n = cipher.update(inBuffer, out);
+            if (n < inputSize) {
+                /**
+                 * Typically code will not get here. CryptoCipher#update will
+                 * consume all input data and put result in outBuffer.
+                 * CryptoCipher#doFinal will reset the cipher context.
+                 */
+                cipher.doFinal(inBuffer, out);
+                cipherReset = true;
+            }
+        } catch (final GeneralSecurityException e) {
+            throw new IOException(e);
+        }
+    }
+
     /**
      * Does final encryption of the last data.
      *
@@ -309,6 +333,15 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
         encrypt();
     }
 
+    /**
+     * Get the underlying stream offset
+     *
+     * @return the underlying stream offset
+     */
+    protected long getStreamOffset() {
+        return streamOffset;
+    }
+
     /**
      * Overrides the {@link CryptoOutputStream#initCipher()}. Initializes the
      * cipher.
@@ -339,39 +372,6 @@ public class CtrCryptoOutputStream extends CryptoOutputStream {
         cipherReset = false;
     }
 
-    /**
-     * Does the encryption if the ByteBuffer data.
-     *
-     * @param out the output ByteBuffer.
-     * @throws IOException if an I/O error occurs.
-     */
-    private void encryptBuffer(final ByteBuffer out) throws IOException {
-        final int inputSize = inBuffer.remaining();
-        try {
-            final int n = cipher.update(inBuffer, out);
-            if (n < inputSize) {
-                /**
-                 * Typically code will not get here. CryptoCipher#update will
-                 * consume all input data and put result in outBuffer.
-                 * CryptoCipher#doFinal will reset the cipher context.
-                 */
-                cipher.doFinal(inBuffer, out);
-                cipherReset = true;
-            }
-        } catch (final GeneralSecurityException e) {
-            throw new IOException(e);
-        }
-    }
-
-    /**
-     * Get the underlying stream offset
-     *
-     * @return the underlying stream offset
-     */
-    protected long getStreamOffset() {
-        return streamOffset;
-    }
-
     /**
      * Set the underlying stream offset
      *
diff --git a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java
index e820e25..07dfdc1 100644
--- a/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java
+++ b/src/main/java/org/apache/commons/crypto/stream/input/ChannelInput.java
@@ -45,70 +45,62 @@ public class ChannelInput implements Input {
     }
 
     /**
-     * Overrides the
-     * {@link org.apache.commons.crypto.stream.input.Input#read(ByteBuffer)}.
-     * Reads a sequence of bytes from input into the given buffer.
+     * Overrides the {@link Input#available()}. Returns an estimate of the
+     * number of bytes that can be read (or skipped over) from this input stream
+     * without blocking by the next invocation of a method for this input
+     * stream. The next invocation might be the same thread or another thread. A
+     * single read or skip of this many bytes will not block, but may read or
+     * skip fewer bytes.
      *
-     * @param dst The buffer into which bytes are to be transferred.
-     * @return the total number of bytes read into the buffer, or
-     *         {@code -1} if there is no more data because the end of the
-     *         stream has been reached.
+     * @return an estimate of the number of bytes that can be read (or skipped
+     *         over) from this input stream without blocking or {@code 0} when
+     *         it reaches the end of the input stream.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public int read(final ByteBuffer dst) throws IOException {
-        return channel.read(dst);
+    public int available() throws IOException {
+        return 0;
     }
 
     /**
      * Overrides the
-     * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips
-     * over and discards {@code n} bytes of data from this input stream.
+     * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes
+     * this input and releases any system resources associated with the under
+     * layer input.
      *
-     * @param n the number of bytes to be skipped.
-     * @return the actual number of bytes skipped.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public long skip(final long n) throws IOException {
-        long remaining = n;
-        int nr;
-
-        if (n <= 0) {
-            return 0;
-        }
+    public void close() throws IOException {
+        channel.close();
+    }
 
-        final int size = (int) Math.min(SKIP_BUFFER_SIZE, remaining);
-        final ByteBuffer skipBuffer = getSkipBuf();
-        while (remaining > 0) {
-            skipBuffer.clear();
-            skipBuffer.limit((int) Math.min(size, remaining));
-            nr = read(skipBuffer);
-            if (nr < 0) {
-                break;
-            }
-            remaining -= nr;
+    /**
+     * Gets the skip buffer.
+     *
+     * @return the buffer.
+     */
+    private ByteBuffer getSkipBuf() {
+        if (buf == null) {
+            buf = ByteBuffer.allocate(SKIP_BUFFER_SIZE);
         }
-
-        return n - remaining;
+        return buf;
     }
 
     /**
-     * Overrides the {@link Input#available()}. Returns an estimate of the
-     * number of bytes that can be read (or skipped over) from this input stream
-     * without blocking by the next invocation of a method for this input
-     * stream. The next invocation might be the same thread or another thread. A
-     * single read or skip of this many bytes will not block, but may read or
-     * skip fewer bytes.
+     * Overrides the
+     * {@link org.apache.commons.crypto.stream.input.Input#read(ByteBuffer)}.
+     * Reads a sequence of bytes from input into the given buffer.
      *
-     * @return an estimate of the number of bytes that can be read (or skipped
-     *         over) from this input stream without blocking or {@code 0} when
-     *         it reaches the end of the input stream.
+     * @param dst The buffer into which bytes are to be transferred.
+     * @return the total number of bytes read into the buffer, or
+     *         {@code -1} if there is no more data because the end of the
+     *         stream has been reached.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public int available() throws IOException {
-        return 0;
+    public int read(final ByteBuffer dst) throws IOException {
+        return channel.read(dst);
     }
 
     /**
@@ -152,26 +144,34 @@ public class ChannelInput implements Input {
 
     /**
      * Overrides the
-     * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes
-     * this input and releases any system resources associated with the under
-     * layer input.
+     * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips
+     * over and discards {@code n} bytes of data from this input stream.
      *
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public void close() throws IOException {
-        channel.close();
-    }
+    public long skip(final long n) throws IOException {
+        long remaining = n;
+        int nr;
 
-    /**
-     * Gets the skip buffer.
-     *
-     * @return the buffer.
-     */
-    private ByteBuffer getSkipBuf() {
-        if (buf == null) {
-            buf = ByteBuffer.allocate(SKIP_BUFFER_SIZE);
+        if (n <= 0) {
+            return 0;
         }
-        return buf;
+
+        final int size = (int) Math.min(SKIP_BUFFER_SIZE, remaining);
+        final ByteBuffer skipBuffer = getSkipBuf();
+        while (remaining > 0) {
+            skipBuffer.clear();
+            skipBuffer.limit((int) Math.min(size, remaining));
+            nr = read(skipBuffer);
+            if (nr < 0) {
+                break;
+            }
+            remaining -= nr;
+        }
+
+        return n - remaining;
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/input/Input.java b/src/main/java/org/apache/commons/crypto/stream/input/Input.java
index 8e1b605..9d9d78b 100644
--- a/src/main/java/org/apache/commons/crypto/stream/input/Input.java
+++ b/src/main/java/org/apache/commons/crypto/stream/input/Input.java
@@ -33,6 +33,33 @@ import org.apache.commons.crypto.stream.CryptoInputStream;
  * {@link ReadableByteChannel}.
  */
 public interface Input extends Closeable {
+    /**
+     * Returns an estimate of the number of bytes that can be read (or skipped
+     * over) from this input without blocking by the next invocation of a method
+     * for this input stream. The next invocation might be the same thread or
+     * another thread. A single read or skip of this many bytes will not block,
+     * but may read or skip fewer bytes.
+     *
+     * <p>
+     * It is never correct to use the return value of this method to allocate a
+     * buffer intended to hold all data in this stream.
+     *
+     * @return an estimate of the number of bytes that can be read (or skipped
+     *         over) from this input stream without blocking or {@code 0} when
+     *         it reaches the end of the input stream.
+     * @throws IOException if an I/O error occurs.
+     */
+    int available() throws IOException;
+
+    /**
+     * Closes this input and releases any system resources associated with the
+     * under layer input.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    void close() throws IOException;
+
     /**
      * Reads a sequence of bytes from input into the given buffer.
      *
@@ -59,47 +86,6 @@ public interface Input extends Closeable {
      */
     int read(ByteBuffer dst) throws IOException;
 
-    /**
-     * Skips over and discards {@code n} bytes of data from this input The
-     * {@code skip} method may, for a variety of reasons, end up skipping
-     * over some smaller number of bytes, possibly {@code 0}. This may
-     * result from any of a number of conditions; reaching end of file before
-     * {@code n} bytes have been skipped is only one possibility. The
-     * actual number of bytes skipped is returned. If {@code n} is
-     * negative, no bytes are skipped.
-     *
-     * <p>
-     * The {@code skip} method of this class creates a byte array and then
-     * repeatedly reads into it until {@code n} bytes have been read or the
-     * end of the stream has been reached. Subclasses are encouraged to provide
-     * a more efficient implementation of this method. For instance, the
-     * implementation may depend on the ability to seek.
-     *
-     * @param n the number of bytes to be skipped.
-     * @return the actual number of bytes skipped.
-     * @throws IOException if the stream does not support seek, or if some
-     *            other I/O error occurs.
-     */
-    long skip(long n) throws IOException;
-
-    /**
-     * Returns an estimate of the number of bytes that can be read (or skipped
-     * over) from this input without blocking by the next invocation of a method
-     * for this input stream. The next invocation might be the same thread or
-     * another thread. A single read or skip of this many bytes will not block,
-     * but may read or skip fewer bytes.
-     *
-     * <p>
-     * It is never correct to use the return value of this method to allocate a
-     * buffer intended to hold all data in this stream.
-     *
-     * @return an estimate of the number of bytes that can be read (or skipped
-     *         over) from this input stream without blocking or {@code 0} when
-     *         it reaches the end of the input stream.
-     * @throws IOException if an I/O error occurs.
-     */
-    int available() throws IOException;
-
     /**
      * Reads up to the specified number of bytes from a given position within a
      * stream and return the number of bytes read. This does not change the
@@ -133,11 +119,25 @@ public interface Input extends Closeable {
     void seek(long position) throws IOException;
 
     /**
-     * Closes this input and releases any system resources associated with the
-     * under layer input.
+     * Skips over and discards {@code n} bytes of data from this input The
+     * {@code skip} method may, for a variety of reasons, end up skipping
+     * over some smaller number of bytes, possibly {@code 0}. This may
+     * result from any of a number of conditions; reaching end of file before
+     * {@code n} bytes have been skipped is only one possibility. The
+     * actual number of bytes skipped is returned. If {@code n} is
+     * negative, no bytes are skipped.
      *
-     * @throws IOException if an I/O error occurs.
+     * <p>
+     * The {@code skip} method of this class creates a byte array and then
+     * repeatedly reads into it until {@code n} bytes have been read or the
+     * end of the stream has been reached. Subclasses are encouraged to provide
+     * a more efficient implementation of this method. For instance, the
+     * implementation may depend on the ability to seek.
+     *
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     * @throws IOException if the stream does not support seek, or if some
+     *            other I/O error occurs.
      */
-    @Override
-    void close() throws IOException;
+    long skip(long n) throws IOException;
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java
index 9dce567..6b6f244 100644
--- a/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java
+++ b/src/main/java/org/apache/commons/crypto/stream/input/StreamInput.java
@@ -47,6 +47,37 @@ public class StreamInput implements Input {
         this.buf = new byte[bufferSize];
     }
 
+    /**
+     * Overrides the {@link Input#available()}. Returns an estimate of the
+     * number of bytes that can be read (or skipped over) from this input stream
+     * without blocking by the next invocation of a method for this input
+     * stream. The next invocation might be the same thread or another thread. A
+     * single read or skip of this many bytes will not block, but may read or
+     * skip fewer bytes.
+     *
+     * @return an estimate of the number of bytes that can be read (or skipped
+     *         over) from this input stream without blocking or {@code 0} when
+     *         it reaches the end of the input stream.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public int available() throws IOException {
+        return in.available();
+    }
+
+    /**
+     * Overrides the
+     * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes
+     * this input and releases any system resources associated with the under
+     * layer input.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public void close() throws IOException {
+        in.close();
+    }
+
     /**
      * Overrides the
      * {@link org.apache.commons.crypto.stream.input.Input#read(ByteBuffer)}.
@@ -80,38 +111,6 @@ public class StreamInput implements Input {
         return read;
     }
 
-    /**
-     * Overrides the
-     * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips
-     * over and discards {@code n} bytes of data from this input stream.
-     *
-     * @param n the number of bytes to be skipped.
-     * @return the actual number of bytes skipped.
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public long skip(final long n) throws IOException {
-        return in.skip(n);
-    }
-
-    /**
-     * Overrides the {@link Input#available()}. Returns an estimate of the
-     * number of bytes that can be read (or skipped over) from this input stream
-     * without blocking by the next invocation of a method for this input
-     * stream. The next invocation might be the same thread or another thread. A
-     * single read or skip of this many bytes will not block, but may read or
-     * skip fewer bytes.
-     *
-     * @return an estimate of the number of bytes that can be read (or skipped
-     *         over) from this input stream without blocking or {@code 0} when
-     *         it reaches the end of the input stream.
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    public int available() throws IOException {
-        return in.available();
-    }
-
     /**
      * Overrides the
      * {@link org.apache.commons.crypto.stream.input.Input#read(long, byte[], int, int)}
@@ -150,14 +149,15 @@ public class StreamInput implements Input {
 
     /**
      * Overrides the
-     * {@link org.apache.commons.crypto.stream.input.Input#seek(long)}. Closes
-     * this input and releases any system resources associated with the under
-     * layer input.
+     * {@link org.apache.commons.crypto.stream.input.Input#skip(long)}. Skips
+     * over and discards {@code n} bytes of data from this input stream.
      *
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public void close() throws IOException {
-        in.close();
+    public long skip(final long n) throws IOException {
+        return in.skip(n);
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java
index 9fcf3a3..d1d5678 100644
--- a/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java
+++ b/src/main/java/org/apache/commons/crypto/stream/output/ChannelOutput.java
@@ -43,18 +43,14 @@ public class ChannelOutput implements Output {
     }
 
     /**
-     * Overrides the
-     * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}.
-     * Writes a sequence of bytes to this output from the given buffer.
-     *
-     * @param src The buffer from which bytes are to be retrieved.
+     * Overrides the {@link Output#close()}. Closes this output and releases any
+     * system resources associated with the under layer output.
      *
-     * @return The number of bytes written, possibly zero.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public int write(final ByteBuffer src) throws IOException {
-        return channel.write(src);
+    public void close() throws IOException {
+        channel.close();
     }
 
     /**
@@ -70,13 +66,17 @@ public class ChannelOutput implements Output {
     }
 
     /**
-     * Overrides the {@link Output#close()}. Closes this output and releases any
-     * system resources associated with the under layer output.
+     * Overrides the
+     * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}.
+     * Writes a sequence of bytes to this output from the given buffer.
+     *
+     * @param src The buffer from which bytes are to be retrieved.
      *
+     * @return The number of bytes written, possibly zero.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public void close() throws IOException {
-        channel.close();
+    public int write(final ByteBuffer src) throws IOException {
+        return channel.write(src);
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/output/Output.java b/src/main/java/org/apache/commons/crypto/stream/output/Output.java
index d5ecc35..bed1337 100644
--- a/src/main/java/org/apache/commons/crypto/stream/output/Output.java
+++ b/src/main/java/org/apache/commons/crypto/stream/output/Output.java
@@ -34,6 +34,27 @@ import org.apache.commons.crypto.stream.CryptoOutputStream;
  */
 public interface Output extends Closeable {
 
+    /**
+     * Closes this output and releases any system resources associated with the
+     * under layer output.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    void close() throws IOException;
+
+    /**
+     * Flushes this output and forces any buffered output bytes to be written
+     * out if the under layer output method support. The general contract of
+     * {@code flush} is that calling it is an indication that, if any bytes
+     * previously written have been buffered by the implementation of the output
+     * stream, such bytes should immediately be written to their intended
+     * destination.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    void flush() throws IOException;
+
     /**
      * Writes a sequence of bytes to this output from the given buffer.
      *
@@ -59,25 +80,4 @@ public interface Output extends Closeable {
      * @throws IOException If some other I/O error occurs.
      */
     int write(ByteBuffer src) throws IOException;
-
-    /**
-     * Flushes this output and forces any buffered output bytes to be written
-     * out if the under layer output method support. The general contract of
-     * {@code flush} is that calling it is an indication that, if any bytes
-     * previously written have been buffered by the implementation of the output
-     * stream, such bytes should immediately be written to their intended
-     * destination.
-     *
-     * @throws IOException if an I/O error occurs.
-     */
-    void flush() throws IOException;
-
-    /**
-     * Closes this output and releases any system resources associated with the
-     * under layer output.
-     *
-     * @throws IOException if an I/O error occurs.
-     */
-    @Override
-    void close() throws IOException;
 }
diff --git a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java
index d77321d..629be84 100644
--- a/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java
+++ b/src/main/java/org/apache/commons/crypto/stream/output/StreamOutput.java
@@ -46,28 +46,14 @@ public class StreamOutput implements Output {
     }
 
     /**
-     * Overrides the
-     * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}.
-     * Writes a sequence of bytes to this output from the given buffer.
-     *
-     * @param src The buffer from which bytes are to be retrieved.
+     * Overrides the {@link Output#close()}. Closes this output and releases any
+     * system resources associated with the under layer output.
      *
-     * @return The number of bytes written, possibly zero.
      * @throws IOException if an I/O error occurs.
      */
     @Override
-    public int write(final ByteBuffer src) throws IOException {
-        final int len = src.remaining();
-
-        int remaining = len;
-        while (remaining > 0) {
-            final int n = Math.min(remaining, bufferSize);
-            src.get(buf, 0, n);
-            out.write(buf, 0, n);
-            remaining = src.remaining();
-        }
-
-        return len;
+    public void close() throws IOException {
+        out.close();
     }
 
     /**
@@ -83,22 +69,36 @@ public class StreamOutput implements Output {
     }
 
     /**
-     * Overrides the {@link Output#close()}. Closes this output and releases any
-     * system resources associated with the under layer output.
+     * Gets the output stream.
      *
-     * @throws IOException if an I/O error occurs.
+     * @return the output stream.
      */
-    @Override
-    public void close() throws IOException {
-        out.close();
+    protected OutputStream getOut() {
+        return out;
     }
 
     /**
-     * Gets the output stream.
+     * Overrides the
+     * {@link org.apache.commons.crypto.stream.output.Output#write(ByteBuffer)}.
+     * Writes a sequence of bytes to this output from the given buffer.
      *
-     * @return the output stream.
+     * @param src The buffer from which bytes are to be retrieved.
+     *
+     * @return The number of bytes written, possibly zero.
+     * @throws IOException if an I/O error occurs.
      */
-    protected OutputStream getOut() {
-        return out;
+    @Override
+    public int write(final ByteBuffer src) throws IOException {
+        final int len = src.remaining();
+
+        int remaining = len;
+        while (remaining > 0) {
+            final int n = Math.min(remaining, bufferSize);
+            src.get(buf, 0, n);
+            out.write(buf, 0, n);
+            remaining = src.remaining();
+        }
+
+        return len;
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/utils/IoUtils.java b/src/main/java/org/apache/commons/crypto/utils/IoUtils.java
index 86e1c91..a7ef2be 100644
--- a/src/main/java/org/apache/commons/crypto/utils/IoUtils.java
+++ b/src/main/java/org/apache/commons/crypto/utils/IoUtils.java
@@ -29,30 +29,31 @@ import org.apache.commons.crypto.stream.input.Input;
 public final class IoUtils {
 
     /**
-     * The private constructor of {@link IoUtils}.
+     * Closes the Closeable objects and <b>ignore</b> any {@link IOException} or
+     * null pointers. Must only be used for cleanup in exception handlers.
+     *
+     * @param closeables the objects to close.
      */
-    private IoUtils() {
+    public static void cleanup(final Closeable... closeables) {
+        if (closeables != null) {
+            for (final Closeable c : closeables) {
+                closeQuietly(c);
+            }
+        }
     }
 
     /**
-     * Does the readFully based on the Input read.
+     * Closes the given {@link Closeable} quietly by ignoring IOException.
      *
-     * @param in the input stream of bytes.
-     * @param buf the buffer to be read.
-     * @param off the start offset in array buffer.
-     * @param len the maximum number of bytes to read.
-     * @throws IOException if an I/O error occurs.
+     * @param closeable The resource to close.
+     * @since 1.1.0
      */
-    public static void readFully(final InputStream in, final byte[] buf, int off, final int len)
-            throws IOException {
-        int toRead = len;
-        while (toRead > 0) {
-            final int ret = in.read(buf, off, toRead);
-            if (ret < 0) {
-                throw new IOException("Premature EOF from inputStream");
+    public static void closeQuietly(final Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (final IOException e) { // NOPMD
             }
-            toRead -= ret;
-            off += ret;
         }
     }
 
@@ -82,31 +83,30 @@ public final class IoUtils {
     }
 
     /**
-     * Closes the Closeable objects and <b>ignore</b> any {@link IOException} or
-     * null pointers. Must only be used for cleanup in exception handlers.
+     * Does the readFully based on the Input read.
      *
-     * @param closeables the objects to close.
+     * @param in the input stream of bytes.
+     * @param buf the buffer to be read.
+     * @param off the start offset in array buffer.
+     * @param len the maximum number of bytes to read.
+     * @throws IOException if an I/O error occurs.
      */
-    public static void cleanup(final Closeable... closeables) {
-        if (closeables != null) {
-            for (final Closeable c : closeables) {
-                closeQuietly(c);
+    public static void readFully(final InputStream in, final byte[] buf, int off, final int len)
+            throws IOException {
+        int toRead = len;
+        while (toRead > 0) {
+            final int ret = in.read(buf, off, toRead);
+            if (ret < 0) {
+                throw new IOException("Premature EOF from inputStream");
             }
+            toRead -= ret;
+            off += ret;
         }
     }
 
     /**
-     * Closes the given {@link Closeable} quietly by ignoring IOException.
-     *
-     * @param closeable The resource to close.
-     * @since 1.1.0
+     * The private constructor of {@link IoUtils}.
      */
-    public static void closeQuietly(final Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (final IOException e) { // NOPMD
-            }
-        }
+    private IoUtils() {
     }
 }
diff --git a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java
index 3dc9df2..deb1f48 100644
--- a/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java
+++ b/src/main/java/org/apache/commons/crypto/utils/ReflectionUtils.java
@@ -31,6 +31,14 @@ import org.apache.commons.crypto.cipher.CryptoCipher;
  */
 public final class ReflectionUtils {
 
+    /**
+     * A unique class which is used as a sentinel value in the caching for
+     * getClassByName. {@link #getClassByNameOrNull(String)}.
+     */
+    private static abstract class NegativeCacheSentinel {
+        // noop
+    }
+
     private static final Map<ClassLoader, Map<String, WeakReference<Class<?>>>> CACHE_CLASSES = new WeakHashMap<>();
 
     private static final ClassLoader CLASSLOADER;
@@ -45,51 +53,6 @@ public final class ReflectionUtils {
      */
     private static final Class<?> NEGATIVE_CACHE_SENTINEL = NegativeCacheSentinel.class;
 
-    /**
-     * The private constructor of {@link ReflectionUtils}.
-     */
-    private ReflectionUtils() {
-    }
-
-    /**
-     * A unique class which is used as a sentinel value in the caching for
-     * getClassByName. {@link #getClassByNameOrNull(String)}.
-     */
-    private static abstract class NegativeCacheSentinel {
-        // noop
-    }
-
-    /**
-     * Uses the constructor represented by this {@code Constructor} object to create
-     * and initialize a new instance of the constructor's declaring class, with the
-     * specified initialization parameters.
-     *
-     * @param <T>   type for the new instance
-     * @param klass the Class object.
-     * @param args  array of objects to be passed as arguments to the constructor
-     *              call.
-     * @return a new object created by calling the constructor this object
-     *         represents.
-     */
-    public static <T> T newInstance(final Class<T> klass, final Object... args) {
-        try {
-            final Constructor<T> ctor;
-            final int argsLength = args.length;
-
-            if (argsLength == 0) {
-                ctor = klass.getDeclaredConstructor();
-            } else {
-                final Class<?>[] argClses = new Class[argsLength];
-                Arrays.setAll(argClses, i -> args[i].getClass());
-                ctor = klass.getDeclaredConstructor(argClses);
-            }
-            ctor.setAccessible(true);
-            return ctor.newInstance(args);
-        } catch (final Exception e) {
-            throw new IllegalArgumentException(e);
-        }
-    }
-
     /**
      * Loads a class by name.
      *
@@ -143,4 +106,41 @@ public final class ReflectionUtils {
         // cache hit
         return clazz;
     }
+
+    /**
+     * Uses the constructor represented by this {@code Constructor} object to create
+     * and initialize a new instance of the constructor's declaring class, with the
+     * specified initialization parameters.
+     *
+     * @param <T>   type for the new instance
+     * @param klass the Class object.
+     * @param args  array of objects to be passed as arguments to the constructor
+     *              call.
+     * @return a new object created by calling the constructor this object
+     *         represents.
+     */
+    public static <T> T newInstance(final Class<T> klass, final Object... args) {
+        try {
+            final Constructor<T> ctor;
+            final int argsLength = args.length;
+
+            if (argsLength == 0) {
+                ctor = klass.getDeclaredConstructor();
+            } else {
+                final Class<?>[] argClses = new Class[argsLength];
+                Arrays.setAll(argClses, i -> args[i].getClass());
+                ctor = klass.getDeclaredConstructor(argClses);
+            }
+            ctor.setAccessible(true);
+            return ctor.newInstance(args);
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /**
+     * The private constructor of {@link ReflectionUtils}.
+     */
+    private ReflectionUtils() {
+    }
 }
diff --git a/src/main/java/org/apache/commons/crypto/utils/Utils.java b/src/main/java/org/apache/commons/crypto/utils/Utils.java
index 1e9a06d..74b772c 100644
--- a/src/main/java/org/apache/commons/crypto/utils/Utils.java
+++ b/src/main/java/org/apache/commons/crypto/utils/Utils.java
@@ -79,54 +79,6 @@ public final class Utils {
      */
     private static final String SYSTEM_PROPERTIES_FILE = Crypto.CONF_PREFIX + "properties";
 
-    /**
-     * The private constructor of {@link Utils}.
-     */
-    private Utils() {
-    }
-
-    /**
-     * Gets a properties instance that defaults to the System Properties
-     * plus any other properties found in the file
-     * {@link #SYSTEM_PROPERTIES_FILE}
-     * @return a Properties instance with defaults
-     */
-    public static Properties getDefaultProperties() {
-        return new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES);
-    }
-
-    /**
-     * Gets the properties merged with default properties.
-     * @param newProp  User-defined properties
-     * @return User-defined properties with the default properties
-     */
-    public static Properties getProperties(final Properties newProp) {
-        final Properties properties = new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES);
-        properties.putAll(newProp);
-        return properties;
-     }
-
-    /**
-     * Helper method to create a CryptoCipher instance and throws only
-     * IOException.
-     *
-     * @param properties The {@code Properties} class represents a set of
-     *        properties.
-     * @param transformation the name of the transformation, e.g.,
-     * <i>AES/CBC/PKCS5Padding</i>.
-     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
-     * for information about standard transformation names.
-     * @return the CryptoCipher instance.
-     * @throws IOException if an I/O error occurs.
-     */
-    public static CryptoCipher getCipherInstance(final String transformation, final Properties properties) throws IOException {
-        try {
-            return CryptoCipherFactory.getCryptoCipher(transformation, properties);
-        } catch (final GeneralSecurityException e) {
-            throw new IOException(e);
-        }
-    }
-
     /**
      * Ensures the truth of an expression involving one or more parameters to
      * the calling method.
@@ -196,29 +148,47 @@ public final class Utils {
     }
 
     /**
-     * Splits class names sequence into substrings, Trim each substring into an
-     * entry,and returns an list of the entries.
+     * Helper method to create a CryptoCipher instance and throws only
+     * IOException.
      *
-     * @param clazzNames a string consist of a list of the entries joined by a
-     *        delimiter, may be null or empty in which case an empty list is returned.
-     * @param separator a delimiter for the input string.
-     * @return a list of class entries.
+     * @param properties The {@code Properties} class represents a set of
+     *        properties.
+     * @param transformation the name of the transformation, e.g.,
+     * <i>AES/CBC/PKCS5Padding</i>.
+     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
+     * for information about standard transformation names.
+     * @return the CryptoCipher instance.
+     * @throws IOException if an I/O error occurs.
      */
-    public static List<String> splitClassNames(final String clazzNames, final String separator) {
-        final List<String> res = new ArrayList<>();
-        if (clazzNames == null || clazzNames.isEmpty()) {
-            return res;
+    public static CryptoCipher getCipherInstance(final String transformation, final Properties properties) throws IOException {
+        try {
+            return CryptoCipherFactory.getCryptoCipher(transformation, properties);
+        } catch (final GeneralSecurityException e) {
+            throw new IOException(e);
         }
+    }
 
-        for (String clazzName : clazzNames.split(separator)) {
-            clazzName = clazzName.trim();
-            if (!clazzName.isEmpty()) {
-                res.add(clazzName);
-            }
-        }
-        return res;
+    /**
+     * Gets a properties instance that defaults to the System Properties
+     * plus any other properties found in the file
+     * {@link #SYSTEM_PROPERTIES_FILE}
+     * @return a Properties instance with defaults
+     */
+    public static Properties getDefaultProperties() {
+        return new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES);
     }
 
+    /**
+     * Gets the properties merged with default properties.
+     * @param newProp  User-defined properties
+     * @return User-defined properties with the default properties
+     */
+    public static Properties getProperties(final Properties newProp) {
+        final Properties properties = new Properties(DefaultPropertiesHolder.DEFAULT_PROPERTIES);
+        properties.putAll(newProp);
+        return properties;
+     }
+
     /*
      * Override the default DLL name if jni.library.path is a valid directory
      * @param name - the default name, passed from native code
@@ -239,4 +209,34 @@ public final class Utils {
         return name;
     }
 
+    /**
+     * Splits class names sequence into substrings, Trim each substring into an
+     * entry,and returns an list of the entries.
+     *
+     * @param clazzNames a string consist of a list of the entries joined by a
+     *        delimiter, may be null or empty in which case an empty list is returned.
+     * @param separator a delimiter for the input string.
+     * @return a list of class entries.
+     */
+    public static List<String> splitClassNames(final String clazzNames, final String separator) {
+        final List<String> res = new ArrayList<>();
+        if (clazzNames == null || clazzNames.isEmpty()) {
+            return res;
+        }
+
+        for (String clazzName : clazzNames.split(separator)) {
+            clazzName = clazzName.trim();
+            if (!clazzName.isEmpty()) {
+                res.add(clazzName);
+            }
+        }
+        return res;
+    }
+
+    /**
+     * The private constructor of {@link Utils}.
+     */
+    private Utils() {
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/crypto/CryptoTest.java b/src/test/java/org/apache/commons/crypto/CryptoTest.java
index eeb1217..b64a1be 100644
--- a/src/test/java/org/apache/commons/crypto/CryptoTest.java
+++ b/src/test/java/org/apache/commons/crypto/CryptoTest.java
@@ -44,14 +44,6 @@ public class CryptoTest {
         assertTrue(version.matches("^\\d+\\.\\d+.*"), version);
     }
 
-    @Test
-    public void testMain() throws Throwable {
-        // Check that Crypt.main will actually run tests
-        assertTrue(Crypto.isNativeCodeLoaded(), "Native code loaded OK");
-        Crypto.main(new String[] { "-q" }); // output causes issues for testing
-        assertTrue(Crypto.isNativeCodeLoaded(), "Completed OK");
-    }
-
     @Test
     public void testLoadingError() throws Throwable {
         final Throwable loadingError = Crypto.getLoadingError();
@@ -61,4 +53,12 @@ public class CryptoTest {
         assertTrue(true, "Completed OK");
     }
 
+    @Test
+    public void testMain() throws Throwable {
+        // Check that Crypt.main will actually run tests
+        assertTrue(Crypto.isNativeCodeLoaded(), "Native code loaded OK");
+        Crypto.main(new String[] { "-q" }); // output causes issues for testing
+        assertTrue(Crypto.isNativeCodeLoaded(), "Completed OK");
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java b/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java
index 8b69c83..705f503 100644
--- a/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java
+++ b/src/test/java/org/apache/commons/crypto/NativeCodeLoaderTest.java
@@ -37,9 +37,16 @@ public class NativeCodeLoaderTest {
     }
 
     @Test
-    public void testNativePresent() {
+    @Disabled("Causes crash on Ubuntu when compiled with Java 17")
+    // The following error is reported:
+    // "Corrupted channel by directly writing to native stream in forked JVM 1"
+    // Note that this appears during a subsequent test, and does not
+    // happen every time.
+    // At this point it is not known where the native stream is written.
+    public void testCanLoadIfPresent() {
         assumeTrue(NativeCodeLoader.isNativeCodeLoaded());
-        assertNull(NativeCodeLoader.getLoadingError());
+        // This will try to reload the library, so should work
+        assertNull(NativeCodeLoader.loadLibrary());
     }
 
     @Test
@@ -49,16 +56,9 @@ public class NativeCodeLoaderTest {
     }
 
     @Test
-    @Disabled("Causes crash on Ubuntu when compiled with Java 17")
-    // The following error is reported:
-    // "Corrupted channel by directly writing to native stream in forked JVM 1"
-    // Note that this appears during a subsequent test, and does not
-    // happen every time.
-    // At this point it is not known where the native stream is written.
-    public void testCanLoadIfPresent() {
+    public void testNativePresent() {
         assumeTrue(NativeCodeLoader.isNativeCodeLoaded());
-        // This will try to reload the library, so should work
-        assertNull(NativeCodeLoader.loadLibrary());
+        assertNull(NativeCodeLoader.getLoadingError());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java
index 735bc84..c9db425 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/AbstractCipherTest.java
@@ -51,35 +51,124 @@ public abstract class AbstractCipherTest {
 	// data
 	public static final int BYTEBUFFER_SIZE = 1000;
 
-	public String[] cipherTests;
-	private Properties props;
-	protected String cipherClass;
-	protected String[] transformations;
-
 	// cipher
 	static final byte[] KEY = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14,
 			0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24 };
 	static final byte[] IV = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
 			0x08 };
+	public String[] cipherTests;
+	private Properties props;
+
+	protected String cipherClass;
+	protected String[] transformations;
 	private CryptoCipher enc, dec;
 
-	@BeforeEach
-	public void setup() {
-		init();
-		assertNotNull(cipherClass, "cipherClass");
-		assertNotNull(transformations, "transformations");
-		props = new Properties();
-		props.setProperty(CryptoCipherFactory.CLASSES_KEY, cipherClass);
+	/** test byte array whose data is randomly generated */
+	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv) throws Exception {
+		final int blockSize = enc.getBlockSize();
+
+		// AES_CBC_NOPADDING only accepts data whose size is the multiple of
+		// block size
+		final int[] dataLenList = transformation.equals(AES.CBC_NO_PADDING) ? new int[] { 10 * 1024 }
+				: new int[] { 10 * 1024, 10 * 1024 - 3 };
+		for (final int dataLen : dataLenList) {
+			final byte[] plainText = new byte[dataLen];
+			final Random random = new SecureRandom();
+			random.nextBytes(plainText);
+			final byte[] cipherText = new byte[dataLen + blockSize];
+
+			// check update method with inputs whose sizes are the multiple of
+			// block size or not
+			final int[] bufferLenList = { 2 * 1024 - 128, 2 * 1024 - 125 };
+			for (final int bufferLen : bufferLenList) {
+				resetCipher(transformation, key, iv);
+
+				int offset = 0;
+				// encrypt (update + doFinal) the data
+				int cipherPos = 0;
+				for (int i = 0; i < dataLen / bufferLen; i++) {
+					cipherPos += enc.update(plainText, offset, bufferLen, cipherText, cipherPos);
+					offset += bufferLen;
+				}
+				cipherPos += enc.doFinal(plainText, offset, dataLen % bufferLen, cipherText, cipherPos);
+
+				offset = 0;
+				// decrypt (update + doFinal) the data
+				final byte[] realPlainText = new byte[cipherPos + blockSize];
+				int plainPos = 0;
+				for (int i = 0; i < cipherPos / bufferLen; i++) {
+					plainPos += dec.update(cipherText, offset, bufferLen, realPlainText, plainPos);
+					offset += bufferLen;
+				}
+				plainPos += dec.doFinal(cipherText, offset, cipherPos % bufferLen, realPlainText, plainPos);
+
+				// verify
+				assertEquals(dataLen, plainPos, "random byte array length changes after transformation");
+
+				final byte[] shrinkPlainText = new byte[plainPos];
+				System.arraycopy(realPlainText, 0, shrinkPlainText, 0, plainPos);
+				assertArrayEquals(plainText, shrinkPlainText, "random byte array contents changes after transformation");
+			}
+		}
 	}
 
-	protected abstract void init();
+	/** test byte array whose data is planned in {@link TestData} */
+	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv, final byte[] input,
+			final byte[] output) throws Exception {
+		resetCipher(transformation, key, iv);
+		final int blockSize = enc.getBlockSize();
 
-	@Test
-	public void closeTestNoInit() throws Exception {
-		// This test deliberately does not use try with resources in order to control
-		// the sequence of operations exactly
-		try (final CryptoCipher enc = getCipher(transformations[0])) {
-		    // empty
+		byte[] temp = new byte[input.length + blockSize];
+		final int n = enc.doFinal(input, 0, input.length, temp, 0);
+		final byte[] cipherText = new byte[n];
+		System.arraycopy(temp, 0, cipherText, 0, n);
+		assertArrayEquals(output, cipherText, "byte array encryption error.");
+
+		temp = new byte[cipherText.length + blockSize];
+		final int m = dec.doFinal(cipherText, 0, cipherText.length, temp, 0);
+		final byte[] plainText = new byte[m];
+		System.arraycopy(temp, 0, plainText, 0, m);
+		assertArrayEquals(input, plainText, "byte array decryption error.");
+	}
+
+	private void byteBufferTest(final String transformation, final byte[] key, final byte[] iv, final ByteBuffer input,
+			final ByteBuffer output) throws Exception {
+		final ByteBuffer decResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
+		final ByteBuffer encResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
+
+		try (final CryptoCipher enc = getCipher(transformation); final CryptoCipher dec = getCipher(transformation)) {
+
+			enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
+			dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
+
+			//
+			// encryption pass
+			//
+			enc.doFinal(input, encResult);
+			input.flip();
+			encResult.flip();
+			if (!output.equals(encResult)) {
+				final byte[] b = new byte[output.remaining()];
+				output.get(b);
+				final byte[] c = new byte[encResult.remaining()];
+				encResult.get(c);
+				fail("AES failed encryption - expected " + DatatypeConverter.printHexBinary(b)
+						+ " got " + DatatypeConverter.printHexBinary(c));
+			}
+
+			//
+			// decryption pass
+			//
+			dec.doFinal(encResult, decResult);
+			decResult.flip();
+
+			if (!input.equals(decResult)) {
+				final byte[] inArray = new byte[input.remaining()];
+				final byte[] decResultArray = new byte[decResult.remaining()];
+				input.get(inArray);
+				decResult.get(decResultArray);
+				fail();
+			}
 		}
 	}
 
@@ -92,32 +181,15 @@ public abstract class AbstractCipherTest {
         }
 	}
 
-    SecretKeySpec newSecretKeySpec() {
-        return AES.newSecretKeySpec(KEY);
-    }
-
-	@Test
-	public void reInitTest() throws Exception {
+    @Test
+	public void closeTestNoInit() throws Exception {
 		// This test deliberately does not use try with resources in order to control
 		// the sequence of operations exactly
-        try (final CryptoCipher enc = getCipher(transformations[0])) {
-            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
-            enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
-            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
-        }
+		try (final CryptoCipher enc = getCipher(transformations[0])) {
+		    // empty
+		}
 	}
 
-	@Test
-    public void reInitAfterClose() throws Exception {
-        // This test deliberately does not use try with resources in order to control
-        // the sequence of operations exactly
-        try (final CryptoCipher enc = getCipher(transformations[0])) {
-            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
-            enc.close();
-            enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
-        }
-    }
-
 	@Test
 	public void closeTestRepeat() throws Exception {
 		// This test deliberately does not use try with resources in order to control
@@ -157,29 +229,56 @@ public abstract class AbstractCipherTest {
 		}
 	}
 
-	@Test
-	public void testNullTransform() {
-		assertThrows(IllegalArgumentException.class,
-				() -> getCipher(null).close());
+	private CryptoCipher getCipher(final String transformation) throws ClassNotFoundException {
+		return (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(cipherClass), props,
+				transformation);
 	}
 
+	protected abstract void init();
+
+	SecretKeySpec newSecretKeySpec() {
+        return AES.newSecretKeySpec(KEY);
+    }
+
 	@Test
-	public void testInvalidTransform() {
-		assertThrows(IllegalArgumentException.class,
-				() -> getCipher("AES/CBR/NoPadding/garbage/garbage").close());
-	}
+    public void reInitAfterClose() throws Exception {
+        // This test deliberately does not use try with resources in order to control
+        // the sequence of operations exactly
+        try (final CryptoCipher enc = getCipher(transformations[0])) {
+            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
+            enc.close();
+            enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
+        }
+    }
 
 	@Test
-	public void testInvalidKey() throws Exception {
-		for (final String transform : transformations) {
-			try (final CryptoCipher cipher = getCipher(transform)) {
-				assertNotNull(cipher);
+	public void reInitTest() throws Exception {
+		// This test deliberately does not use try with resources in order to control
+		// the sequence of operations exactly
+        try (final CryptoCipher enc = getCipher(transformations[0])) {
+            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
+            enc.init(Cipher.DECRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
+            enc.init(Cipher.ENCRYPT_MODE, newSecretKeySpec(), new IvParameterSpec(IV));
+        }
+	}
 
-				final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
-						0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
-				assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(invalidKey), new IvParameterSpec(IV)));
-			}
-		}
+	private void resetCipher(final String transformation, final byte[] key, final byte[] iv)
+			throws ClassNotFoundException, InvalidKeyException, InvalidAlgorithmParameterException {
+		enc = getCipher(transformation);
+		dec = getCipher(transformation);
+
+		enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
+
+		dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
+	}
+
+	@BeforeEach
+	public void setup() {
+		init();
+		assertNotNull(cipherClass, "cipherClass");
+		assertNotNull(transformations, "transformations");
+		props = new Properties();
+		props.setProperty(CryptoCipherFactory.CLASSES_KEY, cipherClass);
 	}
 
 	@Test
@@ -204,127 +303,28 @@ public abstract class AbstractCipherTest {
 		}
 	}
 
-	private void byteBufferTest(final String transformation, final byte[] key, final byte[] iv, final ByteBuffer input,
-			final ByteBuffer output) throws Exception {
-		final ByteBuffer decResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
-		final ByteBuffer encResult = ByteBuffer.allocateDirect(BYTEBUFFER_SIZE);
-
-		try (final CryptoCipher enc = getCipher(transformation); final CryptoCipher dec = getCipher(transformation)) {
-
-			enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
-			dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
-
-			//
-			// encryption pass
-			//
-			enc.doFinal(input, encResult);
-			input.flip();
-			encResult.flip();
-			if (!output.equals(encResult)) {
-				final byte[] b = new byte[output.remaining()];
-				output.get(b);
-				final byte[] c = new byte[encResult.remaining()];
-				encResult.get(c);
-				fail("AES failed encryption - expected " + DatatypeConverter.printHexBinary(b)
-						+ " got " + DatatypeConverter.printHexBinary(c));
-			}
-
-			//
-			// decryption pass
-			//
-			dec.doFinal(encResult, decResult);
-			decResult.flip();
-
-			if (!input.equals(decResult)) {
-				final byte[] inArray = new byte[input.remaining()];
-				final byte[] decResultArray = new byte[decResult.remaining()];
-				input.get(inArray);
-				decResult.get(decResultArray);
-				fail();
-			}
-		}
-	}
-
-	/** test byte array whose data is planned in {@link TestData} */
-	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv, final byte[] input,
-			final byte[] output) throws Exception {
-		resetCipher(transformation, key, iv);
-		final int blockSize = enc.getBlockSize();
-
-		byte[] temp = new byte[input.length + blockSize];
-		final int n = enc.doFinal(input, 0, input.length, temp, 0);
-		final byte[] cipherText = new byte[n];
-		System.arraycopy(temp, 0, cipherText, 0, n);
-		assertArrayEquals(output, cipherText, "byte array encryption error.");
-
-		temp = new byte[cipherText.length + blockSize];
-		final int m = dec.doFinal(cipherText, 0, cipherText.length, temp, 0);
-		final byte[] plainText = new byte[m];
-		System.arraycopy(temp, 0, plainText, 0, m);
-		assertArrayEquals(input, plainText, "byte array decryption error.");
-	}
-
-	/** test byte array whose data is randomly generated */
-	private void byteArrayTest(final String transformation, final byte[] key, final byte[] iv) throws Exception {
-		final int blockSize = enc.getBlockSize();
-
-		// AES_CBC_NOPADDING only accepts data whose size is the multiple of
-		// block size
-		final int[] dataLenList = transformation.equals(AES.CBC_NO_PADDING) ? new int[] { 10 * 1024 }
-				: new int[] { 10 * 1024, 10 * 1024 - 3 };
-		for (final int dataLen : dataLenList) {
-			final byte[] plainText = new byte[dataLen];
-			final Random random = new SecureRandom();
-			random.nextBytes(plainText);
-			final byte[] cipherText = new byte[dataLen + blockSize];
-
-			// check update method with inputs whose sizes are the multiple of
-			// block size or not
-			final int[] bufferLenList = { 2 * 1024 - 128, 2 * 1024 - 125 };
-			for (final int bufferLen : bufferLenList) {
-				resetCipher(transformation, key, iv);
-
-				int offset = 0;
-				// encrypt (update + doFinal) the data
-				int cipherPos = 0;
-				for (int i = 0; i < dataLen / bufferLen; i++) {
-					cipherPos += enc.update(plainText, offset, bufferLen, cipherText, cipherPos);
-					offset += bufferLen;
-				}
-				cipherPos += enc.doFinal(plainText, offset, dataLen % bufferLen, cipherText, cipherPos);
-
-				offset = 0;
-				// decrypt (update + doFinal) the data
-				final byte[] realPlainText = new byte[cipherPos + blockSize];
-				int plainPos = 0;
-				for (int i = 0; i < cipherPos / bufferLen; i++) {
-					plainPos += dec.update(cipherText, offset, bufferLen, realPlainText, plainPos);
-					offset += bufferLen;
-				}
-				plainPos += dec.doFinal(cipherText, offset, cipherPos % bufferLen, realPlainText, plainPos);
-
-				// verify
-				assertEquals(dataLen, plainPos, "random byte array length changes after transformation");
+	@Test
+	public void testInvalidKey() throws Exception {
+		for (final String transform : transformations) {
+			try (final CryptoCipher cipher = getCipher(transform)) {
+				assertNotNull(cipher);
 
-				final byte[] shrinkPlainText = new byte[plainPos];
-				System.arraycopy(realPlainText, 0, shrinkPlainText, 0, plainPos);
-				assertArrayEquals(plainText, shrinkPlainText, "random byte array contents changes after transformation");
+				final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+						0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
+				assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(invalidKey), new IvParameterSpec(IV)));
 			}
 		}
 	}
 
-	private void resetCipher(final String transformation, final byte[] key, final byte[] iv)
-			throws ClassNotFoundException, InvalidKeyException, InvalidAlgorithmParameterException {
-		enc = getCipher(transformation);
-		dec = getCipher(transformation);
-
-		enc.init(Cipher.ENCRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
-
-		dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(key), new IvParameterSpec(iv));
+	@Test
+	public void testInvalidTransform() {
+		assertThrows(IllegalArgumentException.class,
+				() -> getCipher("AES/CBR/NoPadding/garbage/garbage").close());
 	}
 
-	private CryptoCipher getCipher(final String transformation) throws ClassNotFoundException {
-		return (CryptoCipher) ReflectionUtils.newInstance(ReflectionUtils.getClassByName(cipherClass), props,
-				transformation);
+	@Test
+	public void testNullTransform() {
+		assertThrows(IllegalArgumentException.class,
+				() -> getCipher(null).close());
 	}
 }
diff --git a/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java b/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java
index 32b49a5..09dc249 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/DefaultCryptoCipher.java
@@ -41,44 +41,44 @@ public class DefaultCryptoCipher implements CryptoCipher {
     }
 
     @Override
-    public int getBlockSize() {
+    public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
         // Simplest
         return 0;
     }
 
     @Override
-    public String getAlgorithm() {
+    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
         // Simplest
-        return null;
+        return 0;
     }
 
     @Override
-    public void init(final int mode, final Key key, final AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException {
+    public String getAlgorithm() {
         // Simplest
-
+        return null;
     }
 
     @Override
-    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
+    public int getBlockSize() {
         // Simplest
         return 0;
     }
 
     @Override
-    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException {
+    public void init(final int mode, final Key key, final AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException {
         // Simplest
-        return 0;
+
     }
 
     @Override
-    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+    public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset) throws ShortBufferException {
         // Simplest
         return 0;
     }
 
     @Override
-    public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
-            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
         // Simplest
         return 0;
     }
diff --git a/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java
index 13a5591..fb9e189 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/GcmCipherTest.java
@@ -52,6 +52,92 @@ public class GcmCipherTest {
     private String[] cHex;
     private String[] tHex;
 
+    private void initTestData() {
+
+        final int casesNumber = 4;
+
+        kHex = new String[casesNumber];
+        pHex = new String[casesNumber];
+        ivHex = new String[casesNumber];
+        aadHex = new String[casesNumber];
+        cHex = new String[casesNumber];
+        tHex = new String[casesNumber];
+
+        // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+        // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
+        // NIST Case2  -----------------------------
+        // key length:          16 bytes
+        // plain text length:   16 bytes
+        // iv length:           12 bytes
+        // aad length:          0 bytes
+        kHex[0] = "00000000000000000000000000000000";
+        pHex[0] = "00000000000000000000000000000000";
+        ivHex[0]  = "000000000000000000000000";
+        aadHex[0]  = "";
+        cHex[0]  = "0388dace60b6a392f328c2b971b2fe78";
+        tHex[0]  = "ab6e47d42cec13bdf53a67b21257bddf";
+
+        // NIST Case4 ---------------------------------
+        // key length:          16 bytes
+        // plain text length:   60 bytes
+        // iv length:           12 bytes
+        // aad length:          20 bytes
+        kHex[1] = "feffe9928665731c6d6a8f9467308308";
+        pHex[1] = "d9313225f88406e5a55909c5aff5269a"
+                + "86a7a9531534f7da2e4c303d8a318a72"
+                + "1c3c0c95956809532fcf0e2449a6b525"
+                + "b16aedf5aa0de657ba637b39";
+        ivHex[1] = "cafebabefacedbaddecaf888";
+        aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef"
+                + "abaddad2";
+        cHex[1] = "42831ec2217774244b7221b784d0d49c"
+                + "e3aa212f2c02a4e035c17e2329aca12e"
+                + "21d514b25466931c7d8f6a5aac84aa05"
+                + "1ba30b396a0aac973d58e091";
+        tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47";
+
+        // NIST Case5 ---------------------------------
+        // key length:          16 bytes
+        // plain text length:   60 bytes
+        // iv length:           8 bytes
+        // aad length:          20 bytes
+        kHex[2] = "feffe9928665731c6d6a8f9467308308";
+        pHex[2] = "d9313225f88406e5a55909c5aff5269a"
+                + "86a7a9531534f7da2e4c303d8a318a72"
+                + "1c3c0c95956809532fcf0e2449a6b525"
+                + "b16aedf5aa0de657ba637b39";
+        ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits
+        aadHex[2]="feedfacedeadbeeffeedfacedeadbeef"
+                + "abaddad2";
+        cHex[2] = "61353b4c2806934a777ff51fa22a4755"
+                + "699b2a714fcdc6f83766e5f97b6c7423"
+                + "73806900e49f24b22b097544d4896b42"
+                + "4989b5e1ebac0f07c23f4598";
+        tHex[2] = "3612d2e79e3b0785561be14aaca2fccb";
+
+        // NIST Case6 ---------------------------------
+        // key length:          16 bytes
+        // plain text length:   60 bytes
+        // iv length:           60 bytes
+        // aad length:          20 bytes
+        kHex[3] = "feffe9928665731c6d6a8f9467308308";
+        pHex[3] = "d9313225f88406e5a55909c5aff5269a"
+                + "86a7a9531534f7da2e4c303d8a318a72"
+                + "1c3c0c95956809532fcf0e2449a6b525"
+                + "b16aedf5aa0de657ba637b39";
+        ivHex[3] = "9313225df88406e555909c5aff5269aa"
+                + "6a7a9538534f7da1e4c303d2a318a728"
+                + "c3c0c95156809539fcf0e2429a6b5254"
+                + "16aedbf5a0de6a57a637b39b"; // > 96bits
+        aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef"
+                + "abaddad2";
+        cHex[3] = "8ce24998625615b603a033aca13fb894"
+                + "be9112a5c3a211a8ba262a3cca7e2ca7"
+                + "01e4a9a4fba43c90ccdcb281d48c7c6f"
+                + "d62875d2aca417034c34aee5";
+        tHex[3] = "619cc5aefffe0bfa462af43c1699d050";
+    }
+
     @BeforeEach
     public void setup() {
         //init
@@ -63,6 +149,171 @@ public class GcmCipherTest {
         initTestData();
     }
 
+    private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex,
+                                              final String cHex, final String tHex) throws Exception {
+
+        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
+        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
+        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
+        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
+        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);
+
+        final byte[] encOutput = new byte[expectedOutput.length];
+        final byte[] decOutput = new byte[input.length];
+
+        final Random r = new Random();
+
+        int partLen;
+        int len;
+        try (final CryptoCipher enc = Utils.getCipherInstance(transformation, props)) {
+            final Key key = AES.newSecretKeySpec(keyBytes);
+            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
+            enc.init(Cipher.ENCRYPT_MODE, key, iv);
+            if (aad.length > 0) {
+                final int len1 = r.nextInt(aad.length);
+                final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
+                final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
+                enc.updateAAD(aad1);
+                enc.updateAAD(aad2);
+            }
+
+            partLen = r.nextInt(input.length);
+            len = enc.update(input, 0, partLen, encOutput, 0);
+            assertEquals(partLen, len);
+            len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen);
+            assertEquals((input.length + (iv.getTLen() >> 3) - partLen), len);
+
+            assertArrayEquals(expectedOutput, encOutput);
+        }
+
+        // Decryption
+        try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) {
+            dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes));
+            if (aad.length > 0) {
+                final int len1 = r.nextInt(aad.length);
+                final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
+                final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
+                dec.updateAAD(aad1);
+                dec.updateAAD(aad2);
+            }
+            final byte[] decInput = encOutput;
+            partLen = r.nextInt(input.length);
+            len = dec.update(decInput, 0, partLen, decOutput, 0);
+            assertEquals(len, 0);
+            len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0);
+            assertEquals(input.length, len);
+
+            assertArrayEquals(input, decOutput);
+        }
+    }
+
+    private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex,
+                                   final String cHex, final String tHex) throws Exception {
+
+        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
+        final byte[] plainText = DatatypeConverter.parseHexBinary(pHex);
+        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
+        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
+        final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex);
+
+        final byte[] encOutput = new byte[cipherText.length];
+        final byte[] decOutput = new byte[plainText.length];
+
+        final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length);
+        bfAAD.put(aad);
+
+        final ByteBuffer bfPlainText;
+        final ByteBuffer bfCipherText;
+        bfPlainText = ByteBuffer.allocateDirect(plainText.length);
+        bfCipherText = ByteBuffer.allocateDirect(encOutput.length);
+
+        // Encryption -------------------
+        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
+            final Key key = AES.newSecretKeySpec(keyBytes);
+            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
+            c.init(Cipher.ENCRYPT_MODE, key, iv);
+
+            bfAAD.flip();
+            c.updateAAD(bfAAD);
+
+            bfPlainText.put(plainText);
+            bfPlainText.flip();
+            bfCipherText.position(0);
+
+            c.doFinal(bfPlainText, bfCipherText);
+
+            bfCipherText.flip();
+            bfCipherText.get(encOutput);
+            assertArrayEquals(cipherText, encOutput);
+        }
+
+        // Decryption -------------------
+        try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) {
+            dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes));
+            bfAAD.flip();
+            dec.updateAAD(bfAAD);
+            bfCipherText.clear();
+            bfPlainText.clear();
+            bfCipherText.put(cipherText);
+            bfCipherText.flip();
+            dec.doFinal(bfCipherText, bfPlainText);
+            bfPlainText.flip();
+            bfPlainText.get(decOutput);
+            assertArrayEquals(plainText, decOutput);
+        }
+    }
+
+    private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
+                                   final String cHex, final String tHex) throws Exception {
+
+        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
+        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
+        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
+
+        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
+        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);
+
+        final byte[] input = cipherBytes;
+        final byte[] output = new byte[plainBytes.length];
+
+        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
+
+            final Key key = AES.newSecretKeySpec(keyBytes);
+
+            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
+            c.init(Cipher.DECRYPT_MODE, key, iv);
+            c.updateAAD(aad);
+            c.doFinal(input, 0, input.length, output, 0);
+
+            assertArrayEquals(plainBytes, output);
+        }
+    }
+
+    private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
+                                   final String cHex, final String tHex) throws Exception {
+
+        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
+        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
+        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
+        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
+        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);
+
+        final byte[] output = new byte[expectedOutput.length];
+
+        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
+
+            final Key key = AES.newSecretKeySpec(keyBytes);
+
+            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
+            c.init(Cipher.ENCRYPT_MODE, key, iv);
+            c.updateAAD(aad);
+
+            c.doFinal(input, 0, input.length, output, 0);
+
+            assertArrayEquals(expectedOutput, output);
+        }
+    }
+
     /**
      * NIST AES Test Vectors
      * http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
@@ -199,10 +450,41 @@ public class GcmCipherTest {
         }
     }
 
-    @Test
-    public void testGcmTamperedData() throws Exception {
+    private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex,
+                                                   final String cHex, final String tHex) throws Exception {
 
-        final Random r = new Random();
+        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
+        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
+        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
+
+        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
+        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);
+
+        final byte[] input = cipherBytes;
+        final byte[] output = new byte[plainBytes.length];
+
+        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
+
+            final Key key = AES.newSecretKeySpec(keyBytes);
+
+            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
+            c.init(Cipher.DECRYPT_MODE, key, iv);
+            c.updateAAD(aad);
+
+            // only return recovered data after tag is successfully verified
+            int len = c.update(input, 0, input.length, output, 0);
+            assertEquals(len, 0);
+            len += c.doFinal(input, input.length, 0, output, 0);
+            assertEquals(plainBytes.length, len);
+
+            assertArrayEquals(plainBytes, output);
+        }
+    }
+
+    @Test
+    public void testGcmTamperedData() throws Exception {
+
+        final Random r = new Random();
         final int textLength = r.nextInt(1024*1024);
         final int ivLength = r.nextInt(59) + 1;
         final int keyLength = 16;
@@ -338,286 +620,4 @@ public class GcmCipherTest {
         }
 
     }
-
-    private void testGcmEncryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
-                                   final String cHex, final String tHex) throws Exception {
-
-        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
-        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
-        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
-        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
-        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);
-
-        final byte[] output = new byte[expectedOutput.length];
-
-        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
-
-            final Key key = AES.newSecretKeySpec(keyBytes);
-
-            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
-            c.init(Cipher.ENCRYPT_MODE, key, iv);
-            c.updateAAD(aad);
-
-            c.doFinal(input, 0, input.length, output, 0);
-
-            assertArrayEquals(expectedOutput, output);
-        }
-    }
-
-    private void testGcmArbitraryLengthUpdate(final String kHex, final String pHex, final String ivHex, final String aadHex,
-                                              final String cHex, final String tHex) throws Exception {
-
-        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
-        final byte[] input = DatatypeConverter.parseHexBinary(pHex);
-        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
-        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
-        final byte[] expectedOutput = DatatypeConverter.parseHexBinary(cHex+tHex);
-
-        final byte[] encOutput = new byte[expectedOutput.length];
-        final byte[] decOutput = new byte[input.length];
-
-        final Random r = new Random();
-
-        int partLen;
-        int len;
-        try (final CryptoCipher enc = Utils.getCipherInstance(transformation, props)) {
-            final Key key = AES.newSecretKeySpec(keyBytes);
-            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
-            enc.init(Cipher.ENCRYPT_MODE, key, iv);
-            if (aad.length > 0) {
-                final int len1 = r.nextInt(aad.length);
-                final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
-                final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
-                enc.updateAAD(aad1);
-                enc.updateAAD(aad2);
-            }
-
-            partLen = r.nextInt(input.length);
-            len = enc.update(input, 0, partLen, encOutput, 0);
-            assertEquals(partLen, len);
-            len = enc.doFinal(input, partLen, input.length - partLen, encOutput, partLen);
-            assertEquals((input.length + (iv.getTLen() >> 3) - partLen), len);
-
-            assertArrayEquals(expectedOutput, encOutput);
-        }
-
-        // Decryption
-        try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) {
-            dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes));
-            if (aad.length > 0) {
-                final int len1 = r.nextInt(aad.length);
-                final byte[] aad1 = Arrays.copyOfRange(aad, 0, len1);
-                final byte[] aad2 = Arrays.copyOfRange(aad, len1, aad.length);
-                dec.updateAAD(aad1);
-                dec.updateAAD(aad2);
-            }
-            final byte[] decInput = encOutput;
-            partLen = r.nextInt(input.length);
-            len = dec.update(decInput, 0, partLen, decOutput, 0);
-            assertEquals(len, 0);
-            len = dec.doFinal(decInput, partLen, decInput.length - partLen, decOutput, 0);
-            assertEquals(input.length, len);
-
-            assertArrayEquals(input, decOutput);
-        }
-    }
-
-    private void testGcmDecryption(final String kHex, final String pHex, final String ivHex, final String aadHex,
-                                   final String cHex, final String tHex) throws Exception {
-
-        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
-        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
-        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
-
-        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
-        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);
-
-        final byte[] input = cipherBytes;
-        final byte[] output = new byte[plainBytes.length];
-
-        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
-
-            final Key key = AES.newSecretKeySpec(keyBytes);
-
-            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
-            c.init(Cipher.DECRYPT_MODE, key, iv);
-            c.updateAAD(aad);
-            c.doFinal(input, 0, input.length, output, 0);
-
-            assertArrayEquals(plainBytes, output);
-        }
-    }
-
-    private void testGcmReturnDataAfterTagVerified(final String kHex, final String pHex, final String ivHex, final String aadHex,
-                                                   final String cHex, final String tHex) throws Exception {
-
-        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
-        final byte[] plainBytes = DatatypeConverter.parseHexBinary(pHex);
-        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
-
-        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
-        final byte[] cipherBytes = DatatypeConverter.parseHexBinary(cHex+tHex);
-
-        final byte[] input = cipherBytes;
-        final byte[] output = new byte[plainBytes.length];
-
-        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
-
-            final Key key = AES.newSecretKeySpec(keyBytes);
-
-            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
-            c.init(Cipher.DECRYPT_MODE, key, iv);
-            c.updateAAD(aad);
-
-            // only return recovered data after tag is successfully verified
-            int len = c.update(input, 0, input.length, output, 0);
-            assertEquals(len, 0);
-            len += c.doFinal(input, input.length, 0, output, 0);
-            assertEquals(plainBytes.length, len);
-
-            assertArrayEquals(plainBytes, output);
-        }
-    }
-
-    private void testGcmByteBuffer(final String kHex, final String pHex, final String ivHex, final String aadHex,
-                                   final String cHex, final String tHex) throws Exception {
-
-        final byte[] keyBytes = DatatypeConverter.parseHexBinary(kHex);
-        final byte[] plainText = DatatypeConverter.parseHexBinary(pHex);
-        final byte[] ivBytes = DatatypeConverter.parseHexBinary(ivHex);
-        final byte[] aad = DatatypeConverter.parseHexBinary(aadHex);
-        final byte[] cipherText = DatatypeConverter.parseHexBinary(cHex+tHex);
-
-        final byte[] encOutput = new byte[cipherText.length];
-        final byte[] decOutput = new byte[plainText.length];
-
-        final ByteBuffer bfAAD = ByteBuffer.allocateDirect(aad.length);
-        bfAAD.put(aad);
-
-        final ByteBuffer bfPlainText;
-        final ByteBuffer bfCipherText;
-        bfPlainText = ByteBuffer.allocateDirect(plainText.length);
-        bfCipherText = ByteBuffer.allocateDirect(encOutput.length);
-
-        // Encryption -------------------
-        try (final CryptoCipher c = Utils.getCipherInstance(transformation, props)) {
-            final Key key = AES.newSecretKeySpec(keyBytes);
-            final GCMParameterSpec iv = new GCMParameterSpec(128, ivBytes);
-            c.init(Cipher.ENCRYPT_MODE, key, iv);
-
-            bfAAD.flip();
-            c.updateAAD(bfAAD);
-
-            bfPlainText.put(plainText);
-            bfPlainText.flip();
-            bfCipherText.position(0);
-
-            c.doFinal(bfPlainText, bfCipherText);
-
-            bfCipherText.flip();
-            bfCipherText.get(encOutput);
-            assertArrayEquals(cipherText, encOutput);
-        }
-
-        // Decryption -------------------
-        try (final CryptoCipher dec = Utils.getCipherInstance(transformation, props)) {
-            dec.init(Cipher.DECRYPT_MODE, AES.newSecretKeySpec(keyBytes), new GCMParameterSpec(128, ivBytes));
-            bfAAD.flip();
-            dec.updateAAD(bfAAD);
-            bfCipherText.clear();
-            bfPlainText.clear();
-            bfCipherText.put(cipherText);
-            bfCipherText.flip();
-            dec.doFinal(bfCipherText, bfPlainText);
-            bfPlainText.flip();
-            bfPlainText.get(decOutput);
-            assertArrayEquals(plainText, decOutput);
-        }
-    }
-
-    private void initTestData() {
-
-        final int casesNumber = 4;
-
-        kHex = new String[casesNumber];
-        pHex = new String[casesNumber];
-        ivHex = new String[casesNumber];
-        aadHex = new String[casesNumber];
-        cHex = new String[casesNumber];
-        tHex = new String[casesNumber];
-
-        // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
-        // http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
-        // NIST Case2  -----------------------------
-        // key length:          16 bytes
-        // plain text length:   16 bytes
-        // iv length:           12 bytes
-        // aad length:          0 bytes
-        kHex[0] = "00000000000000000000000000000000";
-        pHex[0] = "00000000000000000000000000000000";
-        ivHex[0]  = "000000000000000000000000";
-        aadHex[0]  = "";
-        cHex[0]  = "0388dace60b6a392f328c2b971b2fe78";
-        tHex[0]  = "ab6e47d42cec13bdf53a67b21257bddf";
-
-        // NIST Case4 ---------------------------------
-        // key length:          16 bytes
-        // plain text length:   60 bytes
-        // iv length:           12 bytes
-        // aad length:          20 bytes
-        kHex[1] = "feffe9928665731c6d6a8f9467308308";
-        pHex[1] = "d9313225f88406e5a55909c5aff5269a"
-                + "86a7a9531534f7da2e4c303d8a318a72"
-                + "1c3c0c95956809532fcf0e2449a6b525"
-                + "b16aedf5aa0de657ba637b39";
-        ivHex[1] = "cafebabefacedbaddecaf888";
-        aadHex[1] = "feedfacedeadbeeffeedfacedeadbeef"
-                + "abaddad2";
-        cHex[1] = "42831ec2217774244b7221b784d0d49c"
-                + "e3aa212f2c02a4e035c17e2329aca12e"
-                + "21d514b25466931c7d8f6a5aac84aa05"
-                + "1ba30b396a0aac973d58e091";
-        tHex[1] = "5bc94fbc3221a5db94fae95ae7121a47";
-
-        // NIST Case5 ---------------------------------
-        // key length:          16 bytes
-        // plain text length:   60 bytes
-        // iv length:           8 bytes
-        // aad length:          20 bytes
-        kHex[2] = "feffe9928665731c6d6a8f9467308308";
-        pHex[2] = "d9313225f88406e5a55909c5aff5269a"
-                + "86a7a9531534f7da2e4c303d8a318a72"
-                + "1c3c0c95956809532fcf0e2449a6b525"
-                + "b16aedf5aa0de657ba637b39";
-        ivHex[2] ="cafebabefacedbad"; // 64bits < 96bits
-        aadHex[2]="feedfacedeadbeeffeedfacedeadbeef"
-                + "abaddad2";
-        cHex[2] = "61353b4c2806934a777ff51fa22a4755"
-                + "699b2a714fcdc6f83766e5f97b6c7423"
-                + "73806900e49f24b22b097544d4896b42"
-                + "4989b5e1ebac0f07c23f4598";
-        tHex[2] = "3612d2e79e3b0785561be14aaca2fccb";
-
-        // NIST Case6 ---------------------------------
-        // key length:          16 bytes
-        // plain text length:   60 bytes
-        // iv length:           60 bytes
-        // aad length:          20 bytes
-        kHex[3] = "feffe9928665731c6d6a8f9467308308";
-        pHex[3] = "d9313225f88406e5a55909c5aff5269a"
-                + "86a7a9531534f7da2e4c303d8a318a72"
-                + "1c3c0c95956809532fcf0e2449a6b525"
-                + "b16aedf5aa0de657ba637b39";
-        ivHex[3] = "9313225df88406e555909c5aff5269aa"
-                + "6a7a9538534f7da1e4c303d2a318a728"
-                + "c3c0c95156809539fcf0e2429a6b5254"
-                + "16aedbf5a0de6a57a637b39b"; // > 96bits
-        aadHex[3] = "feedfacedeadbeeffeedfacedeadbeef"
-                + "abaddad2";
-        cHex[3] = "8ce24998625615b603a033aca13fb894"
-                + "be9112a5c3a211a8ba262a3cca7e2ca7"
-                + "01e4a9a4fba43c90ccdcb281d48c7c6f"
-                + "d62875d2aca417034c34aee5";
-        tHex[3] = "619cc5aefffe0bfa462af43c1699d050";
-    }
 }
diff --git a/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java
index 81f39ba..edd300e 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/JceCipherTest.java
@@ -32,15 +32,6 @@ public class JceCipherTest extends AbstractCipherTest {
 
     private static final int MAX_KEY_LEN_LOWER_BOUND = 256;
 
-    @Override
-    public void init() {
-        transformations = new String[] {
-                AES.CBC_NO_PADDING,
-                AES.CBC_PKCS5_PADDING,
-                AES.CTR_NO_PADDING};
-        cipherClass = JCE_CIPHER_CLASSNAME;
-    }
-
     @BeforeAll
     public static void checkJceUnlimitedStrength() throws NoSuchAlgorithmException {
         final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES.ALGORITHM);
@@ -52,4 +43,13 @@ public class JceCipherTest extends AbstractCipherTest {
                         "Strength Jurisdiction Policy Files.",
                         MAX_KEY_LEN_LOWER_BOUND, maxKeyLen));
     }
+
+    @Override
+    public void init() {
+        transformations = new String[] {
+                AES.CBC_NO_PADDING,
+                AES.CBC_PKCS5_PADDING,
+                AES.CTR_NO_PADDING};
+        cipherClass = JCE_CIPHER_CLASSNAME;
+    }
 }
diff --git a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
index e4f2c12..e326ad7 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
@@ -42,6 +42,10 @@ import org.junit.jupiter.api.Timeout;
 
 public class OpenSslCipherTest extends AbstractCipherTest {
 
+    private ByteBuffer dummyBuffer() {
+        return ByteBuffer.allocateDirect(8);
+    }
+
     @Override
     public void init() {
         assumeTrue(OpenSsl.getLoadingFailureReason() == null);
@@ -53,48 +57,26 @@ public class OpenSslCipherTest extends AbstractCipherTest {
     }
 
     @Test
-    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
-    public void testInvalidPadding() {
-        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
-        assertThrows(NoSuchPaddingException.class,
-                () -> OpenSsl.getInstance("AES/CTR/NoPadding2"));
-    }
-
-    @Test
-    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
-    public void testInvalidMode() {
-        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
-        assertThrows(NoSuchAlgorithmException.class,
-                () -> OpenSsl.getInstance("AES/CTR2/NoPadding"));
-    }
-
-    @Test
-    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
-    public void testUpdateArguments() throws Exception {
-        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
-        final OpenSsl cipher = OpenSsl
-                .getInstance(AES.CTR_NO_PADDING);
-        assertNotNull(cipher);
-
-        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV));
-
-        // Require direct buffers
-        ByteBuffer input = ByteBuffer.allocate(1024);
-        ByteBuffer output = ByteBuffer.allocate(1024);
+    public void testCipherLifecycle() throws Exception {
+        try (OpenSslCipher cipher = new OpenSslCipher(new Properties(), AES.CTR_NO_PADDING)) {
 
-        final ByteBuffer finalInput = input;
-        final ByteBuffer finalOutput = output;
-        Exception ex = assertThrows(IllegalArgumentException.class, () -> cipher.update(finalInput, finalOutput));
-        assertTrue(ex.getMessage().contains("Direct buffers are required"));
+            assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer()));
+            cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY),
+                new IvParameterSpec(IV));
+            cipher.update(dummyBuffer(), dummyBuffer());
 
-        // Output buffer length should be sufficient to store output data
-        input = ByteBuffer.allocateDirect(1024);
-        output = ByteBuffer.allocateDirect(1000);
-        final ByteBuffer finalInput1 = input;
-        final ByteBuffer finalOutput1 = output;
-        ex = assertThrows(ShortBufferException.class, () -> cipher.update(finalInput1, finalOutput1));
-        assertTrue(ex.getMessage().contains("Output buffer is not sufficient"));
+            assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(new byte[1]),
+                    new IvParameterSpec(IV)));
+            // Should keep working with previous init parameters.
+            cipher.update(dummyBuffer(), dummyBuffer());
+            cipher.doFinal(dummyBuffer(), dummyBuffer());
+            cipher.close();
 
+            assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer()));
+            cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY),
+                new IvParameterSpec(IV));
+            cipher.update(dummyBuffer(), dummyBuffer());
+        }
     }
 
     @Test
@@ -115,22 +97,6 @@ public class OpenSslCipherTest extends AbstractCipherTest {
         assertTrue(ex.getMessage().contains("Direct buffer is required"));
     }
 
-    @Override
-    @Test
-    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
-    public void testInvalidKey() throws Exception {
-        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
-        final OpenSsl cipher = OpenSsl
-                .getInstance(AES.CTR_NO_PADDING);
-        assertNotNull(cipher);
-
-        final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-                0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
-
-        assertThrows(InvalidKeyException.class,
-                () -> cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new IvParameterSpec(IV)));
-    }
-
     @Override
     @Test
     @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
@@ -159,31 +125,65 @@ public class OpenSslCipherTest extends AbstractCipherTest {
                 () ->  cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new GCMParameterSpec(IV.length, IV)));
     }
 
+    @Override
     @Test
-    public void testCipherLifecycle() throws Exception {
-        try (OpenSslCipher cipher = new OpenSslCipher(new Properties(), AES.CTR_NO_PADDING)) {
+    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
+    public void testInvalidKey() throws Exception {
+        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
+        final OpenSsl cipher = OpenSsl
+                .getInstance(AES.CTR_NO_PADDING);
+        assertNotNull(cipher);
 
-            assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer()));
-            cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY),
-                new IvParameterSpec(IV));
-            cipher.update(dummyBuffer(), dummyBuffer());
+        final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+                0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
 
-            assertThrows(InvalidKeyException.class, () -> cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(new byte[1]),
-                    new IvParameterSpec(IV)));
-            // Should keep working with previous init parameters.
-            cipher.update(dummyBuffer(), dummyBuffer());
-            cipher.doFinal(dummyBuffer(), dummyBuffer());
-            cipher.close();
+        assertThrows(InvalidKeyException.class,
+                () -> cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new IvParameterSpec(IV)));
+    }
 
-            assertThrows(IllegalStateException.class, () -> cipher.update(dummyBuffer(), dummyBuffer()));
-            cipher.init(OpenSsl.ENCRYPT_MODE, AES.newSecretKeySpec(KEY),
-                new IvParameterSpec(IV));
-            cipher.update(dummyBuffer(), dummyBuffer());
-        }
+    @Test
+    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
+    public void testInvalidMode() {
+        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
+        assertThrows(NoSuchAlgorithmException.class,
+                () -> OpenSsl.getInstance("AES/CTR2/NoPadding"));
     }
 
-    private ByteBuffer dummyBuffer() {
-        return ByteBuffer.allocateDirect(8);
+    @Test
+    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
+    public void testInvalidPadding() {
+        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
+        assertThrows(NoSuchPaddingException.class,
+                () -> OpenSsl.getInstance("AES/CTR/NoPadding2"));
+    }
+
+    @Test
+    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
+    public void testUpdateArguments() throws Exception {
+        assumeTrue(OpenSsl.getLoadingFailureReason() == null);
+        final OpenSsl cipher = OpenSsl
+                .getInstance(AES.CTR_NO_PADDING);
+        assertNotNull(cipher);
+
+        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV));
+
+        // Require direct buffers
+        ByteBuffer input = ByteBuffer.allocate(1024);
+        ByteBuffer output = ByteBuffer.allocate(1024);
+
+        final ByteBuffer finalInput = input;
+        final ByteBuffer finalOutput = output;
+        Exception ex = assertThrows(IllegalArgumentException.class, () -> cipher.update(finalInput, finalOutput));
+        assertTrue(ex.getMessage().contains("Direct buffers are required"));
+
+        // Output buffer length should be sufficient to store output data
+        input = ByteBuffer.allocateDirect(1024);
+        output = ByteBuffer.allocateDirect(1000);
+        final ByteBuffer finalInput1 = input;
+        final ByteBuffer finalOutput1 = output;
+        ex = assertThrows(ShortBufferException.class, () -> cipher.update(finalInput1, finalOutput1));
+        assertTrue(ex.getMessage().contains("Output buffer is not sufficient"));
+
     }
 
 }
diff --git a/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java b/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java
index a230134..8519e2d 100644
--- a/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java
+++ b/src/test/java/org/apache/commons/crypto/examples/CipherByteArrayExample.java
@@ -36,6 +36,16 @@ import org.apache.commons.crypto.utils.Utils;
  */
 public class CipherByteArrayExample {
 
+    /**
+     * Converts String to UTF8 bytes
+     *
+     * @param input the input string
+     * @return UTF8 bytes
+     */
+    private static byte[] getUTF8Bytes(final String input) {
+        return input.getBytes(StandardCharsets.UTF_8);
+    }
+
     public static void main(final String[] args) throws Exception {
 
         final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456"));
@@ -85,14 +95,4 @@ public class CipherByteArrayExample {
         }
     }
 
-    /**
-     * Converts String to UTF8 bytes
-     *
-     * @param input the input string
-     * @return UTF8 bytes
-     */
-    private static byte[] getUTF8Bytes(final String input) {
-        return input.getBytes(StandardCharsets.UTF_8);
-    }
-
 }
diff --git a/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java b/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java
index 7dd241e..a6de0d2 100644
--- a/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java
+++ b/src/test/java/org/apache/commons/crypto/examples/CipherByteBufferExample.java
@@ -35,6 +35,29 @@ import org.apache.commons.crypto.utils.Utils;
  */
 public class CipherByteBufferExample {
 
+    /**
+     * Converts ByteBuffer to String
+     *
+     * @param buffer input byte buffer
+     * @return the converted string
+     */
+    private static String asString(final ByteBuffer buffer) {
+        final ByteBuffer copy = buffer.duplicate();
+        final byte[] bytes = new byte[copy.remaining()];
+        copy.get(bytes);
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Converts String to UTF8 bytes
+     *
+     * @param input the input string
+     * @return UTF8 bytes
+     */
+    private static byte[] getUTF8Bytes(final String input) {
+        return input.getBytes(StandardCharsets.UTF_8);
+    }
+
     public static void main(final String[] args) throws Exception {
         final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456"));
         final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456"));
@@ -82,27 +105,4 @@ public class CipherByteBufferExample {
         }
     }
 
-    /**
-     * Converts String to UTF8 bytes
-     *
-     * @param input the input string
-     * @return UTF8 bytes
-     */
-    private static byte[] getUTF8Bytes(final String input) {
-        return input.getBytes(StandardCharsets.UTF_8);
-    }
-
-    /**
-     * Converts ByteBuffer to String
-     *
-     * @param buffer input byte buffer
-     * @return the converted string
-     */
-    private static String asString(final ByteBuffer buffer) {
-        final ByteBuffer copy = buffer.duplicate();
-        final byte[] bytes = new byte[copy.remaining()];
-        copy.get(bytes);
-        return new String(bytes, StandardCharsets.UTF_8);
-    }
-
 }
diff --git a/src/test/java/org/apache/commons/crypto/examples/StreamExample.java b/src/test/java/org/apache/commons/crypto/examples/StreamExample.java
index 5b7be6e..75df626 100644
--- a/src/test/java/org/apache/commons/crypto/examples/StreamExample.java
+++ b/src/test/java/org/apache/commons/crypto/examples/StreamExample.java
@@ -37,6 +37,16 @@ import org.apache.commons.crypto.utils.AES;
  */
 public class StreamExample {
 
+    /**
+     * Converts String to UTF8 bytes
+     *
+     * @param input the input string
+     * @return UTF8 bytes
+     */
+    private static byte[] getUTF8Bytes(final String input) {
+        return input.getBytes(StandardCharsets.UTF_8);
+    }
+
     public static void main(final String []args) throws IOException {
         final SecretKeySpec key = AES.newSecretKeySpec(getUTF8Bytes("1234567890123456"));
         final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456"));
@@ -70,14 +80,4 @@ public class StreamExample {
         }
     }
 
-    /**
-     * Converts String to UTF8 bytes
-     *
-     * @param input the input string
-     * @return UTF8 bytes
-     */
-    private static byte[] getUTF8Bytes(final String input) {
-        return input.getBytes(StandardCharsets.UTF_8);
-    }
-
 }
diff --git a/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java b/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java
index 0922fc3..631a201 100644
--- a/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java
+++ b/src/test/java/org/apache/commons/crypto/jna/AbstractCipherJnaStreamTest.java
@@ -37,16 +37,6 @@ public abstract class AbstractCipherJnaStreamTest extends AbstractCipherStreamTe
         assumeTrue(OpenSslJna.isEnabled());
     }
 
-    /** Test skip. */
-    @Override
-    @Test
-    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
-    public void testSkip() throws Exception {
-        doSkipTest(CIPHER_OPENSSL_JNA, false);
-
-        doSkipTest(CIPHER_OPENSSL_JNA, true);
-    }
-
     /** Test byte buffer read with different buffer size. */
     @Override
     @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
@@ -81,4 +71,14 @@ public abstract class AbstractCipherJnaStreamTest extends AbstractCipherStreamTe
         doReadWriteTest(count, AbstractCipherTest.JCE_CIPHER_CLASSNAME, CIPHER_OPENSSL_JNA, iv);
         doReadWriteTest(count, CIPHER_OPENSSL_JNA, AbstractCipherTest.JCE_CIPHER_CLASSNAME, iv);
     }
+
+    /** Test skip. */
+    @Override
+    @Test
+    @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
+    public void testSkip() throws Exception {
+        doSkipTest(CIPHER_OPENSSL_JNA, false);
+
+        doSkipTest(CIPHER_OPENSSL_JNA, true);
+    }
 }
diff --git a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java
index 4678400..9d0f308 100644
--- a/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java
+++ b/src/test/java/org/apache/commons/crypto/jna/OpenSslJnaCryptoRandomTest.java
@@ -30,11 +30,6 @@ import org.junit.jupiter.api.BeforeEach;
 
 public class OpenSslJnaCryptoRandomTest extends AbstractRandomTest {
 
-    @BeforeEach
-    public void init() {
-        Assumptions.assumeTrue(OpenSslJna.isEnabled());
-    }
-
     @Override
     public CryptoRandom getCryptoRandom() throws GeneralSecurityException {
         final Properties props = new Properties();
@@ -44,4 +39,9 @@ public class OpenSslJnaCryptoRandomTest extends AbstractRandomTest {
         return random;
     }
 
+    @BeforeEach
+    public void init() {
+        Assumptions.assumeTrue(OpenSslJna.isEnabled());
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java b/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java
index 27bc259..feb7bec 100644
--- a/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java
+++ b/src/test/java/org/apache/commons/crypto/jna/PositionedCryptoInputStreamJnaTest.java
@@ -28,14 +28,14 @@ import org.junit.jupiter.api.Test;
  */
 public class PositionedCryptoInputStreamJnaTest extends PositionedCryptoInputStreamTest {
 
-    @BeforeEach
-    public void init() {
-        assumeTrue(OpenSslJna.isEnabled());
-    }
-
     @Test
     public void doTest() throws Exception {
         testCipher(OpenSslJnaCipher.class.getName());
     }
 
+    @BeforeEach
+    public void init() {
+        assumeTrue(OpenSslJna.isEnabled());
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java b/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java
index 0981d40..dca853a 100644
--- a/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java
+++ b/src/test/java/org/apache/commons/crypto/random/AbstractRandomTest.java
@@ -29,6 +29,20 @@ import org.junit.jupiter.api.Timeout;
 
 public abstract class AbstractRandomTest {
 
+    /**
+     * Test will timeout if secure random implementation always returns a constant value.
+     */
+    private void checkRandomBytes(final CryptoRandom random, final int len) {
+        final byte[] bytes = new byte[len];
+        final byte[] bytes1 = new byte[len];
+        random.nextBytes(bytes);
+        random.nextBytes(bytes1);
+
+        while (Arrays.equals(bytes1, new byte[len]) || Arrays.equals(bytes, bytes1)) {
+            random.nextBytes(bytes1);
+        }
+    }
+
     public abstract CryptoRandom getCryptoRandom() throws GeneralSecurityException;
 
     @Test
@@ -71,18 +85,4 @@ public abstract class AbstractRandomTest {
 
         }
     }
-
-    /**
-     * Test will timeout if secure random implementation always returns a constant value.
-     */
-    private void checkRandomBytes(final CryptoRandom random, final int len) {
-        final byte[] bytes = new byte[len];
-        final byte[] bytes1 = new byte[len];
-        random.nextBytes(bytes);
-        random.nextBytes(bytes1);
-
-        while (Arrays.equals(bytes1, new byte[len]) || Arrays.equals(bytes, bytes1)) {
-            random.nextBytes(bytes1);
-        }
-    }
 }
diff --git a/src/test/java/org/apache/commons/crypto/random/FailingRandom.java b/src/test/java/org/apache/commons/crypto/random/FailingRandom.java
index 3f73709..01c8cdf 100644
--- a/src/test/java/org/apache/commons/crypto/random/FailingRandom.java
+++ b/src/test/java/org/apache/commons/crypto/random/FailingRandom.java
@@ -21,6 +21,8 @@ import java.util.Properties;
 
 class FailingRandom implements CryptoRandom {
 
+    public static native void NoSuchMethod();
+
     /** Should fail with NoSuchMethodException. */
     FailingRandom(final Properties props) {
         NoSuchMethod();
@@ -35,6 +37,4 @@ class FailingRandom implements CryptoRandom {
     public void nextBytes(final byte[] bytes) {
         // empty
     }
-
-    public static native void NoSuchMethod();
 }
diff --git a/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java
index 390f11e..f5597c0 100644
--- a/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java
+++ b/src/test/java/org/apache/commons/crypto/stream/CtrCryptoStreamTest.java
@@ -45,53 +45,24 @@ import org.junit.jupiter.api.Timeout;
 
 public class CtrCryptoStreamTest extends AbstractCipherStreamTest {
 
-    @Override
-    public void setUp() {
-        transformation = AES.CTR_NO_PADDING;
-    }
-
-    @Override
-    protected CtrCryptoInputStream newCryptoInputStream(
-            final ByteArrayInputStream bais, final CryptoCipher cipher, final int bufferSize,
-            final byte[] iv, final boolean withChannel) throws IOException {
-        if (withChannel) {
-            return new CtrCryptoInputStream(Channels.newChannel(bais), cipher,
-                    bufferSize, key, iv);
-        }
-        return new CtrCryptoInputStream(bais, cipher, bufferSize, key, iv);
-    }
+    protected void doDecryptTest(final String cipherClass, final boolean withChannel)
+            throws IOException {
 
-    @Override
-    protected CryptoInputStream newCryptoInputStream(final String transformation, final Properties props,
-            final ByteArrayInputStream bais, final byte[] key, final AlgorithmParameterSpec params,
-            final boolean withChannel) throws IOException {
-        if (withChannel) {
-            return new CtrCryptoInputStream(props, Channels.newChannel(bais), key,
-                    ((IvParameterSpec)params).getIV());
-        }
-        return new CtrCryptoInputStream(props, bais, key, ((IvParameterSpec)params).getIV());
-    }
+        final CtrCryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encData),
+                getCipher(cipherClass), defaultBufferSize, iv, withChannel);
 
-    @Override
-    protected CtrCryptoOutputStream newCryptoOutputStream(
-            final ByteArrayOutputStream baos, final CryptoCipher cipher, final int bufferSize,
-            final byte[] iv, final boolean withChannel) throws IOException {
-        if (withChannel) {
-            return new CtrCryptoOutputStream(Channels.newChannel(baos), cipher,
-                    bufferSize, key, iv);
-        }
-        return new CtrCryptoOutputStream(baos, cipher, bufferSize, key, iv);
-    }
+        final ByteBuffer buf = ByteBuffer.allocateDirect(dataLen);
+        buf.put(encData);
+        buf.rewind();
+        in.decrypt(buf, 0, dataLen);
+        final byte[] readData = new byte[dataLen];
+        final byte[] expectedData = new byte[dataLen];
+        buf.get(readData);
+        System.arraycopy(data, 0, expectedData, 0, dataLen);
+        assertArrayEquals(readData, expectedData);
+        final Exception ex = assertThrows(IOException.class, () -> in.decryptBuffer(buf));
+        assertEquals(ex.getCause().getClass(), ShortBufferException.class);
 
-    @Override
-    protected CtrCryptoOutputStream newCryptoOutputStream(final String transformation,
-            final Properties props, final ByteArrayOutputStream baos, final byte[] key,
-            final AlgorithmParameterSpec params, final boolean withChannel) throws IOException {
-        if (withChannel) {
-            return new CtrCryptoOutputStream(props, Channels.newChannel(baos), key,
-                    ((IvParameterSpec)params).getIV());
-        }
-        return new CtrCryptoOutputStream(props, baos, key, ((IvParameterSpec)params).getIV());
     }
 
     @Override
@@ -143,6 +114,55 @@ public class CtrCryptoStreamTest extends AbstractCipherStreamTest {
         out.close();
     }
 
+    @Override
+    protected CtrCryptoInputStream newCryptoInputStream(
+            final ByteArrayInputStream bais, final CryptoCipher cipher, final int bufferSize,
+            final byte[] iv, final boolean withChannel) throws IOException {
+        if (withChannel) {
+            return new CtrCryptoInputStream(Channels.newChannel(bais), cipher,
+                    bufferSize, key, iv);
+        }
+        return new CtrCryptoInputStream(bais, cipher, bufferSize, key, iv);
+    }
+
+    @Override
+    protected CryptoInputStream newCryptoInputStream(final String transformation, final Properties props,
+            final ByteArrayInputStream bais, final byte[] key, final AlgorithmParameterSpec params,
+            final boolean withChannel) throws IOException {
+        if (withChannel) {
+            return new CtrCryptoInputStream(props, Channels.newChannel(bais), key,
+                    ((IvParameterSpec)params).getIV());
+        }
+        return new CtrCryptoInputStream(props, bais, key, ((IvParameterSpec)params).getIV());
+    }
+
+    @Override
+    protected CtrCryptoOutputStream newCryptoOutputStream(
+            final ByteArrayOutputStream baos, final CryptoCipher cipher, final int bufferSize,
+            final byte[] iv, final boolean withChannel) throws IOException {
+        if (withChannel) {
+            return new CtrCryptoOutputStream(Channels.newChannel(baos), cipher,
+                    bufferSize, key, iv);
+        }
+        return new CtrCryptoOutputStream(baos, cipher, bufferSize, key, iv);
+    }
+
+    @Override
+    protected CtrCryptoOutputStream newCryptoOutputStream(final String transformation,
+            final Properties props, final ByteArrayOutputStream baos, final byte[] key,
+            final AlgorithmParameterSpec params, final boolean withChannel) throws IOException {
+        if (withChannel) {
+            return new CtrCryptoOutputStream(props, Channels.newChannel(baos), key,
+                    ((IvParameterSpec)params).getIV());
+        }
+        return new CtrCryptoOutputStream(props, baos, key, ((IvParameterSpec)params).getIV());
+    }
+
+    @Override
+    public void setUp() {
+        transformation = AES.CTR_NO_PADDING;
+    }
+
     @Test
     @Timeout(value = 120000, unit = TimeUnit.MILLISECONDS)
     public void testDecrypt() throws Exception {
@@ -152,24 +172,4 @@ public class CtrCryptoStreamTest extends AbstractCipherStreamTest {
         doDecryptTest(AbstractCipherTest.JCE_CIPHER_CLASSNAME, true);
         doDecryptTest(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME, true);
     }
-
-    protected void doDecryptTest(final String cipherClass, final boolean withChannel)
-            throws IOException {
-
-        final CtrCryptoInputStream in = newCryptoInputStream(new ByteArrayInputStream(encData),
-                getCipher(cipherClass), defaultBufferSize, iv, withChannel);
-
-        final ByteBuffer buf = ByteBuffer.allocateDirect(dataLen);
-        buf.put(encData);
-        buf.rewind();
-        in.decrypt(buf, 0, dataLen);
-        final byte[] readData = new byte[dataLen];
-        final byte[] expectedData = new byte[dataLen];
-        buf.get(readData);
-        System.arraycopy(data, 0, expectedData, 0, dataLen);
-        assertArrayEquals(readData, expectedData);
-        final Exception ex = assertThrows(IOException.class, () -> in.decryptBuffer(buf));
-        assertEquals(ex.getCause().getClass(), ShortBufferException.class);
-
-    }
 }
diff --git a/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java
index e7f99e3..3c2dc00 100644
--- a/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java
+++ b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java
@@ -46,6 +46,91 @@ import org.junit.jupiter.api.Test;
 
 public class PositionedCryptoInputStreamTest {
 
+    static class PositionedInputForTest implements Input {
+
+        final byte[] data;
+        long pos;
+        final long count;
+
+        public PositionedInputForTest(final byte[] data) {
+            this.data = data;
+            this.pos = 0;
+            this.count = data.length;
+        }
+
+        @Override
+        public int available() {
+            return (int) (count - pos);
+        }
+
+        @Override
+        public void close() {
+        }
+
+        @Override
+        public int read(final ByteBuffer dst) {
+            final int remaining = (int) (count - pos);
+            if (remaining <= 0) {
+                return -1;
+            }
+
+            final int length = Math.min(dst.remaining(), remaining);
+            dst.put(data, (int) pos, length);
+            pos += length;
+            return length;
+        }
+
+        @Override
+        public int read(final long position, final byte[] buffer, final int offset, int length) {
+            Objects.requireNonNull(buffer, "buffer");
+            if (offset < 0 || length < 0
+                    || length > buffer.length - offset) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            if (position < 0 || position >= count) {
+                return -1;
+            }
+
+            final long avail = count - position;
+            if (length > avail) {
+                length = (int) avail;
+            }
+            if (length <= 0) {
+                return 0;
+            }
+            System.arraycopy(data, (int) position, buffer, offset, length);
+            return length;
+        }
+
+        @Override
+        public void seek(final long position) throws IOException {
+            if (pos < 0) {
+                throw new IOException("Negative seek offset");
+            }
+            if (position >= 0 && position < count) {
+                pos = position;
+            } else {
+                // to the end of file
+                pos = count;
+            }
+        }
+
+        @Override
+        public long skip(long n) {
+            if (n <= 0) {
+                return 0;
+            }
+
+            final long remaining = count - pos;
+            if (remaining < n) {
+                n = remaining;
+            }
+            pos += n;
+
+            return n;
+        }
+    }
     private final int dataLen = 20000;
     private final byte[] testData = new byte[dataLen];
     private byte[] encData;
@@ -57,6 +142,7 @@ public class PositionedCryptoInputStreamTest {
     private final int bufferSizeMore = bufferSize + 1;
     private final int length = 1024;
     private final int lengthLess = length - 1;
+
     private final int lengthMore = length + 1;
 
     private final String transformation = AES.CTR_NO_PADDING;
@@ -70,59 +156,22 @@ public class PositionedCryptoInputStreamTest {
         prepareData();
     }
 
-    private void prepareData() throws IOException {
-        final CryptoCipher cipher;
-        try {
-            cipher = (CryptoCipher) ReflectionUtils.newInstance(
-                    ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props,
-                    transformation);
-        } catch (final ClassNotFoundException cnfe) {
-            throw new IOException("Illegal crypto cipher!");
-        }
-
-        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        // encryption data
-        try (final OutputStream out = new CryptoOutputStream(baos, cipher, bufferSize,
-                AES.newSecretKeySpec(key), new IvParameterSpec(iv))) {
-            out.write(testData);
-            out.flush();
-        }
-        encData = baos.toByteArray();
-    }
-
-    private PositionedCryptoInputStream getCryptoInputStream(
-            final CryptoCipher cipher, final int bufferSize) throws IOException {
-        return new PositionedCryptoInputStream(props, new PositionedInputForTest(
-                Arrays.copyOf(encData, encData.length)), cipher, bufferSize,
-                key, iv, 0);
-    }
-
-    private PositionedCryptoInputStream getCryptoInputStream(final int streamOffset)
-            throws IOException {
-        return new PositionedCryptoInputStream(props, new PositionedInputForTest(
-                Arrays.copyOf(encData, encData.length)), key, iv, streamOffset);
-    }
-
-    @Test
-    public void doTestJCE() throws Exception {
-        testCipher(AbstractCipherTest.JCE_CIPHER_CLASSNAME);
-    }
-
-    @Test
-    public void doTestJNI() throws Exception {
-        assumeTrue(Crypto.isNativeCodeLoaded());
-        testCipher(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME);
+    // compare the data from pos with length and data2 from 0 with length
+    private void compareByteArray(final byte[] data1, final int pos, final byte[] data2,
+            final int length) {
+        final byte[] expectedData = new byte[length];
+        final byte[] realData = new byte[length];
+        // get the expected data with the position and length
+        System.arraycopy(data1, pos, expectedData, 0, length);
+        // get the real data
+        System.arraycopy(data2, 0, realData, 0, length);
+        assertArrayEquals(expectedData, realData);
     }
 
-    protected void testCipher(final String cipherClass) throws Exception {
-        doPositionedReadTests(cipherClass);
-        doPositionedReadTests();
-        doReadFullyTests(cipherClass);
-        doReadFullyTests();
-        doSeekTests(cipherClass);
-        doSeekTests();
+    private void doMultipleReadTest() throws Exception{
+        final PositionedCryptoInputStream in = getCryptoInputStream(0);
+        final String cipherClass = in.getCipher().getClass().getName();
         doMultipleReadTest(cipherClass);
-        doMultipleReadTest();
     }
 
     // when there are multiple positioned read actions and one read action,
@@ -157,10 +206,10 @@ public class PositionedCryptoInputStreamTest {
         }
     }
 
-    private void doMultipleReadTest() throws Exception{
-        final PositionedCryptoInputStream in = getCryptoInputStream(0);
-        final String cipherClass = in.getCipher().getClass().getName();
-        doMultipleReadTest(cipherClass);
+    private void doPositionedReadTests() throws Exception {
+    	final PositionedCryptoInputStream in = getCryptoInputStream(0);
+    	final String cipherClass = in.getCipher().getClass().getName();
+    	doPositionedReadTests(cipherClass);
     }
 
     private void doPositionedReadTests(final String cipherClass) throws Exception {
@@ -180,10 +229,10 @@ public class PositionedCryptoInputStreamTest {
         testPositionedReadNone(cipherClass, dataLen, length, bufferSize);
     }
 
-    private void doPositionedReadTests() throws Exception {
-    	final PositionedCryptoInputStream in = getCryptoInputStream(0);
-    	final String cipherClass = in.getCipher().getClass().getName();
-    	doPositionedReadTests(cipherClass);
+    private void doReadFullyTests() throws Exception {
+        final PositionedCryptoInputStream in = getCryptoInputStream(0);
+        final String cipherClass = in.getCipher().getClass().getName();
+        doReadFullyTests(cipherClass);
     }
 
     private void doReadFullyTests(final String cipherClass) throws Exception {
@@ -202,10 +251,10 @@ public class PositionedCryptoInputStreamTest {
                 bufferSize);
     }
 
-    private void doReadFullyTests() throws Exception {
+    private void doSeekTests() throws Exception{
         final PositionedCryptoInputStream in = getCryptoInputStream(0);
         final String cipherClass = in.getCipher().getClass().getName();
-        doReadFullyTests(cipherClass);
+        doSeekTests(cipherClass);
     }
 
     private void doSeekTests(final String cipherClass) throws Exception {
@@ -219,34 +268,69 @@ public class PositionedCryptoInputStreamTest {
         testSeekFailed(cipherClass, -1, bufferSize);
     }
 
-    private void doSeekTests() throws Exception{
-        final PositionedCryptoInputStream in = getCryptoInputStream(0);
-        final String cipherClass = in.getCipher().getClass().getName();
-        doSeekTests(cipherClass);
+    @Test
+    public void doTestJCE() throws Exception {
+        testCipher(AbstractCipherTest.JCE_CIPHER_CLASSNAME);
     }
 
-    private void testSeekLoop(final String cipherClass, int position, final int length,
-            final int bufferSize) throws Exception {
-        try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) {
-            while (in.available() > 0) {
-                in.seek(position);
-                final ByteBuffer buf = ByteBuffer.allocate(length);
-                final int n = in.read(buf);
-                if (n <= 0) {
-                    break;
-                }
-                compareByteArray(testData, position, buf.array(), n);
-                position += n;
-            }
+    @Test
+    public void doTestJNI() throws Exception {
+        assumeTrue(Crypto.isNativeCodeLoaded());
+        testCipher(AbstractCipherTest.OPENSSL_CIPHER_CLASSNAME);
+    }
+
+    private CryptoCipher getCipher(final String cipherClass) throws IOException {
+        try {
+            return (CryptoCipher) ReflectionUtils.newInstance(
+                    ReflectionUtils.getClassByName(cipherClass), props,
+                    transformation);
+        } catch (final ClassNotFoundException cnfe) {
+            throw new IOException("Illegal crypto cipher!");
         }
     }
 
-    // test for the out of index position, eg, -1.
-    private void testSeekFailed(final String cipherClass, final int position, final int bufferSize)
-            throws Exception {
-        try (final PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) {
-            assertThrows(IllegalArgumentException.class, () -> in.seek(position));
+    private PositionedCryptoInputStream getCryptoInputStream(
+            final CryptoCipher cipher, final int bufferSize) throws IOException {
+        return new PositionedCryptoInputStream(props, new PositionedInputForTest(
+                Arrays.copyOf(encData, encData.length)), cipher, bufferSize,
+                key, iv, 0);
+    }
+
+    private PositionedCryptoInputStream getCryptoInputStream(final int streamOffset)
+            throws IOException {
+        return new PositionedCryptoInputStream(props, new PositionedInputForTest(
+                Arrays.copyOf(encData, encData.length)), key, iv, streamOffset);
+    }
+
+    private void prepareData() throws IOException {
+        final CryptoCipher cipher;
+        try {
+            cipher = (CryptoCipher) ReflectionUtils.newInstance(
+                    ReflectionUtils.getClassByName(AbstractCipherTest.JCE_CIPHER_CLASSNAME), props,
+                    transformation);
+        } catch (final ClassNotFoundException cnfe) {
+            throw new IOException("Illegal crypto cipher!");
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        // encryption data
+        try (final OutputStream out = new CryptoOutputStream(baos, cipher, bufferSize,
+                AES.newSecretKeySpec(key), new IvParameterSpec(iv))) {
+            out.write(testData);
+            out.flush();
         }
+        encData = baos.toByteArray();
+    }
+
+    protected void testCipher(final String cipherClass) throws Exception {
+        doPositionedReadTests(cipherClass);
+        doPositionedReadTests();
+        doReadFullyTests(cipherClass);
+        doReadFullyTests();
+        doSeekTests(cipherClass);
+        doSeekTests();
+        doMultipleReadTest(cipherClass);
+        doMultipleReadTest();
     }
 
     private void testPositionedReadLoop(final String cipherClass, int position,
@@ -275,6 +359,17 @@ public class PositionedCryptoInputStreamTest {
         }
     }
 
+    // test for the End of file reached before reading fully
+    private void testReadFullyFailed(final String cipherClass, final int position,
+            final int length, final int bufferSize) throws Exception {
+        try (final PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) {
+            final byte[] bytes = new byte[length];
+            assertThrows(IOException.class, () -> in.readFully(position, bytes, 0, length));
+            in.close();
+            in.close(); // Don't throw exception.
+        }
+    }
+
     private void testReadFullyLoop(final String cipherClass, int position,
             final int length, final int bufferSize, final int total) throws Exception {
         try (PositionedCryptoInputStream in = getCryptoInputStream(
@@ -291,122 +386,27 @@ public class PositionedCryptoInputStreamTest {
         }
     }
 
-    // test for the End of file reached before reading fully
-    private void testReadFullyFailed(final String cipherClass, final int position,
-            final int length, final int bufferSize) throws Exception {
+    // test for the out of index position, eg, -1.
+    private void testSeekFailed(final String cipherClass, final int position, final int bufferSize)
+            throws Exception {
         try (final PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) {
-            final byte[] bytes = new byte[length];
-            assertThrows(IOException.class, () -> in.readFully(position, bytes, 0, length));
-            in.close();
-            in.close(); // Don't throw exception.
-        }
-    }
-
-    // compare the data from pos with length and data2 from 0 with length
-    private void compareByteArray(final byte[] data1, final int pos, final byte[] data2,
-            final int length) {
-        final byte[] expectedData = new byte[length];
-        final byte[] realData = new byte[length];
-        // get the expected data with the position and length
-        System.arraycopy(data1, pos, expectedData, 0, length);
-        // get the real data
-        System.arraycopy(data2, 0, realData, 0, length);
-        assertArrayEquals(expectedData, realData);
-    }
-
-    private CryptoCipher getCipher(final String cipherClass) throws IOException {
-        try {
-            return (CryptoCipher) ReflectionUtils.newInstance(
-                    ReflectionUtils.getClassByName(cipherClass), props,
-                    transformation);
-        } catch (final ClassNotFoundException cnfe) {
-            throw new IOException("Illegal crypto cipher!");
+            assertThrows(IllegalArgumentException.class, () -> in.seek(position));
         }
     }
 
-    static class PositionedInputForTest implements Input {
-
-        final byte[] data;
-        long pos;
-        final long count;
-
-        public PositionedInputForTest(final byte[] data) {
-            this.data = data;
-            this.pos = 0;
-            this.count = data.length;
-        }
-
-        @Override
-        public int read(final ByteBuffer dst) {
-            final int remaining = (int) (count - pos);
-            if (remaining <= 0) {
-                return -1;
-            }
-
-            final int length = Math.min(dst.remaining(), remaining);
-            dst.put(data, (int) pos, length);
-            pos += length;
-            return length;
-        }
-
-        @Override
-        public long skip(long n) {
-            if (n <= 0) {
-                return 0;
-            }
-
-            final long remaining = count - pos;
-            if (remaining < n) {
-                n = remaining;
-            }
-            pos += n;
-
-            return n;
-        }
-
-        @Override
-        public int read(final long position, final byte[] buffer, final int offset, int length) {
-            Objects.requireNonNull(buffer, "buffer");
-            if (offset < 0 || length < 0
-                    || length > buffer.length - offset) {
-                throw new IndexOutOfBoundsException();
-            }
-
-            if (position < 0 || position >= count) {
-                return -1;
-            }
-
-            final long avail = count - position;
-            if (length > avail) {
-                length = (int) avail;
-            }
-            if (length <= 0) {
-                return 0;
-            }
-            System.arraycopy(data, (int) position, buffer, offset, length);
-            return length;
-        }
-
-        @Override
-        public void seek(final long position) throws IOException {
-            if (pos < 0) {
-                throw new IOException("Negative seek offset");
-            }
-            if (position >= 0 && position < count) {
-                pos = position;
-            } else {
-                // to the end of file
-                pos = count;
+    private void testSeekLoop(final String cipherClass, int position, final int length,
+            final int bufferSize) throws Exception {
+        try (PositionedCryptoInputStream in = getCryptoInputStream(getCipher(cipherClass), bufferSize)) {
+            while (in.available() > 0) {
+                in.seek(position);
+                final ByteBuffer buf = ByteBuffer.allocate(length);
+                final int n = in.read(buf);
+                if (n <= 0) {
+                    break;
+                }
+                compareByteArray(testData, position, buf.array(), n);
+                position += n;
             }
         }
-
-        @Override
-        public void close() {
-        }
-
-        @Override
-        public int available() {
-            return (int) (count - pos);
-        }
     }
 }
diff --git a/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java b/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java
index ee151b1..33965f9 100644
--- a/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java
+++ b/src/test/java/org/apache/commons/crypto/utils/UtilsTest.java
@@ -28,6 +28,21 @@ import org.junit.jupiter.api.Test;
 
 
 public class UtilsTest {
+    @Test
+    public void testGetProperties() {
+        final Properties props = new Properties();
+        props.setProperty(
+            "garbage.in",
+            "out");
+        final Properties allprops = Utils.getProperties(props);
+        assertEquals(allprops.getProperty("garbage.in"), "out");
+    }
+
+    @Test
+    public void testSplitNull() {
+        assertEquals(Collections.<String> emptyList(), Utils.splitClassNames(null, ","));
+    }
+
     @Test
     public void testSplitOmitEmptyLine() {
         List<String> clazzNames = Utils.splitClassNames("", ",");
@@ -40,19 +55,4 @@ public class UtilsTest {
         clazzNames = Utils.splitClassNames("a, b,", ",");
         assertEquals(Arrays.asList("a", "b"), clazzNames);
     }
-
-    @Test
-    public void testSplitNull() {
-        assertEquals(Collections.<String> emptyList(), Utils.splitClassNames(null, ","));
-    }
-
-    @Test
-    public void testGetProperties() {
-        final Properties props = new Properties();
-        props.setProperty(
-            "garbage.in",
-            "out");
-        final Properties allprops = Utils.getProperties(props);
-        assertEquals(allprops.getProperty("garbage.in"), "out");
-    }
 }


[commons-crypto] 03/03: Pick up spotbugs-maven-plugin version from parent POM

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-crypto.git

commit 9ee9c7e4156fa4f9594bee744ae83483381b0871
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jan 14 14:47:58 2023 -0500

    Pick up spotbugs-maven-plugin version from parent POM
---
 pom.xml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3e9bc2b..6121c40 100644
--- a/pom.xml
+++ b/pom.xml
@@ -143,7 +143,6 @@ The following provides more details on the included cryptographic software:
     <checkstyle.resourceExcludes>LICENSE.txt, NOTICE.txt, **/maven-archiver/pom.properties</checkstyle.resourceExcludes>
 
     <japicmp.skip>false</japicmp.skip>
-    <commons.spotbugs.version>4.7.3.0</commons.spotbugs.version>
     <!-- 1.21 and 1.22 cause failures -->
     <commons.animal-sniffer.version>1.20</commons.animal-sniffer.version>