Repository: commons-crypto
Updated Branches:
  refs/heads/master de5423477 -> aad8c55a4


CRYPTO-60: opensslCipher support GCM mode

Closes #70 from kexianda/gcm3


Project: http://git-wip-us.apache.org/repos/asf/commons-crypto/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-crypto/commit/aad8c55a
Tree: http://git-wip-us.apache.org/repos/asf/commons-crypto/tree/aad8c55a
Diff: http://git-wip-us.apache.org/repos/asf/commons-crypto/diff/aad8c55a

Branch: refs/heads/master
Commit: aad8c55a43f72d7869d704a44b42557bb634baf2
Parents: de54234
Author: Xianda Ke <xianda...@intel.com>
Authored: Mon Nov 20 07:57:14 2017 +0800
Committer: Sun Dapeng <s...@apache.org>
Committed: Mon Nov 20 08:29:11 2017 +0800

----------------------------------------------------------------------
 Makefile.common                                 |   2 +-
 .../commons/crypto/cipher/CryptoCipher.java     |  52 +++
 .../apache/commons/crypto/cipher/JceCipher.java |  56 ++++
 .../apache/commons/crypto/cipher/OpenSsl.java   | 119 ++++---
 .../commons/crypto/cipher/OpenSslCipher.java    | 142 ++++++---
 .../crypto/cipher/OpenSslCommonMode.java        | 117 +++++++
 .../crypto/cipher/OpenSslEvpCtrlValues.java     |  52 +++
 .../crypto/cipher/OpenSslFeedbackCipher.java    |  73 +++++
 .../crypto/cipher/OpenSslGaloisCounterMode.java | 313 +++++++++++++++++++
 .../commons/crypto/cipher/OpenSslNative.java    |  48 ++-
 .../commons/crypto/jna/OpenSslJnaCipher.java    |  62 ++++
 .../commons/crypto/cipher/OpenSslNative.c       | 170 +++++++++-
 .../commons/crypto/org_apache_commons_crypto.h  |   5 +-
 .../crypto/cipher/OpenSslCipherTest.java        |  12 +-
 14 files changed, 1113 insertions(+), 110 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/Makefile.common
----------------------------------------------------------------------
diff --git a/Makefile.common b/Makefile.common
index 0f50098..3518f57 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -88,7 +88,7 @@ Linux-x86_64_CXX       := $(CROSS_PREFIX)g++
 Linux-x86_64_STRIP     := $(CROSS_PREFIX)strip
 Linux-x86_64_CXXFLAGS  := -Ilib/inc_linux -I$(JAVA_HOME)/include -Ilib/inc_mac 
-O2 -fPIC -fvisibility=hidden -m64
 Linux-x86_64_CFLAGS    := -Ilib/inc_linux -I$(JAVA_HOME)/include -Ilib/inc_mac 
-O2 -fPIC -fvisibility=hidden -m64
-Linux-x86_64_LINKFLAGS := -shared -static-libgcc -static-libstdc++
+Linux-x86_64_LINKFLAGS := -shared -static-libgcc
 Linux-x86_64_LIBNAME   := libcommons-crypto.so
 Linux-x86_64_COMMONS_CRYPTO_FLAGS  :=
 

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
----------------------------------------------------------------------
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 f3bdbcd..668ef2f 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java
@@ -153,4 +153,56 @@ public interface CryptoCipher extends Closeable {
     int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
             int outputOffset) throws ShortBufferException,
             IllegalBlockSizeException, BadPaddingException;
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the cipher when operating in
+     * modes such as AEAD (GCM).  If this cipher is operating in
+     * GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this cipher is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM or CCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the corresponding method
+     * has not been overridden by an implementation
+     *
+     */
+    void updateAAD(byte[] aad)
+            throws IllegalArgumentException, IllegalStateException, 
UnsupportedOperationException;
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the cipher when operating in
+     * modes such as AEAD (GCM).  If this cipher is operating in
+     * GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this cipher is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM or CCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the corresponding method
+     * has not been overridden by an implementation
+     *
+     */
+    void updateAAD(ByteBuffer aad)
+            throws IllegalArgumentException, IllegalStateException, 
UnsupportedOperationException;
 }

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
----------------------------------------------------------------------
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 a6b64fd..0df3f4b 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java
@@ -196,6 +196,62 @@ class JceCipher implements CryptoCipher {
                 outputOffset);
     }
 
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the cipher when operating in
+     * modes such as AEAD (GCM/CCM).  If this cipher is operating in
+     * either GCM or CCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this cipher is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM or CCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if JCE's implementation does not
+     * support such operation
+     */
+    @Override
+    public void updateAAD(byte[] aad) {
+        cipher.updateAAD(aad);
+    }
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the cipher when operating in
+     * modes such as AEAD (GCM/CCM).  If this cipher is operating in
+     * either GCM or CCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this cipher is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM or CCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if JCE's implementation does not
+     * support such operation
+     */
+    @Override
+    public void updateAAD(ByteBuffer aad) {
+        cipher.updateAAD(aad);
+    }
+
+
     /**
      * Closes Jce cipher.
      */

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
----------------------------------------------------------------------
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 1820b47..e7c061a 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
@@ -18,7 +18,9 @@
 package org.apache.commons.crypto.cipher;
 
 import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
 import java.util.StringTokenizer;
 import javax.crypto.BadPaddingException;
 import javax.crypto.IllegalBlockSizeException;
@@ -34,13 +36,15 @@ import org.apache.commons.crypto.utils.Utils;
  */
 final class OpenSsl {
 
+    OpenSslFeedbackCipher opensslBlockCipher;
+
     // Mode constant defined by OpenSsl JNI
     public static final int ENCRYPT_MODE = 1;
     public static final int DECRYPT_MODE = 0;
 
     /** Currently only support AES/CTR/NoPadding. */
     private static enum AlgorithmMode {
-        AES_CTR, AES_CBC;
+        AES_CTR, AES_CBC, AES_GCM;
 
         /**
          * Gets the mode.
@@ -82,10 +86,6 @@ final class OpenSsl {
         }
     }
 
-    private long context = 0;
-    private final int algorithm;
-    private final int padding;
-
     private static final Throwable loadingFailureReason;
 
     static {
@@ -122,9 +122,11 @@ final class OpenSsl {
      * @param padding the padding.
      */
     private OpenSsl(long context, int algorithm, int padding) {
-        this.context = context;
-        this.algorithm = algorithm;
-        this.padding = padding;
+        if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
+            opensslBlockCipher = new OpenSslGaloisCounterMode(context, 
algorithm, padding);
+        } else {
+            opensslBlockCipher = new OpenSslCommonMode(context, algorithm, 
padding);
+        }
     }
 
     /**
@@ -210,11 +212,13 @@ final class OpenSsl {
      *
      * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
      * @param key crypto key
-     * @param iv crypto iv
+     * @param params the algorithm parameters
+     * @throws InvalidAlgorithmParameterException if IV length is wrong
      */
-    public void init(int mode, byte[] key, byte[] iv) {
-        context = OpenSslNative
-                .init(context, mode, algorithm, padding, key, iv);
+    public void init(int mode, byte[] key, AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+        checkState();
+        opensslBlockCipher.init(mode, key, params);
     }
 
     /**
@@ -250,12 +254,7 @@ final class OpenSsl {
         checkState();
         Utils.checkArgument(input.isDirect() && output.isDirect(),
                 "Direct buffers are required.");
-        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;
+        return opensslBlockCipher.update(input, output);
     }
 
     /**
@@ -274,8 +273,37 @@ final class OpenSsl {
     public int update(byte[] input, int inputOffset, int inputLen,
             byte[] output, int outputOffset) throws ShortBufferException {
         checkState();
-        return OpenSslNative.updateByteArray(context, input, inputOffset,
-                inputLen, output, outputOffset, output.length - outputOffset);
+        return opensslBlockCipher.update(input, inputOffset, inputLen, output, 
outputOffset);
+    }
+
+    /**
+     * 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.
+     */
+    public int doFinal(byte[] input, int inputOffset, int inputLen,
+                       byte[] output, int outputOffset)
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException{
+
+        checkState();
+        return opensslBlockCipher.doFinal(input, inputOffset, inputLen, 
output, outputOffset);
     }
 
     /**
@@ -304,6 +332,7 @@ final class OpenSsl {
      * If any exception is thrown, this cipher object need to be reset before 
it
      * can be used again.
      *
+     * @param input the input ByteBuffer
      * @param output the output ByteBuffer
      * @return int number of bytes stored in <code>output</code>
      * @throws ShortBufferException if the given output byte array is too small
@@ -317,53 +346,43 @@ final class OpenSsl {
      *         (un)padding has been requested, but the decrypted data is not
      *         bounded by the appropriate padding bytes
      */
-    public int doFinal(ByteBuffer output) throws ShortBufferException,
+    public int doFinal(ByteBuffer input, ByteBuffer output) throws 
ShortBufferException,
             IllegalBlockSizeException, BadPaddingException {
         checkState();
         Utils.checkArgument(output.isDirect(), "Direct buffer is required.");
-        int len = OpenSslNative.doFinal(context, output, output.position(),
-                output.remaining());
-        output.position(output.position() + len);
-        return len;
+
+        return opensslBlockCipher.doFinal(input, output);
     }
 
+
     /**
-     * Encrypts or decrypts data in a single-part operation, or finishes a
-     * multiple-part operation.
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the cipher when operating in
+     * modes such as AEAD (GCM).  If this cipher is operating in
+     * either GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
      *
-     * @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.
      */
-    public int doFinal(byte[] output, int outputOffset)
-            throws ShortBufferException, IllegalBlockSizeException,
-            BadPaddingException {
-        checkState();
-        return OpenSslNative.doFinalByteArray(context, output, outputOffset,
-                output.length - outputOffset);
+    public void updateAAD(byte[] aad) {
+        this.opensslBlockCipher.updateAAD(aad);
     }
 
+
     /** Forcibly clean the context. */
     public void clean() {
-        if (context != 0) {
-            OpenSslNative.clean(context);
-            context = 0;
+        if (opensslBlockCipher != null) {
+            opensslBlockCipher.clean();
         }
     }
 
     /** Checks whether context is initialized. */
     private void checkState() {
-        Utils.checkState(context != 0);
+        Utils.checkState(opensslBlockCipher != null);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
----------------------------------------------------------------------
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 dff7dad..c62d58b 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
@@ -37,16 +37,17 @@ import org.apache.commons.crypto.utils.Utils;
  */
 class OpenSslCipher implements CryptoCipher {
 
-    private final OpenSsl cipher;
+    private final OpenSsl openSslEngine;
+    private boolean initialized = false;
 
     private final String transformation;
 
     /**
      * Constructs a {@link CryptoCipher} using JNI into OpenSSL
      *
-     * @param props  properties for OpenSSL cipher (unused)
-     * @param transformation  transformation for OpenSSL cipher 
(algorithm/mode/padding)
-     * @throws GeneralSecurityException if OpenSSL cipher initialize failed
+     * @param props  properties for OpenSSL openSslEngine (unused)
+     * @param transformation  transformation for OpenSSL openSslEngine 
(algorithm/mode/padding)
+     * @throws GeneralSecurityException if OpenSSL openSslEngine initialize 
failed
      */
     // N.B. this class is not public/protected so does not appear in the main 
Javadoc
     // Please ensure that property use is documented in the enum 
CryptoRandomFactory.RandomProvider
@@ -59,14 +60,14 @@ class OpenSslCipher implements CryptoCipher {
             throw new RuntimeException(loadingFailureReason);
         }
 
-        cipher = OpenSsl.getInstance(transformation);
+        openSslEngine = OpenSsl.getInstance(transformation);
     }
 
     /**
      * Returns the block size (in bytes).
      *
      * @return the block size (in bytes), or 0 if the underlying algorithm is
-     * not a block cipher
+     * not a block openSslEngine
      */
     @Override
     public final int getBlockSize() {
@@ -88,10 +89,10 @@ class OpenSslCipher implements CryptoCipher {
     }
 
     /**
-     * Initializes the cipher with mode, key and iv.
+     * Initializes the openSslEngine with mode, key and iv.
      *
      * @param mode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
-     * @param key crypto key for the cipher
+     * @param key crypto key for the openSslEngine
      * @param params the algorithm parameters
      * @throws InvalidKeyException If key length is invalid
      * @throws InvalidAlgorithmParameterException if IV length is wrong
@@ -106,20 +107,13 @@ class OpenSslCipher implements CryptoCipher {
         if (mode == Cipher.ENCRYPT_MODE) {
             cipherMode = OpenSsl.ENCRYPT_MODE;
         }
-        byte[] iv;
-        if (params instanceof IvParameterSpec) {
-            iv = ((IvParameterSpec) params).getIV();
-        } else {
-            // other AlgorithmParameterSpec such as GCMParameterSpec is not
-            // supported now.
-            throw new InvalidAlgorithmParameterException("Illegal parameters");
-        }
-        cipher.init(cipherMode, key.getEncoded(), iv);
+        openSslEngine.init(cipherMode, key.getEncoded(), params);
+        initialized = true;
     }
 
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
+     * encrypted or decrypted, depending on how this openSslEngine was 
initialized.
      *
      * @param inBuffer the input ByteBuffer
      * @param outBuffer the output ByteBuffer
@@ -130,12 +124,12 @@ class OpenSslCipher implements CryptoCipher {
     @Override
     public int update(ByteBuffer inBuffer, ByteBuffer outBuffer)
             throws ShortBufferException {
-        return cipher.update(inBuffer, outBuffer);
+        return openSslEngine.update(inBuffer, outBuffer);
     }
 
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
-     * encrypted or decrypted, depending on how this cipher was initialized.
+     * encrypted or decrypted, depending on how this openSslEngine was 
initialized.
      *
      * @param input the input byte array
      * @param inputOffset the offset in input where the input starts
@@ -149,24 +143,24 @@ class OpenSslCipher implements CryptoCipher {
     @Override
     public int update(byte[] input, int inputOffset, int inputLen,
             byte[] output, int outputOffset) throws ShortBufferException {
-        return cipher
+        return openSslEngine
                 .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.
+     * how this openSslEngine was initialized.
      *
      * @param inBuffer the input ByteBuffer
      * @param outBuffer the output ByteBuffer
      * @return int number of bytes stored in <code>output</code>
-     * @throws BadPaddingException if this cipher is in decryption mode, and
+     * @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 cipher is a block cipher, no
+     * @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 cipher is not a
+     *         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
@@ -176,8 +170,7 @@ class OpenSslCipher implements CryptoCipher {
     public int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
             throws ShortBufferException, IllegalBlockSizeException,
             BadPaddingException {
-        int n = cipher.update(inBuffer, outBuffer);
-        return n + cipher.doFinal(outBuffer);
+        return openSslEngine.doFinal(inBuffer, outBuffer);
     }
 
     /**
@@ -192,12 +185,12 @@ class OpenSslCipher implements CryptoCipher {
      * @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
+     * @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 cipher is a block cipher, no
+     * @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 cipher is not a
+     *         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.
      */
@@ -205,16 +198,95 @@ class OpenSslCipher implements CryptoCipher {
     public int doFinal(byte[] input, int inputOffset, int inputLen,
             byte[] output, int outputOffset) throws ShortBufferException,
             IllegalBlockSizeException, BadPaddingException {
-        int n = cipher.update(input, inputOffset, inputLen, output,
-                outputOffset);
-        return n + cipher.doFinal(output, outputOffset + n);
+        return openSslEngine.doFinal(input, inputOffset, inputLen, 
output,outputOffset);
+    }
+
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the opensslEngine when operating in
+     * modes such as AEAD (GCM).  If this opensslEngine is operating in
+     * either GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this opensslEngine is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the implementation {@code 
opensslEngine}
+     * doesn't support this operation.
+     */
+    @Override
+    public void updateAAD(byte[] aad) throws IllegalArgumentException,
+            IllegalStateException, UnsupportedOperationException {
+        if (aad == null) {
+            throw new IllegalArgumentException("aad buffer is null");
+        }
+        if (!initialized) {
+            throw new IllegalStateException("Cipher not initialized");
+        }
+        if (aad.length == 0) {
+            return;
+        }
+
+        openSslEngine.updateAAD(aad);
     }
 
     /**
-     * Closes the OpenSSL cipher. Clean the OpenSsl native context.
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the opensslEngine when operating in
+     * modes such as AEAD (GCM).  If this opensslEngine is operating in
+     * either GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this opensslEngine is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the implementation {@code 
opensslEngine}
+     * doesn't support this operation.
+     */
+    @Override
+    public void updateAAD(ByteBuffer aad) throws IllegalArgumentException,
+            IllegalStateException, UnsupportedOperationException {
+        if (aad == null) {
+            throw new IllegalArgumentException("aad buffer is null");
+        }
+        if (!initialized) {
+            throw new IllegalStateException("Cipher not initialized");
+        }
+
+        int aadLen = aad.limit() - aad.position();
+        if (aadLen == 0) {
+            return;
+        }
+        byte[] aadBytes = new byte[aadLen];
+        aad.get(aadBytes);
+        openSslEngine.updateAAD(aadBytes);
+    }
+
+
+    /**
+     * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context.
      */
     @Override
     public void close() {
-        cipher.clean();
+        openSslEngine.clean();
     }
 }

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
new file mode 100644
index 0000000..f602d11
--- /dev/null
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.crypto.cipher;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class do the real work(Encryption/Decryption) for non-authenticated 
modes, such as CTR, CBC.
+ * <p>
+ * It will call the OpenSSL API to implement encryption/decryption
+ */
+class OpenSslCommonMode extends OpenSslFeedbackCipher {
+
+    public OpenSslCommonMode(long context, int algorithmMode, int padding) {
+        super(context, algorithmMode, padding);
+    }
+
+    @Override
+    public void init(int mode, byte[] key, AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+        this.cipherMode = mode;
+        byte[] iv;
+        if (params instanceof IvParameterSpec) {
+            iv = ((IvParameterSpec) params).getIV();
+        } else {
+            // other AlgorithmParameterSpec is not supported now.
+            throw new InvalidAlgorithmParameterException("Illegal parameters");
+        }
+        context = OpenSslNative.init(context, mode, algorithmMode, padding, 
key, iv);
+    }
+
+    @Override
+    public int update(ByteBuffer input, ByteBuffer output) throws 
ShortBufferException {
+        checkState();
+
+        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(byte[] input, int inputOffset, int inputLen, byte[] 
output, int outputOffset)
+            throws ShortBufferException {
+        checkState();
+
+        return OpenSslNative.updateByteArray(context, input, inputOffset,
+                inputLen, output, outputOffset, output.length - outputOffset);
+    }
+
+    @Override
+    public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] 
output, int outputOffset)
+            throws ShortBufferException, IllegalBlockSizeException, 
BadPaddingException {
+        checkState();
+
+        int len = OpenSslNative.updateByteArray(context, input, inputOffset,
+                inputLen, output, outputOffset, output.length - outputOffset);
+
+        len += OpenSslNative.doFinalByteArray(context, output, outputOffset + 
len,
+                output.length - outputOffset - len);
+
+        return len;
+    }
+
+    @Override
+    public int doFinal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, 
BadPaddingException {
+        checkState();
+
+        int totalLen = 0;
+        int len = OpenSslNative.update(context, input, input.position(),
+                input.remaining(), output, output.position(), 
output.remaining());
+        totalLen += len;
+
+        input.position(input.limit());
+        output.position(output.position() + len);
+
+        len = OpenSslNative.doFinal(context, output, output.position(),
+                output.remaining());
+        totalLen += len;
+
+        output.position(output.position() + len);
+
+        return totalLen;
+    }
+
+    @Override
+    public void updateAAD(byte[] aad) {
+        throw new UnsupportedOperationException(
+                "The underlying Cipher implementation "
+                        + "does not support this method");
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java
new file mode 100644
index 0000000..2b64ff5
--- /dev/null
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.crypto.cipher;
+
+/**
+ * This enum is defined for OpensslNative.ctrl() to allow various cipher
+ * specific parameters to be determined and set.
+ * see the macro definitions in openssl/evp.h
+ */
+enum OpenSslEvpCtrlValues {
+    INIT(0x00),
+    SET_KEY_LENGTH(0x01),
+    GET_RC2_KEY_BITS(0x02),
+    SET_RC2_KEY_BITS(0x03),
+    GET_RC5_ROUNDS(0x04),
+    SET_RC5_ROUNDS(0x05),
+    RAND_KEY(0x06),
+    PBE_PRF_NID(0x07),
+    COPY(0x08),
+    AEAD_SET_IVLEN(0x09),
+    AEAD_GET_TAG(0x10),
+    AEAD_SET_TAG(0x11),
+    AEAD_SET_IV_FIXED(0x12),
+    GCM_IV_GEN(0x13),
+    CCM_SET_L(0x14),
+    CCM_SET_MSGLEN(0x15);
+
+    private final int value;
+
+    OpenSslEvpCtrlValues(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java
new file mode 100644
index 0000000..4fa2743
--- /dev/null
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.crypto.cipher;
+
+import org.apache.commons.crypto.utils.Utils;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
+import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class represents a block cipher in one of its modes.
+ */
+abstract class OpenSslFeedbackCipher {
+
+    protected long context = 0;
+    protected int algorithmMode;
+    protected int padding;
+
+    protected int cipherMode = OpenSsl.DECRYPT_MODE;
+
+    OpenSslFeedbackCipher(long context, int algorithmMode, int padding) {
+        this.context = context;
+        this.algorithmMode = algorithmMode;
+        this.padding = padding;
+    }
+
+    abstract void init(int mode, byte[] key, AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException;
+
+    abstract int update(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException;
+
+    abstract int update(byte[] input, int inputOffset, int inputLen, byte[] 
output, int outputOffset)
+            throws ShortBufferException;
+
+    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);
+
+    public void clean() {
+        if (context != 0) {
+            OpenSslNative.clean(context);
+            context = 0;
+        }
+    }
+
+    public void checkState() {
+        Utils.checkState(context != 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
new file mode 100644
index 0000000..991c475
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java
@@ -0,0 +1,313 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.crypto.cipher;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * This class do the real work(Encryption/Decryption/Authentication) for the 
authenticated mode: GCM.
+ *
+ * It calls the OpenSSL API to implement the JCE-like behavior
+ *
+ * @since 1.1
+ */
+class OpenSslGaloisCounterMode extends OpenSslFeedbackCipher {
+
+    // 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;
+
+    // buffer for storing input in decryption, not used for encryption
+    private ByteArrayOutputStream inBuffer = null;
+
+    public OpenSslGaloisCounterMode(long context, int algorithmMode, int 
padding) {
+        super(context, algorithmMode, padding);
+    }
+
+    @Override
+    public void init(int mode, byte[] key, AlgorithmParameterSpec params)
+            throws InvalidAlgorithmParameterException {
+
+        if (aadBuffer == null) {
+            aadBuffer = new ByteArrayOutputStream();
+        } else {
+            aadBuffer.reset();
+        }
+
+        this.cipherMode = mode;
+        byte[] iv;
+        if (params instanceof GCMParameterSpec) {
+            GCMParameterSpec gcmParam = (GCMParameterSpec) params;
+            iv = gcmParam.getIV();
+            this.tagBitLen = gcmParam.getTLen();
+        } else {
+            // other AlgorithmParameterSpec is not supported now.
+            throw new InvalidAlgorithmParameterException("Illegal parameters");
+        }
+
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            inBuffer = new ByteArrayOutputStream();
+        }
+
+        context = OpenSslNative.init(context, mode, algorithmMode, padding, 
key, iv);
+    }
+
+    @Override
+    public int update(ByteBuffer input, ByteBuffer output) throws 
ShortBufferException {
+        checkState();
+
+        processAAD();
+
+        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
+            int inputLen = input.remaining();
+            byte[] inputBuf = new byte[inputLen];
+            input.get(inputBuf, 0, inputLen);
+            inBuffer.write(inputBuf, 0, inputLen);
+            return 0;
+        } else {
+            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(byte[] input, int inputOffset, int inputLen, byte[] 
output, 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;
+        } else {
+            return OpenSslNative.updateByteArray(context, input, inputOffset,
+                    inputLen, output, outputOffset, output.length - 
outputOffset);
+        }
+    }
+
+    @Override
+    public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] 
output, int outputOffset)
+            throws ShortBufferException, IllegalBlockSizeException, 
BadPaddingException {
+        checkState();
+
+        processAAD();
+
+        int len;
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            // if GCM-DECRYPT, we have to handle the buffered input
+            // and the retrieve the trailing tag from input
+            int inputOffsetFinal = inputOffset;
+            int inputLenFinal = inputLen;
+            byte[] inputFinal;
+            if (inBuffer != null && inBuffer.size() > 0) {
+                inBuffer.write(input, inputOffset, inputLen);
+                inputFinal = inBuffer.toByteArray();
+                inputOffsetFinal = 0;
+                inputLenFinal = inputFinal.length;
+                inBuffer.reset();
+            } else {
+                inputFinal = input;
+            }
+
+            if (inputFinal.length < getTagLen()) {
+                throw new AEADBadTagException("Input too short - need tag");
+            }
+
+            int inputDataLen = inputLenFinal - getTagLen();
+            len = OpenSslNative.updateByteArray(context, inputFinal, 
inputOffsetFinal,
+                    inputDataLen, output, outputOffset, output.length - 
outputOffset);
+
+            // set tag to EVP_Cipher for integrity verification in doFinal
+            ByteBuffer tag = ByteBuffer.allocate(getTagLen());
+            tag.put(input, input.length - getTagLen(), getTagLen());
+            tag.flip();
+            evpCipherCtxCtrl(context, 
OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(), getTagLen(), tag);
+        } else {
+            len = OpenSslNative.updateByteArray(context, input, inputOffset,
+                    inputLen, output, outputOffset, output.length - 
outputOffset);
+        }
+
+        len += OpenSslNative.doFinalByteArray(context, output, outputOffset + 
len,
+                output.length - outputOffset - len);
+
+        // Keep the similar behavior as JCE, append the tag to end of output
+        if (this.cipherMode == OpenSsl.ENCRYPT_MODE) {
+            ByteBuffer tag;
+            tag = ByteBuffer.allocate(getTagLen());
+            evpCipherCtxCtrl(context, 
OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag);
+            tag.get(output, output.length - getTagLen(), getTagLen());
+            len += getTagLen();
+        }
+
+        return len;
+    }
+
+    @Override
+    public int doFinal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, 
BadPaddingException {
+        checkState();
+
+        processAAD();
+
+        int totalLen = 0;
+        int len;
+        if (this.cipherMode == OpenSsl.DECRYPT_MODE) {
+            ByteBuffer tag = ByteBuffer.allocate(getTagLen());
+
+            // if GCM-DECRYPT, we have to handle the buffered input
+            // and the retrieve the trailing tag from input
+            if (inBuffer != null && inBuffer.size() > 0) {
+                byte[] inputBytes = new byte[input.remaining()];
+                input.get(inputBytes, 0, inputBytes.length);
+                inBuffer.write(inputBytes, 0, inputBytes.length);
+                byte[] inputFinal = inBuffer.toByteArray();
+                inBuffer.reset();
+
+                if (inputFinal.length < getTagLen()) {
+                    throw new AEADBadTagException("Input too short - need 
tag");
+                }
+
+                len = OpenSslNative.updateByteArrayByteBuffer(context, 
inputFinal, 0,
+                        inputFinal.length - getTagLen(),
+                        output, output.position(), output.remaining());
+
+                // retrieve tag
+                tag.put(inputFinal, inputFinal.length - getTagLen(), 
getTagLen());
+                tag.flip();
+
+            } else {
+                // if no buffered input, just use the input directly
+                if (input.remaining() < getTagLen()) {
+                    throw new AEADBadTagException("Input too short - need 
tag");
+                }
+
+                len = OpenSslNative.update(context, input, input.position(),
+                        input.remaining() - getTagLen(), output, 
output.position(),
+                        output.remaining());
+
+                input.position(input.position() + len);
+
+                // retrieve tag
+                tag.put(input);
+                tag.flip();
+            }
+
+            // set tag to EVP_Cipher for integrity verification in doFinal
+            evpCipherCtxCtrl(context, 
OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(),
+                    getTagLen(), tag);
+        } else {
+            len = OpenSslNative.update(context, input, input.position(),
+                    input.remaining(), output, output.position(),
+                    output.remaining());
+            input.position(input.limit());
+        }
+
+        totalLen += len;
+        output.position(output.position() + len);
+
+        len = OpenSslNative.doFinal(context, output, output.position(),
+                output.remaining());
+        output.position(output.position() + len);
+        totalLen += len;
+
+        // Keep the similar behavior as JCE, append the tag to end of output
+        if (this.cipherMode == OpenSsl.ENCRYPT_MODE) {
+            ByteBuffer tag;
+            tag = ByteBuffer.allocate(getTagLen());
+            evpCipherCtxCtrl(context, 
OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag);
+            output.put(tag);
+            totalLen += getTagLen();
+        }
+
+        return totalLen;
+    }
+
+    public void clean() {
+        super.clean();
+        aadBuffer = null;
+    }
+
+    @Override
+    public void updateAAD(byte[] aad) {
+        // must be called after initialized.
+        if (aadBuffer != null) {
+            aadBuffer.write(aad, 0, aad.length);
+        } else {
+            // update has already been called
+            throw new IllegalStateException
+                    ("Update has been called; no more AAD data");
+        }
+    }
+
+    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);
+    }
+
+    /**
+     * a wrapper 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,
+     * it may set/get any native char or long type to the data buffer(ptr).
+     * Here we use ByteBuffer and set nativeOrder to handle the endianness.
+     */
+    private void evpCipherCtxCtrl(long context, int type, int arg, ByteBuffer 
bb) {
+        checkState();
+
+        try {
+            if (bb != null) {
+                bb.order(ByteOrder.nativeOrder());
+                OpenSslNative.ctrl(context, type, arg, bb.array());
+            } else {
+                OpenSslNative.ctrl(context, type, arg, null);
+            }
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java 
b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java
index c73be17..dcaf37a 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,9 +25,9 @@ import java.nio.ByteBuffer;
  */
 class OpenSslNative {
 
-  /**
-   * The private constructor of {@link OpenSslNative}.
-   */
+    /**
+     * The private constructor of {@link OpenSslNative}.
+     */
     private OpenSslNative() {
     }
 
@@ -57,7 +57,7 @@ class OpenSslNative {
      * @return the context address of cipher
      */
     public native static long init(long context, int mode, int alg,
-            int padding, byte[] key, byte[] iv);
+                                   int padding, byte[] key, byte[] iv);
 
     /**
      * Continues a multiple-part encryption/decryption operation. The data is
@@ -94,6 +94,23 @@ class OpenSslNative {
             int maxOutputLength);
 
     /**
+     * Continues a multiple-part encryption/decryption operation. The data is
+     * encrypted or decrypted, depending on how this cipher was initialized.
+     *
+     * @param context The cipher context address
+     * @param input The input byte array
+     * @param inputOffset The offset in input where the input starts
+     * @param inputLength The input length
+     * @param output The byte buffer for the result
+     * @param outputOffset The offset in output where the result is stored
+     * @param maxOutputLength The maximum length for output
+     * @return The number of bytes stored in output
+     */
+    public native static int updateByteArrayByteBuffer(long context, byte[] 
input,
+                                                       int inputOffset, int 
inputLength,
+                                                       ByteBuffer output, int 
outputOffset, int maxOutputLength);
+
+    /**
      * Finishes a multiple-part operation. The data is encrypted or decrypted,
      * depending on how this cipher was initialized.
      *
@@ -120,6 +137,23 @@ class OpenSslNative {
             int offset, int maxOutputLength);
 
     /**
+     * allows various cipher specific parameters to be determined and set.
+     *
+     * it will call OpenSSL's API
+     * int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void 
*ptr)
+     * In OpenSSL, data type of ptr can be char* or long*.  Here, we map java's
+     * byte[] to native void*ptr. Note that the byte order is 
ByteOrder.nativeOrder.
+     *
+     * @param context The cipher context address
+     * @param type CtrlValues
+     * @param arg
+     * @param data byte buffer or null
+     * @return return 0 if there is any error, else return 1.
+     */
+    public native static int ctrl(long context, int type, int arg, byte[] 
data);
+
+
+    /**
      * Cleans the context at native.
      *
      * @param context The cipher context address

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
----------------------------------------------------------------------
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 c83f150..01de554 100644
--- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
+++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java
@@ -232,6 +232,68 @@ class OpenSslJnaCipher implements CryptoCipher {
         return doFinal(inputBuf, outputBuf);
     }
 
+
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the opensslEngine when operating in
+     * modes such as AEAD (GCM).  If this opensslEngine is operating in
+     * either GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this opensslEngine is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the implementation {@code 
opensslEngine}
+     * doesn't support this operation.
+     */
+    @Override
+    public void updateAAD(byte[] aad) throws IllegalArgumentException,
+            IllegalStateException, UnsupportedOperationException {
+        //TODO: implement GCM mode using Jna
+        throw new UnsupportedOperationException("This is unsupported in Jna 
Cipher");
+    }
+
+    /**
+     * Continues a multi-part update of the Additional Authentication
+     * Data (AAD).
+     * <p>
+     * Calls to this method provide AAD to the opensslEngine when operating in
+     * modes such as AEAD (GCM).  If this opensslEngine is operating in
+     * either GCM mode, all AAD must be supplied before beginning
+     * operations on the ciphertext (via the {@code update} and
+     * {@code doFinal} methods).
+     *
+     * @param aad the buffer containing the Additional Authentication Data
+     *
+     * @throws IllegalArgumentException if the {@code aad}
+     * byte array is null
+     * @throws IllegalStateException if this opensslEngine is in a wrong state
+     * (e.g., has not been initialized), does not accept AAD, or if
+     * operating in either GCM mode and one of the {@code update}
+     * methods has already been called for the active
+     * encryption/decryption operation
+     * @throws UnsupportedOperationException if the implementation {@code 
opensslEngine}
+     * doesn't support this operation.
+     */
+    @Override
+    public void updateAAD(ByteBuffer aad) throws IllegalArgumentException,
+            IllegalStateException, UnsupportedOperationException {
+        //TODO: implement GCM mode using Jna
+        throw new UnsupportedOperationException("This is unsupported in Jna 
Cipher");
+    }
+
+
+
     /**
      * Closes the OpenSSL cipher. Clean the OpenSsl native context.
      */

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c
----------------------------------------------------------------------
diff --git a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c 
b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c
index 46196e5..078e2f2 100644
--- a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c
+++ b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c
@@ -35,6 +35,7 @@ static void (*dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *);
 static int (*dlsym_EVP_CIPHER_CTX_cleanup)(EVP_CIPHER_CTX *);
 static void (*dlsym_EVP_CIPHER_CTX_init)(EVP_CIPHER_CTX *);
 static int (*dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, int);
+static int (*dlsym_EVP_CIPHER_CTX_ctrl)(EVP_CIPHER_CTX *, int, int, void *);
 static int (*dlsym_EVP_CipherInit_ex)(EVP_CIPHER_CTX *, const EVP_CIPHER *,  \
            ENGINE *, const unsigned char *, const unsigned char *, int);
 static int (*dlsym_EVP_CipherUpdate)(EVP_CIPHER_CTX *, unsigned char *,  \
@@ -46,6 +47,9 @@ static EVP_CIPHER * (*dlsym_EVP_aes_128_ctr)(void);
 static EVP_CIPHER * (*dlsym_EVP_aes_256_cbc)(void);
 static EVP_CIPHER * (*dlsym_EVP_aes_192_cbc)(void);
 static EVP_CIPHER * (*dlsym_EVP_aes_128_cbc)(void);
+static EVP_CIPHER * (*dlsym_EVP_aes_256_gcm)(void);
+static EVP_CIPHER * (*dlsym_EVP_aes_192_gcm)(void);
+static EVP_CIPHER * (*dlsym_EVP_aes_128_gcm)(void);
 #endif
 
 #ifdef WINDOWS
@@ -54,6 +58,7 @@ typedef void (__cdecl 
*__dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *);
 typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_cleanup)(EVP_CIPHER_CTX *);
 typedef void (__cdecl *__dlsym_EVP_CIPHER_CTX_init)(EVP_CIPHER_CTX *);
 typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, 
int);
+typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_ctrl)(EVP_CIPHER_CTX *, int, int, 
void *);
 typedef int (__cdecl *__dlsym_EVP_CipherInit_ex)(EVP_CIPHER_CTX *,  \
              const EVP_CIPHER *, ENGINE *, const unsigned char *,  \
              const unsigned char *, int);
@@ -67,11 +72,15 @@ typedef EVP_CIPHER * (__cdecl 
*__dlsym_EVP_aes_128_ctr)(void);
 typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_256_cbc)(void);
 typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_192_cbc)(void);
 typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_128_cbc)(void);
+typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_256_gcm)(void);
+typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_192_gcm)(void);
+typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_128_gcm)(void);
 static __dlsym_EVP_CIPHER_CTX_new dlsym_EVP_CIPHER_CTX_new;
 static __dlsym_EVP_CIPHER_CTX_free dlsym_EVP_CIPHER_CTX_free;
 static __dlsym_EVP_CIPHER_CTX_cleanup dlsym_EVP_CIPHER_CTX_cleanup;
 static __dlsym_EVP_CIPHER_CTX_init dlsym_EVP_CIPHER_CTX_init;
 static __dlsym_EVP_CIPHER_CTX_set_padding dlsym_EVP_CIPHER_CTX_set_padding;
+static __dlsym_EVP_CIPHER_CTX_ctrl dlsym_EVP_CIPHER_CTX_ctrl;
 static __dlsym_EVP_CipherInit_ex dlsym_EVP_CipherInit_ex;
 static __dlsym_EVP_CipherUpdate dlsym_EVP_CipherUpdate;
 static __dlsym_EVP_CipherFinal_ex dlsym_EVP_CipherFinal_ex;
@@ -81,6 +90,9 @@ static __dlsym_EVP_aes_128_ctr dlsym_EVP_aes_128_ctr;
 static __dlsym_EVP_aes_256_cbc dlsym_EVP_aes_256_cbc;
 static __dlsym_EVP_aes_192_cbc dlsym_EVP_aes_192_cbc;
 static __dlsym_EVP_aes_128_cbc dlsym_EVP_aes_128_cbc;
+static __dlsym_EVP_aes_256_gcm dlsym_EVP_aes_256_gcm;
+static __dlsym_EVP_aes_192_gcm dlsym_EVP_aes_192_gcm;
+static __dlsym_EVP_aes_128_gcm dlsym_EVP_aes_128_gcm;
 #endif
 
 #ifdef UNIX
@@ -98,6 +110,9 @@ static void loadAes(JNIEnv *env, HMODULE openssl)
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_cbc, env, openssl, "EVP_aes_256_cbc");
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_cbc, env, openssl, "EVP_aes_192_cbc");
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_cbc, env, openssl, "EVP_aes_128_cbc");
+  LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_gcm, env, openssl, "EVP_aes_256_gcm");
+  LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_gcm, env, openssl, "EVP_aes_192_gcm");
+  LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_gcm, env, openssl, "EVP_aes_128_gcm");
 #endif
 
 #ifdef WINDOWS
@@ -113,6 +128,12 @@ static void loadAes(JNIEnv *env, HMODULE openssl)
                       env, openssl, "EVP_aes_192_cbc");
   LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_128_cbc, dlsym_EVP_aes_128_cbc,  \
                       env, openssl, "EVP_aes_128_cbc");
+  LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_256_gcm, dlsym_EVP_aes_256_gcm,  \
+                      env, openssl, "EVP_aes_256_gcm");
+  LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_192_gcm, dlsym_EVP_aes_192_gcm,  \
+                      env, openssl, "EVP_aes_192_gcm");
+  LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_128_gcm, dlsym_EVP_aes_128_gcm,  \
+                      env, openssl, "EVP_aes_128_gcm");
 #endif
 }
 
@@ -153,6 +174,8 @@ JNIEXPORT void JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_initI
                       "EVP_CIPHER_CTX_init");
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_set_padding, env, openssl,  \
                       "EVP_CIPHER_CTX_set_padding");
+  LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_ctrl, env, openssl,  \
+                      "EVP_CIPHER_CTX_ctrl");
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherInit_ex, env, openssl,  \
                       "EVP_CipherInit_ex");
   LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherUpdate, env, openssl,  \
@@ -195,12 +218,13 @@ JNIEXPORT void JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_initI
 JNIEXPORT jlong JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_initContext
     (JNIEnv *env, jclass clazz, jint alg, jint padding)
 {
-  if (alg != AES_CTR && alg != AES_CBC) {
+  if (alg != AES_CTR && alg != AES_CBC && alg != AES_GCM) {
     THROW(env, "java/security/NoSuchAlgorithmException", NULL);
     return (jlong)0;
   }
   if (!(alg == AES_CTR && padding == NOPADDING)
-      && !(alg == AES_CBC && (padding == NOPADDING|| padding == 
PKCS5PADDING))) {
+      && !(alg == AES_CBC && (padding == NOPADDING|| padding == PKCS5PADDING))
+      && !(alg == AES_GCM && padding == NOPADDING)) {
     THROW(env, "javax/crypto/NoSuchPaddingException", NULL);
     return (jlong)0;
   }
@@ -219,6 +243,13 @@ JNIEXPORT jlong JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_init
     return (jlong)0;
   }
 
+  if (dlsym_EVP_aes_256_gcm == NULL ||
+    dlsym_EVP_aes_192_gcm == NULL || dlsym_EVP_aes_128_gcm == NULL) {
+    THROW(env, "java/security/NoSuchAlgorithmException",  \
+       "Doesn't support AES GCM.");
+    return (jlong)0;
+  }
+
   // Create and initialize a EVP_CIPHER_CTX
   EVP_CIPHER_CTX *context = dlsym_EVP_CIPHER_CTX_new();
   if (!context) {
@@ -249,6 +280,14 @@ static EVP_CIPHER * getEvpCipher(int alg, int keyLen)
     } else if (keyLen == KEY_LENGTH_128) {
       cipher = dlsym_EVP_aes_128_cbc();
     }
+  } else if (alg == AES_GCM) {
+    if (keyLen == KEY_LENGTH_256) {
+      cipher = dlsym_EVP_aes_256_gcm();
+    } else if (keyLen == KEY_LENGTH_192) {
+      cipher = dlsym_EVP_aes_192_gcm();
+    } else if (keyLen == KEY_LENGTH_128) {
+      cipher = dlsym_EVP_aes_128_gcm();
+    }
   }
   return cipher;
 }
@@ -271,7 +310,7 @@ JNIEXPORT jlong JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_init
     THROW(env, "java/security/InvalidKeyException", str);
     goto cleanup;
   }
-  if (jIvLen != IV_LENGTH) {
+  if ((alg != AES_GCM) && (jIvLen != IV_LENGTH)) {
     THROW(env, "java/security/InvalidAlgorithmParameterException", "Wrong IV 
length: must be 16 bytes long");
     goto cleanup;
   }
@@ -296,13 +335,26 @@ JNIEXPORT jlong JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_init
     goto cleanup;
   }
 
-  if (!(alg == AES_CTR || alg == AES_CBC)) {
+  if (!(alg == AES_CTR || alg == AES_CBC || alg == AES_GCM)) {
     THROW(env, "java/security/NoSuchAlgorithmException", "The algorithm is not 
supported.");
     goto cleanup;
   }
 
+  // initialize cipher & mode
   int rc = dlsym_EVP_CipherInit_ex(context, getEvpCipher(alg, jKeyLen),  \
-      NULL, (unsigned char *)jKey, (unsigned char *)jIv, mode == ENCRYPT_MODE);
+      NULL, NULL, NULL, mode == ENCRYPT_MODE);
+  if (rc == 0) {
+    THROW(env, "java/lang/InternalError", "Error in EVP_CipherInit_ex.");
+    goto cleanup;
+  }
+
+  // Set IV length if default 12 bytes (96 bits) is not appropriate
+  // Note: set IV length after cipher is initialized, before iv is initialized.
+  if (alg == AES_GCM) {
+    rc = dlsym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, jIvLen, 
NULL);
+  }
+  rc = dlsym_EVP_CipherInit_ex(context, NULL, NULL, \
+         (unsigned char *)jKey, (unsigned char *)jIv, mode == ENCRYPT_MODE);
   if (rc == 0) {
     THROW(env, "java/lang/InternalError", "Error in EVP_CipherInit_ex.");
     goto cleanup;
@@ -394,20 +446,28 @@ JNIEXPORT jint JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_updat
     jint input_len, jbyteArray output, jint output_offset, jint max_output_len)
 {
   EVP_CIPHER_CTX *context = CONTEXT(ctx);
-  if (!check_update_max_output_len(context, input_len, max_output_len)) {
+
+  // when provide AAD to EVP cipher, output is NULL.
+  if (output != NULL
+        && !check_update_max_output_len(context, input_len, max_output_len)) {
     THROW(env, "javax/crypto/ShortBufferException",  \
         "Output buffer is not sufficient.");
     return 0;
   }
+
   unsigned char *input_bytes = NULL;
   unsigned char *output_bytes = NULL;
   int output_len = 0;
 
   input_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, input, 0);
-  output_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, output, 
0);
-  if (input_bytes == NULL || output_bytes == NULL) {
+
+  // output is NULL when updateAAD
+  if (output != NULL) {
+    output_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, output, 
0);
+  }
+  if (input_bytes == NULL || (output != NULL && output_bytes == NULL)) {
     THROW(env, "java/lang/InternalError", "Cannot get buffer address.");
-    goto cleanup;
+    return 0;
   }
 
   int rc = dlsym_EVP_CipherUpdate(context, output_bytes + output_offset, 
&output_len,  \
@@ -429,6 +489,37 @@ cleanup:
   return output_len;
 }
 
+JNIEXPORT jint JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_updateByteArrayByteBuffer
+    (JNIEnv *env, jclass clazz, jlong ctx, jbyteArray input, jint input_offset,
+    jint input_len, jobject output, jint output_offset, jint max_output_len)
+{
+  EVP_CIPHER_CTX *context = CONTEXT(ctx);
+  if (!check_update_max_output_len(context, input_len, max_output_len)) {
+    THROW(env, "javax/crypto/ShortBufferException",  \
+        "Output buffer is not sufficient.");
+    return 0;
+  }
+  unsigned char *input_bytes = (unsigned char *) 
(*env)->GetByteArrayElements(env, input, 0);
+  unsigned char *output_bytes = (*env)->GetDirectBufferAddress(env, output);
+  if (input_bytes == NULL || output_bytes == NULL) {
+    THROW(env, "java/lang/InternalError", "Cannot get buffer address.");
+    return 0;
+  }
+  input_bytes = input_bytes + input_offset;
+  output_bytes = output_bytes + output_offset;
+
+  int output_len = 0;
+  if (!dlsym_EVP_CipherUpdate(context, output_bytes, &output_len,  \
+      input_bytes, input_len)) {
+    (*env)->ReleaseByteArrayElements(env, input, (jbyte *) input_bytes, 0);
+    dlsym_EVP_CIPHER_CTX_cleanup(context);
+    THROW(env, "java/lang/InternalError", "Error in EVP_CipherUpdate.");
+    return 0;
+  }
+  return output_len;
+}
+
+
 // https://www.openssl.org/docs/crypto/EVP_EncryptInit.html
 static int check_doFinal_max_output_len(EVP_CIPHER_CTX *context,
     int max_output_len)
@@ -464,8 +555,14 @@ JNIEXPORT jint JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_doFin
 
   int output_len = 0;
   if (!dlsym_EVP_CipherFinal_ex(context, output_bytes, &output_len)) {
+    // validate tag in GCM mode when decrypt
+    if ((context->cipher->flags & EVP_CIPH_MODE) == EVP_CIPH_GCM_MODE
+        && context->encrypt == DECRYPT_MODE) {
+      THROW(env, "javax/crypto/AEADBadTagException", "Tag mismatch!");
+    } else {
+      THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex.");
+    }
     dlsym_EVP_CIPHER_CTX_cleanup(context);
-    THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex.");
     return 0;
   }
   return output_len;
@@ -493,13 +590,64 @@ JNIEXPORT jint JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_doFin
   (*env)->ReleaseByteArrayElements(env, output, (jbyte *) output_bytes, 0);
 
   if (rc == 0) {
-    dlsym_EVP_CIPHER_CTX_cleanup(context);
+    // validate tag in GCM mode when decrypt
+    if ((context->cipher->flags & EVP_CIPH_MODE) == EVP_CIPH_GCM_MODE
+      && context->encrypt == DECRYPT_MODE) {
+    THROW(env, "javax/crypto/AEADBadTagException", "Tag mismatch!");
+    } else {
     THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex.");
+    }
+    dlsym_EVP_CIPHER_CTX_cleanup(context);
     return 0;
   }
   return output_len;
 }
 
+JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_ctrl
+    (JNIEnv *env, jclass clazz, jlong ctx, jint type, jint arg, jbyteArray 
data)
+{
+  EVP_CIPHER_CTX *context = CONTEXT(ctx);
+
+  int rc = 0;
+  void *data_ptr = NULL;
+  if (data != NULL) {
+    data_ptr = (void*) (*env)->GetByteArrayElements(env, data, 0);
+    if (data_ptr == NULL) {
+      THROW(env, "java/lang/InternalError", "Cannot get buffer address.");
+      return 0;
+    }
+  }
+
+  // get/set tag for GCM
+  if (type == EVP_CTRL_GCM_GET_TAG || type == EVP_CTRL_GCM_SET_TAG) {
+    if (arg <= 0 || arg > 16) {
+      THROW(env, "javax/crypto/AEADBadTagException", 
"TAG_LENGTH_INTERNAL_ERROR");
+      goto exit_;
+    }
+    if (data == NULL) {
+      THROW(env, "javax/crypto/AEADBadTagException", "tag is null");
+      goto exit_;
+    }
+
+    unsigned char *tag = (unsigned char*) data_ptr;
+    rc = dlsym_EVP_CIPHER_CTX_ctrl(context, type, arg, tag);
+    if (!rc) {
+      THROW(env, "javax/crypto/AEADBadTagException", "TAG_SET_ERROR or 
TAG_RETRIEVE_ERROR");
+      goto exit_;
+    }
+  } else {
+    THROW(env, "java.lang.UnsupportedOperationException", "Not implemented 
yet!");
+    goto exit_;
+  }
+
+exit_:
+  if (data_ptr != NULL) {
+    (*env)->ReleaseByteArrayElements(env, data, (jbyte *) data_ptr, 0);
+  }
+  return rc;
+}
+
+
 JNIEXPORT void JNICALL 
Java_org_apache_commons_crypto_cipher_OpenSslNative_clean
     (JNIEnv *env, jclass clazz, jlong ctx)
 {

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h
----------------------------------------------------------------------
diff --git 
a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h 
b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h
index 936cfb3..b980b77 100644
--- a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h
+++ b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h
@@ -216,12 +216,15 @@ static FARPROC WINAPI do_dlsym(JNIEnv *env, HMODULE 
handle, LPCSTR symbol) {
 #define ENCRYPT_MODE 1
 #define DECRYPT_MODE 0
 
-/** Currently only support AES/CTR/NoPadding. */
+/** Currently only support AES/CTR/NoPadding, AES/CBC/NoPadding, 
AES/CBC/PKCS5Padding, AES/GCM/NoPadding */
 #define AES_CTR 0
 #define AES_CBC 1
+#define AES_GCM 2
+
 #define NOPADDING 0
 #define PKCS5PADDING 1
 
+
 #endif
 
 //vim: sw=2: ts=2: et

http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/aad8c55a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
----------------------------------------------------------------------
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 a4cd2c6..4f43a0c 100644
--- a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
+++ b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java
@@ -24,6 +24,7 @@ import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
 
 import org.junit.Assert;
 import org.junit.Assume;
@@ -89,7 +90,7 @@ public class OpenSslCipherTest extends AbstractCipherTest {
                 .getInstance("AES/CTR/NoPadding");
         Assert.assertNotNull(cipher);
 
-        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, IV);
+        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV));
 
         // Require direct buffers
         ByteBuffer input = ByteBuffer.allocate(1024);
@@ -123,13 +124,14 @@ public class OpenSslCipherTest extends AbstractCipherTest 
{
                 .getInstance("AES/CTR/NoPadding");
         Assert.assertNotNull(cipher);
 
-        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, IV);
+        cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV));
 
         // Require direct buffer
+        ByteBuffer input = ByteBuffer.allocate(1024);
         ByteBuffer output = ByteBuffer.allocate(1024);
 
         try {
-            cipher.doFinal(output);
+            cipher.doFinal(input, output);
             Assert.fail("Output buffer should be direct buffer.");
         } catch (IllegalArgumentException e) {
             Assert.assertTrue(e.getMessage().contains(
@@ -147,7 +149,7 @@ public class OpenSslCipherTest extends AbstractCipherTest {
         final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
         try {
-            cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, IV);
+            cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new 
IvParameterSpec(IV));
             Assert.fail("java.security.InvalidKeyException should be thrown.");
         } catch (Exception e) {
             Assert.assertTrue(e.getMessage().contains("Invalid AES key length: 
" + invalidKey.length + " bytes"));
@@ -165,7 +167,7 @@ public class OpenSslCipherTest extends AbstractCipherTest {
         final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 };
         try {
-            cipher.init(OpenSsl.ENCRYPT_MODE, KEY, invalidIV);
+            cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new 
IvParameterSpec(invalidIV));
             Assert.fail("java.security.InvalidAlgorithmParameterException 
should be thrown.");
         } catch (Exception e) {
             Assert.assertTrue(e.getMessage().contains("Wrong IV length: must 
be 16 bytes long"));

Reply via email to