From c7c77fd32b1b1bc736ef3523456a2968447fc627 Mon Sep 17 00:00:00 2001
From: Anthony Scarpino <ascarpino@openjdk.org>
Date: Mon, 7 Jun 2021 22:22:28 +0000
Subject: [PATCH] 8255557: Decouple GCM from CipherCore

Reviewed-by: valeriep
---
 .../com/sun/crypto/provider/AESCipher.java    |  112 +-
 .../com/sun/crypto/provider/CipherCore.java   |  371 +--
 .../sun/crypto/provider/FeedbackCipher.java   |   68 +-
 .../classes/com/sun/crypto/provider/GCM.java  |   41 +
 .../classes/com/sun/crypto/provider/GCTR.java |  202 +-
 .../com/sun/crypto/provider/GHASH.java        |  215 +-
 .../crypto/provider/GaloisCounterMode.java    | 2294 +++++++++++------
 .../com/sun/crypto/provider/SunJCE.java       |   28 +-
 .../util/SecurityProviderConstants.java       |    2 -
 .../crypto/provider/Cipher/AEAD/Encrypt.java  |    9 +-
 .../provider/Cipher/AEAD/GCMBufferTest.java   |   92 +-
 .../provider/Cipher/AEAD/GCMShortBuffer.java  |  123 +
 .../Cipher/AEAD/OverlapByteBuffer.java        |    5 +-
 .../provider/Cipher/AES/TestAESCipher.java    |   66 +-
 .../provider/Cipher/AES/TestSameBuffer.java   |    4 +-
 15 files changed, 2087 insertions(+), 1545 deletions(-)
 create mode 100644 src/java.base/share/classes/com/sun/crypto/provider/GCM.java
 create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java

diff --git a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java
index dac33aa6211..c082dde20b8 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java
@@ -138,21 +138,6 @@ abstract class AESCipher extends CipherSpi {
             super(32, "CFB", "NOPADDING");
         }
     }
-    public static final class AES128_GCM_NoPadding extends OidImpl {
-        public AES128_GCM_NoPadding() {
-            super(16, "GCM", "NOPADDING");
-        }
-    }
-    public static final class AES192_GCM_NoPadding extends OidImpl {
-        public AES192_GCM_NoPadding() {
-            super(24, "GCM", "NOPADDING");
-        }
-    }
-    public static final class AES256_GCM_NoPadding extends OidImpl {
-        public AES256_GCM_NoPadding() {
-            super(32, "GCM", "NOPADDING");
-        }
-    }
 
     // utility method used by AESCipher and AESWrapCipher
     static final void checkKeySize(Key key, int fixedKeySize)
@@ -185,10 +170,6 @@ abstract class AESCipher extends CipherSpi {
      */
     private final int fixedKeySize; // in bytes, -1 if no restriction
 
-    /*
-     * needed to enforce ISE thrown when updateAAD is called after update for GCM mode.
-     */
-    private boolean updateCalled;
 
     /**
      * Creates an instance of AES cipher with default ECB mode and
@@ -322,7 +303,6 @@ abstract class AESCipher extends CipherSpi {
     protected void engineInit(int opmode, Key key, SecureRandom random)
         throws InvalidKeyException {
         checkKeySize(key, fixedKeySize);
-        updateCalled = false;
         core.init(opmode, key, random);
     }
 
@@ -355,7 +335,6 @@ abstract class AESCipher extends CipherSpi {
                               SecureRandom random)
         throws InvalidKeyException, InvalidAlgorithmParameterException {
         checkKeySize(key, fixedKeySize);
-        updateCalled = false;
         core.init(opmode, key, params, random);
     }
 
@@ -364,7 +343,6 @@ abstract class AESCipher extends CipherSpi {
                               SecureRandom random)
         throws InvalidKeyException, InvalidAlgorithmParameterException {
         checkKeySize(key, fixedKeySize);
-        updateCalled = false;
         core.init(opmode, key, params, random);
     }
 
@@ -389,7 +367,6 @@ abstract class AESCipher extends CipherSpi {
      */
     protected byte[] engineUpdate(byte[] input, int inputOffset,
                                   int inputLen) {
-        updateCalled = true;
         return core.update(input, inputOffset, inputLen);
     }
 
@@ -419,7 +396,6 @@ abstract class AESCipher extends CipherSpi {
     protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
                                byte[] output, int outputOffset)
         throws ShortBufferException {
-        updateCalled = true;
         return core.update(input, inputOffset, inputLen, output,
                            outputOffset);
     }
@@ -458,7 +434,6 @@ abstract class AESCipher extends CipherSpi {
     protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
         throws IllegalBlockSizeException, BadPaddingException {
         byte[] out = core.doFinal(input, inputOffset, inputLen);
-        updateCalled = false;
         return out;
     }
 
@@ -504,7 +479,6 @@ abstract class AESCipher extends CipherSpi {
                BadPaddingException {
         int outLen = core.doFinal(input, inputOffset, inputLen, output,
                                   outputOffset);
-        updateCalled = false;
         return outLen;
     }
 
@@ -577,86 +551,6 @@ abstract class AESCipher extends CipherSpi {
                            wrappedKeyType);
     }
 
-    /**
-     * Continues a multi-part update of the Additional Authentication
-     * Data (AAD), using a subset of the provided buffer.
-     * <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 src the buffer containing the AAD
-     * @param offset the offset in {@code src} where the AAD input starts
-     * @param len the number of AAD bytes
-     *
-     * @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 this method
-     * has not been overridden by an implementation
-     *
-     * @since 1.8
-     */
-    @Override
-    protected void engineUpdateAAD(byte[] src, int offset, int len) {
-        if (core.getMode() == CipherCore.GCM_MODE && updateCalled) {
-            throw new IllegalStateException("AAD must be supplied before encryption/decryption starts");
-        }
-        core.updateAAD(src, offset, len);
-    }
-
-    /**
-     * 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).
-     * <p>
-     * All {@code src.remaining()} bytes starting at
-     * {@code src.position()} are processed.
-     * Upon return, the input buffer's position will be equal
-     * to its limit; its limit will not have changed.
-     *
-     * @param src the buffer containing the AAD
-     *
-     * @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 this method
-     * has not been overridden by an implementation
-     *
-     * @since 1.8
-     */
-    @Override
-    protected void engineUpdateAAD(ByteBuffer src) {
-        if (core.getMode() == CipherCore.GCM_MODE && updateCalled) {
-            throw new IllegalStateException("AAD must be supplied before encryption/decryption starts");
-        }
-        if (src != null) {
-            int aadLen = src.limit() - src.position();
-            if (aadLen > 0) {
-                if (src.hasArray()) {
-                    int aadOfs = Math.addExact(src.arrayOffset(), src.position());
-                    core.updateAAD(src.array(), aadOfs, aadLen);
-                    src.position(src.limit());
-                } else {
-                    byte[] aad = new byte[aadLen];
-                    src.get(aad);
-                    core.updateAAD(aad, 0, aadLen);
-                }
-            }
-        }
-    }
-
     /**
      * Finalize crypto operation with ByteBuffers
      *
@@ -672,10 +566,6 @@ abstract class AESCipher extends CipherSpi {
     protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
         throws ShortBufferException, IllegalBlockSizeException,
         BadPaddingException {
-        if (core.getMode() == CipherCore.GCM_MODE && !input.hasArray()) {
-            return core.gcmDoFinal(input, output);
-        } else {
-            return super.engineDoFinal(input, output);
-        }
+        return super.engineDoFinal(input, output);
     }
 }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
index d8ad366ebd8..acef059d20a 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
@@ -25,7 +25,6 @@
 
 package com.sun.crypto.provider;
 
-import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -83,7 +82,6 @@ final class CipherCore {
      * currently, only the following cases have non-zero values:
      * 1) CTS mode - due to its special handling on the last two blocks
      * (the last one may be incomplete).
-     * 2) GCM mode + decryption - due to its trailing tag bytes
      */
     private int minBytes = 0;
 
@@ -125,24 +123,6 @@ final class CipherCore {
     private static final int PCBC_MODE = 4;
     private static final int CTR_MODE = 5;
     private static final int CTS_MODE = 6;
-    static final int GCM_MODE = 7;
-
-    /*
-     * variables used for performing the GCM (key+iv) uniqueness check.
-     * To use GCM mode safely, the cipher object must be re-initialized
-     * with a different combination of key + iv values for each
-     * encryption operation. However, checking all past key + iv values
-     * isn't feasible. Thus, we only do a per-instance check of the
-     * key + iv values used in previous encryption.
-     * For decryption operations, no checking is necessary.
-     * NOTE: this key+iv check have to be done inside CipherCore class
-     * since CipherCore class buffers potential tag bytes in GCM mode
-     * and may not call GaloisCounterMode when there isn't sufficient
-     * input to process.
-     */
-    private boolean requireReinit = false;
-    private byte[] lastEncKey = null;
-    private byte[] lastEncIv = null;
 
     /**
      * Creates an instance of CipherCore with default ECB mode and
@@ -197,15 +177,6 @@ final class CipherCore {
             cipher = new CounterMode(rawImpl);
             unitBytes = 1;
             padding = null;
-        }  else if (modeUpperCase.equals("GCM")) {
-            // can only be used for block ciphers w/ 128-bit block size
-            if (blockSize != 16) {
-                throw new NoSuchAlgorithmException
-                    ("GCM mode can only be used for AES cipher");
-            }
-            cipherMode = GCM_MODE;
-            cipher = new GaloisCounterMode(rawImpl);
-            padding = null;
         } else if (modeUpperCase.startsWith("CFB")) {
             cipherMode = CFB_MODE;
             unitBytes = getNumOfUnit(mode, "CFB".length(), blockSize);
@@ -224,15 +195,6 @@ final class CipherCore {
         }
     }
 
-    /**
-     * Returns the mode of this cipher.
-     *
-     * @return the parsed cipher mode
-     */
-    int getMode() {
-        return cipherMode;
-    }
-
     private static int getNumOfUnit(String mode, int offset, int blockSize)
         throws NoSuchAlgorithmException {
         int result = blockSize; // use blockSize as default value
@@ -279,17 +241,13 @@ final class CipherCore {
                                              + " not implemented");
         }
         if ((padding != null) &&
-            ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE)
-             || (cipherMode == GCM_MODE))) {
+            ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE))) {
             padding = null;
             String modeStr = null;
             switch (cipherMode) {
             case CTR_MODE:
                 modeStr = "CTR";
                 break;
-            case GCM_MODE:
-                modeStr = "GCM";
-                break;
             case CTS_MODE:
                 modeStr = "CTS";
                 break;
@@ -310,7 +268,7 @@ final class CipherCore {
      * <code>inputLen</code> (in bytes).
      *
      * <p>This call takes into account any unprocessed (buffered) data from a
-     * previous <code>update</code> call, padding, and AEAD tagging.
+     * previous <code>update</code> call, and padding.
      *
      * <p>The actual output length of the next <code>update</code> or
      * <code>doFinal</code> call may be smaller than the length returned by
@@ -326,36 +284,19 @@ final class CipherCore {
     }
 
     private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
-        int totalLen = Math.addExact(buffered, cipher.getBufferedLength());
+        int totalLen = buffered;
         totalLen = Math.addExact(totalLen, inputLen);
-        switch (cipherMode) {
-        case GCM_MODE:
-            if (isDoFinal) {
-                int tagLen = ((GaloisCounterMode) cipher).getTagLen();
-                if (!decrypting) {
-                    totalLen = Math.addExact(totalLen, tagLen);
+        if (padding != null && !decrypting) {
+            if (unitBytes != blockSize) {
+                if (totalLen < diffBlocksize) {
+                    totalLen = diffBlocksize;
                 } else {
-                    totalLen -= tagLen;
+                    int residue = (totalLen - diffBlocksize) % blockSize;
+                    totalLen = Math.addExact(totalLen, (blockSize - residue));
                 }
+            } else {
+                totalLen = Math.addExact(totalLen, padding.padLength(totalLen));
             }
-            if (totalLen < 0) {
-                totalLen = 0;
-            }
-            break;
-        default:
-            if (padding != null && !decrypting) {
-                if (unitBytes != blockSize) {
-                    if (totalLen < diffBlocksize) {
-                        totalLen = diffBlocksize;
-                    } else {
-                        int residue = (totalLen - diffBlocksize) % blockSize;
-                        totalLen = Math.addExact(totalLen, (blockSize - residue));
-                    }
-                } else {
-                    totalLen = Math.addExact(totalLen, padding.padLength(totalLen));
-                }
-            }
-            break;
         }
         return totalLen;
     }
@@ -398,26 +339,15 @@ final class CipherCore {
         AlgorithmParameterSpec spec;
         byte[] iv = getIV();
         if (iv == null) {
-            // generate spec using default value
-            if (cipherMode == GCM_MODE) {
-                iv = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
-            } else {
-                iv = new byte[blockSize];
-            }
+            iv = new byte[blockSize];
             SunJCE.getRandom().nextBytes(iv);
         }
-        if (cipherMode == GCM_MODE) {
-            algName = "GCM";
-            spec = new GCMParameterSpec
-                (((GaloisCounterMode) cipher).getTagLen()*8, iv);
+        if (algName.equals("RC2")) {
+            RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
+            spec = new RC2ParameterSpec
+                (rawImpl.getEffectiveKeyBits(), iv);
         } else {
-           if (algName.equals("RC2")) {
-               RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
-               spec = new RC2ParameterSpec
-                   (rawImpl.getEffectiveKeyBits(), iv);
-           } else {
-               spec = new IvParameterSpec(iv);
-           }
+            spec = new IvParameterSpec(iv);
         }
         try {
             params = AlgorithmParameters.getInstance(algName,
@@ -504,106 +434,51 @@ final class CipherCore {
                   || (opmode == Cipher.UNWRAP_MODE);
 
         byte[] keyBytes = getKeyBytes(key);
-        try {
-            int tagLen = -1;
-            byte[] ivBytes = null;
-            if (params != null) {
-                if (cipherMode == GCM_MODE) {
-                    if (params instanceof GCMParameterSpec) {
-                        tagLen = ((GCMParameterSpec) params).getTLen();
-                        if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) {
-                            throw new InvalidAlgorithmParameterException
-                                    ("Unsupported TLen value; must be one of " +
-                                            "{128, 120, 112, 104, 96}");
-                        }
-                        tagLen = tagLen >> 3;
-                        ivBytes = ((GCMParameterSpec) params).getIV();
-                    } else {
-                        throw new InvalidAlgorithmParameterException
-                                ("Unsupported parameter: " + params);
-                    }
-                } else {
-                    if (params instanceof IvParameterSpec) {
-                        ivBytes = ((IvParameterSpec) params).getIV();
-                        if ((ivBytes == null) || (ivBytes.length != blockSize)) {
-                            throw new InvalidAlgorithmParameterException
-                                    ("Wrong IV length: must be " + blockSize +
-                                            " bytes long");
-                        }
-                    } else if (params instanceof RC2ParameterSpec) {
-                        ivBytes = ((RC2ParameterSpec) params).getIV();
-                        if ((ivBytes != null) && (ivBytes.length != blockSize)) {
-                            throw new InvalidAlgorithmParameterException
-                                    ("Wrong IV length: must be " + blockSize +
-                                            " bytes long");
-                        }
-                    } else {
-                        throw new InvalidAlgorithmParameterException
-                                ("Unsupported parameter: " + params);
-                    }
-                }
-            }
-            if (cipherMode == ECB_MODE) {
-                if (ivBytes != null) {
+        byte[] ivBytes = null;
+        if (params != null) {
+            if (params instanceof IvParameterSpec) {
+                ivBytes = ((IvParameterSpec) params).getIV();
+                if ((ivBytes == null) || (ivBytes.length != blockSize)) {
                     throw new InvalidAlgorithmParameterException
-                            ("ECB mode cannot use IV");
+                        ("Wrong IV length: must be " + blockSize +
+                            " bytes long");
                 }
-            } else if (ivBytes == null) {
-                if (decrypting) {
-                    throw new InvalidAlgorithmParameterException("Parameters "
-                            + "missing");
+            } else if (params instanceof RC2ParameterSpec) {
+                ivBytes = ((RC2ParameterSpec) params).getIV();
+                if ((ivBytes != null) && (ivBytes.length != blockSize)) {
+                    throw new InvalidAlgorithmParameterException
+                        ("Wrong IV length: must be " + blockSize +
+                            " bytes long");
                 }
-
-                if (random == null) {
-                    random = SunJCE.getRandom();
-                }
-                if (cipherMode == GCM_MODE) {
-                    ivBytes = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
-                } else {
-                    ivBytes = new byte[blockSize];
-                }
-                random.nextBytes(ivBytes);
-            }
-
-            buffered = 0;
-            diffBlocksize = blockSize;
-
-            String algorithm = key.getAlgorithm();
-
-            // GCM mode needs additional handling
-            if (cipherMode == GCM_MODE) {
-                if (tagLen == -1) {
-                    tagLen = GaloisCounterMode.DEFAULT_TAG_LEN;
-                }
-                if (decrypting) {
-                    minBytes = tagLen;
-                } else {
-                    // check key+iv for encryption in GCM mode
-                    requireReinit =
-                            Arrays.equals(ivBytes, lastEncIv) &&
-                                    MessageDigest.isEqual(keyBytes, lastEncKey);
-                    if (requireReinit) {
-                        throw new InvalidAlgorithmParameterException
-                                ("Cannot reuse iv for GCM encryption");
-                    }
-                    lastEncIv = ivBytes;
-                    if (lastEncKey != null) {
-                        Arrays.fill(lastEncKey, (byte) 0);
-                    }
-                    lastEncKey = keyBytes;
-                }
-                ((GaloisCounterMode) cipher).init
-                        (decrypting, algorithm, keyBytes, ivBytes, tagLen);
             } else {
-                cipher.init(decrypting, algorithm, keyBytes, ivBytes);
-            }
-            // skip checking key+iv from now on until after doFinal()
-            requireReinit = false;
-        } finally {
-            if (lastEncKey != keyBytes) {
-                Arrays.fill(keyBytes, (byte) 0);
+                throw new InvalidAlgorithmParameterException
+                    ("Unsupported parameter: " + params);
             }
         }
+        if (cipherMode == ECB_MODE) {
+            if (ivBytes != null) {
+                throw new InvalidAlgorithmParameterException
+                    ("ECB mode cannot use IV");
+            }
+        } else if (ivBytes == null) {
+            if (decrypting) {
+                throw new InvalidAlgorithmParameterException("Parameters "
+                    + "missing");
+            }
+
+            if (random == null) {
+                random = SunJCE.getRandom();
+            }
+
+            ivBytes = new byte[blockSize];
+            random.nextBytes(ivBytes);
+        }
+
+        buffered = 0;
+        diffBlocksize = blockSize;
+
+        String algorithm = key.getAlgorithm();
+        cipher.init(decrypting, algorithm, keyBytes, ivBytes);
     }
 
     void init(int opmode, Key key, AlgorithmParameters params,
@@ -613,16 +488,11 @@ final class CipherCore {
         String paramType = null;
         if (params != null) {
             try {
-                if (cipherMode == GCM_MODE) {
-                    paramType = "GCM";
-                    spec = params.getParameterSpec(GCMParameterSpec.class);
-                } else {
-                    // NOTE: RC2 parameters are always handled through
-                    // init(..., AlgorithmParameterSpec,...) method, so
-                    // we can assume IvParameterSpec type here.
-                    paramType = "IV";
-                    spec = params.getParameterSpec(IvParameterSpec.class);
-                }
+                // NOTE: RC2 parameters are always handled through
+                // init(..., AlgorithmParameterSpec,...) method, so
+                // we can assume IvParameterSpec type here.
+                paramType = "IV";
+                spec = params.getParameterSpec(IvParameterSpec.class);
             } catch (InvalidParameterSpecException ipse) {
                 throw new InvalidAlgorithmParameterException
                     ("Wrong parameter type: " + paramType + " expected");
@@ -671,7 +541,6 @@ final class CipherCore {
      * (e.g., has not been initialized)
      */
     byte[] update(byte[] input, int inputOffset, int inputLen) {
-        checkReinit();
 
         byte[] output = null;
         try {
@@ -719,7 +588,6 @@ final class CipherCore {
      */
     int update(byte[] input, int inputOffset, int inputLen, byte[] output,
                int outputOffset) throws ShortBufferException {
-        checkReinit();
 
         // figure out how much can be sent to crypto function
         int len = Math.addExact(buffered, inputLen);
@@ -854,7 +722,6 @@ final class CipherCore {
     byte[] doFinal(byte[] input, int inputOffset, int inputLen)
         throws IllegalBlockSizeException, BadPaddingException {
         try {
-            checkReinit();
             byte[] output = new byte[getOutputSizeByOperation(inputLen, true)];
             byte[] finalBuf = prepareInputBuffer(input, inputOffset,
                     inputLen, output, 0);
@@ -868,7 +735,7 @@ final class CipherCore {
             if (outLen < output.length) {
                 byte[] copy = Arrays.copyOf(output, outLen);
                 if (decrypting) {
-                    // Zero out internal (ouput) array
+                    // Zero out internal (output) array
                     Arrays.fill(output, (byte) 0x00);
                 }
                 return copy;
@@ -921,7 +788,6 @@ final class CipherCore {
                 int outputOffset)
         throws IllegalBlockSizeException, ShortBufferException,
                BadPaddingException {
-        checkReinit();
 
         int estOutSize = getOutputSizeByOperation(inputLen, true);
         int outputCapacity = checkOutputCapacity(output, outputOffset,
@@ -943,15 +809,13 @@ final class CipherCore {
             if (outputCapacity < estOutSize) {
                 cipher.save();
             }
-            if (getMode() != GCM_MODE || outputCapacity < estOutSize) {
-                // create temporary output buffer if the estimated size is larger
-                // than the user-provided buffer.
-                internalOutput = new byte[estOutSize];
-                offset = 0;
-            }
+            // create temporary output buffer if the estimated size is larger
+            // than the user-provided buffer.
+            internalOutput = new byte[estOutSize];
+            offset = 0;
         }
-        byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
 
+        byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
         int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer,
                 offset, finalBufLen, input);
 
@@ -961,13 +825,13 @@ final class CipherCore {
                 // restore so users can retry with a larger buffer
                 cipher.restore();
                 throw new ShortBufferException("Output buffer too short: "
-                                               + (outputCapacity)
-                                               + " bytes given, " + outLen
-                                               + " bytes needed");
+                    + (outputCapacity) + " bytes given, " + outLen
+                    + " bytes needed");
             }
             // copy the result into user-supplied output buffer
             if (internalOutput != null) {
-                System.arraycopy(internalOutput, 0, output, outputOffset, outLen);
+                System.arraycopy(internalOutput, 0, output, outputOffset,
+                    outLen);
                 // decrypt mode. Zero out output data that's not required
                 Arrays.fill(internalOutput, (byte) 0x00);
             }
@@ -1001,7 +865,7 @@ final class CipherCore {
         // calculate total input length
         int len = Math.addExact(buffered, inputLen);
         // calculate padding length
-        int totalLen = Math.addExact(len, cipher.getBufferedLength());
+        int totalLen = len;
         int paddingLen = 0;
         // will the total input length be a multiple of blockSize?
         if (unitBytes != blockSize) {
@@ -1059,30 +923,27 @@ final class CipherCore {
     }
 
     private int fillOutputBuffer(byte[] finalBuf, int finalOffset,
-                                 byte[] output, int outOfs, int finalBufLen,
-                                 byte[] input)
-            throws ShortBufferException, BadPaddingException,
-            IllegalBlockSizeException {
+        byte[] output, int outOfs, int finalBufLen, byte[] input)
+        throws ShortBufferException, BadPaddingException,
+        IllegalBlockSizeException {
+
         int len;
         try {
             len = finalNoPadding(finalBuf, finalOffset, output,
-                    outOfs, finalBufLen);
+                outOfs, finalBufLen);
             if (decrypting && padding != null) {
                 len = unpad(len, outOfs, output);
             }
             return len;
         } finally {
-            if (!decrypting) {
-                // reset after doFinal() for GCM encryption
-                requireReinit = (cipherMode == GCM_MODE);
-                if (finalBuf != input) {
-                    // done with internal finalBuf array. Copied to output
-                    Arrays.fill(finalBuf, (byte) 0x00);
-                }
+            if (!decrypting && finalBuf != input) {
+                // done with internal finalBuf array. Copied to output
+                Arrays.fill(finalBuf, (byte) 0x00);
             }
         }
     }
 
+
     private int checkOutputCapacity(byte[] output, int outputOffset,
                             int estOutSize) throws ShortBufferException {
         // check output buffer capacity.
@@ -1098,23 +959,14 @@ final class CipherCore {
         return outputCapacity;
     }
 
-    private void checkReinit() {
-        if (requireReinit) {
-            throw new IllegalStateException
-                ("Must use either different key or iv for GCM encryption");
-        }
-    }
-
     private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
                                int len)
-        throws IllegalBlockSizeException, AEADBadTagException,
-        ShortBufferException {
+        throws IllegalBlockSizeException, ShortBufferException {
 
-        if ((cipherMode != GCM_MODE) && (in == null || len == 0)) {
+        if (in == null || len == 0) {
             return 0;
         }
         if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE) &&
-            (cipherMode != GCM_MODE) &&
             ((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) {
                 if (padding != null) {
                     throw new IllegalBlockSizeException
@@ -1126,7 +978,7 @@ final class CipherCore {
                          + " bytes");
                 }
         }
-        int outLen = 0;
+        int outLen;
         if (decrypting) {
             outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs);
         } else {
@@ -1217,59 +1069,4 @@ final class CipherCore {
             Arrays.fill(encodedKey, (byte)0);
         }
     }
-
-    /**
-     * Continues a multi-part update of the Additional Authentication
-     * Data (AAD), using a subset of the provided buffer.
-     * <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 src the buffer containing the AAD
-     * @param offset the offset in {@code src} where the AAD input starts
-     * @param len the number of AAD bytes
-     *
-     * @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 this method
-     * has not been overridden by an implementation
-     *
-     * @since 1.8
-     */
-    void updateAAD(byte[] src, int offset, int len) {
-        checkReinit();
-        cipher.updateAAD(src, offset, len);
-    }
-
-    // This must only be used with GCM.
-    // If some data has been buffered from an update call, operate on the buffer
-    // then run doFinal.
-    int gcmDoFinal(ByteBuffer src, ByteBuffer dst) throws ShortBufferException,
-        IllegalBlockSizeException, BadPaddingException {
-        int estOutSize = getOutputSizeByOperation(src.remaining(), true);
-        if (estOutSize > dst.remaining()) {
-            throw new ShortBufferException("output buffer too small");
-        }
-
-        int len;
-        if (decrypting) {
-            if (buffered > 0) {
-                cipher.decrypt(buffer, 0, buffered, new byte[0], 0);
-            }
-            len = cipher.decryptFinal(src, dst);
-        } else {
-            if (buffered > 0) {
-                ((GaloisCounterMode)cipher).encrypt(buffer, 0, buffered);
-            }
-            len = cipher.encryptFinal(src, dst);
-        }
-        endDoFinal();
-        return len;
-    }
 }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java
index c7bb57664a1..6c0e10eb6d0 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,6 @@
 
 package com.sun.crypto.provider;
 
-import java.nio.ByteBuffer;
 import java.security.InvalidKeyException;
 import java.security.InvalidAlgorithmParameterException;
 import javax.crypto.*;
@@ -200,70 +199,7 @@ abstract class FeedbackCipher {
      */
      int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
                       byte[] plain, int plainOffset)
-         throws IllegalBlockSizeException, AEADBadTagException,
-         ShortBufferException {
+         throws IllegalBlockSizeException, ShortBufferException {
          return decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
      }
-
-    /**
-     * Continues a multi-part update of the Additional Authentication
-     * Data (AAD), using a subset of the provided buffer. 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).
-     * <p>
-     * NOTE: Given most modes do not accept AAD, default impl for this
-     * method throws IllegalStateException.
-     *
-     * @param src the buffer containing the AAD
-     * @param offset the offset in {@code src} where the AAD input starts
-     * @param len the number of AAD bytes
-     *
-     * @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 this method
-     * has not been overridden by an implementation
-     *
-     * @since 1.8
-     */
-    void updateAAD(byte[] src, int offset, int len) {
-        throw new IllegalStateException("No AAD accepted");
-    }
-
-    /**
-     * @return the number of bytes that are buffered internally inside
-     * this FeedbackCipher instance.
-     * @since 1.8
-     */
-    int getBufferedLength() {
-        // Currently only AEAD cipher impl, e.g. GCM, buffers data
-        // internally during decryption mode
-        return 0;
-    }
-
-    /*
-     * ByteBuffer methods should not be accessed as CipherCore and AESCipher
-     * copy the data to byte arrays.  These methods are to satisfy the compiler.
-     */
-    int encrypt(ByteBuffer src, ByteBuffer dst) {
-        throw new UnsupportedOperationException("ByteBuffer not supported");
-    };
-
-    int decrypt(ByteBuffer src, ByteBuffer dst) {
-        throw new UnsupportedOperationException("ByteBuffer not supported");
-    };
-
-    int encryptFinal(ByteBuffer src, ByteBuffer dst)
-        throws IllegalBlockSizeException, ShortBufferException {
-        throw new UnsupportedOperationException("ByteBuffer not supported");
-    };
-
-    int decryptFinal(ByteBuffer src, ByteBuffer dst)
-        throws IllegalBlockSizeException, AEADBadTagException,
-        ShortBufferException {
-        throw new UnsupportedOperationException("ByteBuffer not supported");
-    }
 }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GCM.java b/src/java.base/share/classes/com/sun/crypto/provider/GCM.java
new file mode 100644
index 00000000000..3bb05a0c16a
--- /dev/null
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GCM.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.crypto.provider;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This interface allows GHASH.java and GCTR.java to easily operate to
+ * better operate with GaloisCounterMode.java
+ */
+
+public interface GCM {
+    int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs);
+    int update(byte[] in, int inOfs, int inLen, ByteBuffer dst);
+    int update(ByteBuffer src, ByteBuffer dst);
+    int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs);
+    int doFinal(ByteBuffer src, ByteBuffer dst);
+}
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java
index 1a09a617a4d..7aaec3d6c1a 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java
@@ -31,8 +31,7 @@ package com.sun.crypto.provider;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import javax.crypto.IllegalBlockSizeException;
-import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
+import java.util.Arrays;
 
 /**
  * This class represents the GCTR function defined in NIST 800-38D
@@ -52,17 +51,18 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
  *
  * @since 1.8
  */
-final class GCTR extends CounterMode {
+final class GCTR extends CounterMode implements GCM {
 
     // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
     private static final int MAX_LEN = 1024;
+    private byte[] block;
 
     GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
         super(cipher);
-        if (initialCounterBlk.length != AES_BLOCK_SIZE) {
+        if (initialCounterBlk.length != blockSize) {
             throw new RuntimeException("length of initial counter block (" +
-                initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" +
-                AES_BLOCK_SIZE + ")");
+                initialCounterBlk.length + ") not equal to blockSize (" +
+                blockSize + ")");
         }
 
         iv = initialCounterBlk;
@@ -83,30 +83,47 @@ final class GCTR extends CounterMode {
         return blocksLeft;
     }
 
-    // input must be multiples of 128-bit blocks when calling update
-    int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
+    private void checkBlock() {
+        if (block == null) {
+            block = new byte[blockSize];
+        } else {
+            Arrays.fill(block, (byte)0);
+        }
+    }
+
+    /**
+     * Using the given inLen, this operates only on blockSize data, leaving
+     * the remainder in 'in'.
+     * The return value will be (inLen - (inLen % blockSize))
+     */
+    public int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
+        if (inLen == 0) {
+            return 0;
+        }
+
         if (inLen - inOfs > in.length) {
             throw new RuntimeException("input length out of bound");
         }
-        if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) {
+        if (inLen < 0) {
             throw new RuntimeException("input length unsupported");
         }
-        if (out.length - outOfs < inLen) {
+        if (out.length - outOfs < (inLen - (inLen % blockSize))) {
             throw new RuntimeException("output buffer too small");
         }
 
+        inLen -= inLen % blockSize;
         long blocksLeft = blocksUntilRollover();
-        int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE;
+        int numOfCompleteBlocks = inLen / blockSize;
         if (numOfCompleteBlocks >= blocksLeft) {
             // Counter Mode encryption cannot be used because counter will
             // roll over incorrectly. Use GCM-specific code instead.
-            byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
+            checkBlock();
             for (int i = 0; i < numOfCompleteBlocks; i++) {
-                embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
-                for (int n = 0; n < AES_BLOCK_SIZE; n++) {
-                    int index = (i * AES_BLOCK_SIZE + n);
+                embeddedCipher.encryptBlock(counter, 0, block, 0);
+                for (int n = 0; n < blockSize; n++) {
+                    int index = (i * blockSize + n);
                     out[outOfs + index] =
-                        (byte) ((in[inOfs + index] ^ encryptedCntr[n]));
+                        (byte) ((in[inOfs + index] ^ block[n]));
                 }
                 GaloisCounterMode.increment32(counter);
             }
@@ -116,34 +133,46 @@ final class GCTR extends CounterMode {
         }
     }
 
-    // input must be multiples of AES blocks, 128-bit, when calling update
-    int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
+    /**
+     * Operate on only blocksize data leaving the remainder in 'in' .
+     */
+    public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
+        // If the bytebuffer is backed by arrays, use that instead of
+        // allocating and copying for direct bytebuffers
+        if (!dst.isDirect()) {
+            int len = update(in, inOfs, inLen,  dst.array(),
+                dst.arrayOffset() + dst.position());
+            dst.position(dst.position() + len);
+            return len;
+        }
+
+        // Direct ByteBuffer operation
         if (inLen - inOfs > in.length) {
             throw new RuntimeException("input length out of bound");
         }
-        if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) {
+        if (inLen < 0) {
             throw new RuntimeException("input length unsupported");
         }
         // See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for
         // details on the check for 'dst' having enough space for the result.
 
         long blocksLeft = blocksUntilRollover();
-        int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE;
+        int numOfCompleteBlocks = inLen / blockSize;
         if (numOfCompleteBlocks >= blocksLeft) {
             // Counter Mode encryption cannot be used because counter will
             // roll over incorrectly. Use GCM-specific code instead.
-            byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
+            checkBlock();
             for (int i = 0; i < numOfCompleteBlocks; i++) {
-                embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
-                for (int n = 0; n < AES_BLOCK_SIZE; n++) {
-                    int index = (i * AES_BLOCK_SIZE + n);
-                    dst.put((byte) ((in[inOfs + index] ^ encryptedCntr[n])));
+                embeddedCipher.encryptBlock(counter, 0, block, 0);
+                for (int n = 0; n < blockSize; n++) {
+                    int index = (i * blockSize + n);
+                    dst.put((byte) ((in[inOfs + index] ^ block[n])));
                 }
                 GaloisCounterMode.increment32(counter);
             }
             return inLen;
         } else {
-            int len = inLen - inLen % AES_BLOCK_SIZE;
+            int len = inLen - inLen % blockSize;
             int processed = len;
             byte[] out = new byte[Math.min(MAX_LEN, len)];
             int offset = inOfs;
@@ -162,26 +191,41 @@ final class GCTR extends CounterMode {
         }
     }
 
-    // input operates on multiples of AES blocks, 128-bit, when calling update.
-    // The remainder is left in the src buffer.
-    int update(ByteBuffer src, ByteBuffer dst) {
+    /**
+     * Operate on only blocksize data leaving the remainder in the src buffer.
+     */
+    public int update(ByteBuffer src, ByteBuffer dst) {
+        int len;
+
+        // If the bytebuffer is backed by arrays, use that instead of
+        // allocating and copying for direct bytebuffers
+        if (src.hasArray() && dst.hasArray()) {
+            len = update(src.array(), src.arrayOffset() + src.position(),
+                src.remaining() - (src.remaining() % blockSize),
+                dst.array(), dst.arrayOffset() + dst.position());
+            src.position(src.position() + len);
+            dst.position(dst.position() + len);
+            return len;
+        }
+
+        // Direct bytebuffer operation
         long blocksLeft = blocksUntilRollover();
-        int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE;
+        int numOfCompleteBlocks = src.remaining() / blockSize;
         if (numOfCompleteBlocks >= blocksLeft) {
             // Counter Mode encryption cannot be used because counter will
             // roll over incorrectly. Use GCM-specific code instead.
-            byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
+            checkBlock();
             for (int i = 0; i < numOfCompleteBlocks; i++) {
-                embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
-                for (int n = 0; n < AES_BLOCK_SIZE; n++) {
-                    dst.put((byte) (src.get() ^ encryptedCntr[n]));
+                embeddedCipher.encryptBlock(counter, 0, block, 0);
+                for (int n = 0; n < blockSize; n++) {
+                    dst.put((byte) (src.get() ^ block[n]));
                 }
                 GaloisCounterMode.increment32(counter);
             }
-            return numOfCompleteBlocks * AES_BLOCK_SIZE;
+            return numOfCompleteBlocks * blockSize;
         }
 
-        int len = src.remaining() - (src.remaining() % AES_BLOCK_SIZE);
+        len = src.remaining() - (src.remaining() % blockSize);
         int processed = len;
         byte[] in = new byte[Math.min(MAX_LEN, len)];
         while (processed > MAX_LEN) {
@@ -196,50 +240,62 @@ final class GCTR extends CounterMode {
         return len;
     }
 
-    // input can be arbitrary size when calling doFinal
-    int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
-        int outOfs) throws IllegalBlockSizeException {
-        try {
-            if (inLen < 0) {
-                throw new IllegalBlockSizeException("Negative input size!");
-            } else if (inLen > 0) {
-                int lastBlockSize = inLen % AES_BLOCK_SIZE;
-                int completeBlkLen = inLen - lastBlockSize;
-                // process the complete blocks first
-                update(in, inOfs, completeBlkLen, out, outOfs);
-                if (lastBlockSize != 0) {
-                    // do the last partial block
-                    byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
-                    embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
-                    for (int n = 0; n < lastBlockSize; n++) {
-                        out[outOfs + completeBlkLen + n] =
-                            (byte) ((in[inOfs + completeBlkLen + n] ^
-                                encryptedCntr[n]));
-                    }
-                }
+    /**
+     * doFinal operation by using update() for any full block operations needed,
+     * then operating on the final bytes in the input buffer.
+     *
+     * This method will not write any block padding to the output buffer
+     */
+    public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+        int outOfs) {
+        if (inLen == 0) {
+            return 0;
+        }
+        int lastBlockSize = inLen % blockSize;
+        int completeBlkLen = inLen - lastBlockSize;
+        // process the complete blocks first
+        update(in, inOfs, completeBlkLen, out, outOfs);
+        if (lastBlockSize != 0) {
+            // do the last partial block
+            checkBlock();
+            embeddedCipher.encryptBlock(counter, 0, block, 0);
+            for (int n = 0; n < lastBlockSize; n++) {
+                out[outOfs + completeBlkLen + n] =
+                    (byte) ((in[inOfs + completeBlkLen + n] ^ block[n]));
             }
-        } finally {
-            reset();
         }
         return inLen;
     }
 
-    // src can be arbitrary size when calling doFinal
-    int doFinal(ByteBuffer src, ByteBuffer dst) {
+    /**
+     * doFinal operation by using update() for any full block operations needed,
+     * then operating on the final bytes in the input buffer.
+     *
+     * If src and dst are array-backed bytebuffers, call doFinal(byte[]...) for
+     * less memory usage.
+     */
+    public int doFinal(ByteBuffer src, ByteBuffer dst) {
+        // If the bytebuffer is backed by arrays, use that instead of
+        // allocating and copying for direct bytebuffers
+        if (src.hasArray() && dst.hasArray()) {
+            int len = doFinal(src.array(), src.arrayOffset() + src.position(),
+                src.remaining(), dst.array(),
+                dst.arrayOffset() + dst.position());
+            src.position(src.position() + len);
+            dst.position(dst.position() + len);
+            return len;
+        }
+
         int len = src.remaining();
-        int lastBlockSize = len % AES_BLOCK_SIZE;
-        try {
-            update(src, dst);
-            if (lastBlockSize != 0) {
-                // do the last partial block
-                byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
-                embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
-                for (int n = 0; n < lastBlockSize; n++) {
-                    dst.put((byte) (src.get() ^ encryptedCntr[n]));
-                }
+        int lastBlockSize = len % blockSize;
+        update(src, dst);
+        if (lastBlockSize != 0) {
+            checkBlock();
+            // do the last partial block
+            embeddedCipher.encryptBlock(counter, 0, block, 0);
+            for (int n = 0; n < lastBlockSize; n++) {
+                dst.put((byte) (src.get() ^ block[n]));
             }
-        } finally {
-            reset();
         }
         return len;
     }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java
index e9ce33b6b58..c0815524dd7 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2015 Red Hat, Inc.
+ * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,11 +24,15 @@
  */
 /*
  * (C) Copyright IBM Corp. 2013
+ * Copyright (c) 2015 Red Hat, Inc.
  */
 
 package com.sun.crypto.provider;
 
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.security.ProviderException;
 
 import jdk.internal.vm.annotation.IntrinsicCandidate;
@@ -44,27 +47,19 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
  *
  * @since 1.8
  */
-final class GHASH {
 
-    private static long getLong(byte[] buffer, int offset) {
-        long result = 0;
-        int end = offset + 8;
-        for (int i = offset; i < end; ++i) {
-            result = (result << 8) + (buffer[i] & 0xFF);
-        }
-        return result;
-    }
-
-    private static void putLong(byte[] buffer, int offset, long value) {
-        int end = offset + 8;
-        for (int i = end - 1; i >= offset; --i) {
-            buffer[i] = (byte) value;
-            value >>= 8;
-        }
-    }
+final class GHASH implements Cloneable, GCM {
 
     private static final int AES_BLOCK_SIZE = 16;
 
+    // Handle for converting byte[] <-> long
+    private static final VarHandle asLongView =
+        MethodHandles.byteArrayViewVarHandle(long[].class,
+            ByteOrder.BIG_ENDIAN);
+
+    // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
+    private static final int MAX_LEN = 1024;
+
     // Multiplies state[0], state[1] by subkeyH[0], subkeyH[1].
     private static void blockMult(long[] st, long[] subH) {
         long Z0 = 0;
@@ -127,15 +122,13 @@ final class GHASH {
 
     /* subkeyHtbl and state are stored in long[] for GHASH intrinsic use */
 
-    // hashtable subkeyHtbl; holds 2*9 powers of subkeyH computed using carry-less multiplication
+    // hashtable subkeyHtbl holds 2*9 powers of subkeyH computed using
+    // carry-less multiplication
     private long[] subkeyHtbl;
 
     // buffer for storing hash
     private final long[] state;
 
-    // variables for save/restore calls
-    private long stateSave0, stateSave1;
-
     /**
      * Initializes the cipher in the specified mode with the given key
      * and iv.
@@ -151,87 +144,106 @@ final class GHASH {
         }
         state = new long[2];
         subkeyHtbl = new long[2*9];
-        subkeyHtbl[0] = getLong(subkeyH, 0);
-        subkeyHtbl[1] = getLong(subkeyH, 8);
+        subkeyHtbl[0] = (long)asLongView.get(subkeyH, 0);
+        subkeyHtbl[1] = (long)asLongView.get(subkeyH, 8);
     }
 
-    /**
-     * Resets the GHASH object to its original state, i.e. blank w/
-     * the same subkey H. Used after digest() is called and to re-use
-     * this object for different data w/ the same H.
-     */
-    void reset() {
-        state[0] = 0;
-        state[1] = 0;
+    // Cloning constructor
+    private GHASH(GHASH g) {
+        state = g.state.clone();
+        subkeyHtbl = g.subkeyHtbl.clone();
     }
 
-    /**
-     * Save the current snapshot of this GHASH object.
-     */
-    void save() {
-        stateSave0 = state[0];
-        stateSave1 = state[1];
+    @Override
+    public GHASH clone() {
+        return new GHASH(this);
     }
 
-    /**
-     * Restores this object using the saved snapshot.
-     */
-    void restore() {
-        state[0] = stateSave0;
-        state[1] = stateSave1;
-    }
-
-    private static void processBlock(byte[] data, int ofs, long[] st, long[] subH) {
-        st[0] ^= getLong(data, ofs);
-        st[1] ^= getLong(data, ofs + 8);
+    private static void processBlock(byte[] data, int ofs, long[] st,
+        long[] subH) {
+        st[0] ^= (long)asLongView.get(data, ofs);
+        st[1] ^= (long)asLongView.get(data, ofs + 8);
         blockMult(st, subH);
     }
 
-    void update(byte[] in) {
-        update(in, 0, in.length);
+    int update(byte[] in) {
+        return update(in, 0, in.length);
     }
 
-    void update(byte[] in, int inOfs, int inLen) {
+    int update(byte[] in, int inOfs, int inLen) {
         if (inLen == 0) {
-            return;
+            return 0;
         }
-        ghashRangeCheck(in, inOfs, inLen, state, subkeyHtbl);
-        processBlocks(in, inOfs, inLen/AES_BLOCK_SIZE, state, subkeyHtbl);
+        int len = inLen - (inLen % AES_BLOCK_SIZE);
+        ghashRangeCheck(in, inOfs, len, state, subkeyHtbl);
+        processBlocks(in, inOfs, len / AES_BLOCK_SIZE, state, subkeyHtbl);
+        return len;
     }
 
-    // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
-    private static final int MAX_LEN = 1024;
-
     // Will process as many blocks it can and will leave the remaining.
-    int update(ByteBuffer src, int inLen) {
+    int update(ByteBuffer ct, int inLen) {
         inLen -= (inLen % AES_BLOCK_SIZE);
         if (inLen == 0) {
             return 0;
         }
 
-        int processed = inLen;
-        byte[] in = new byte[Math.min(MAX_LEN, inLen)];
-        while (processed > MAX_LEN ) {
-            src.get(in, 0, MAX_LEN);
-            update(in, 0 , MAX_LEN);
-            processed -= MAX_LEN;
+        // If ct is a direct bytebuffer, send it directly to the intrinsic
+        if (ct.isDirect()) {
+            int processed = inLen;
+            processBlocksDirect(ct, inLen);
+            return processed;
+        } else if (!ct.isReadOnly()) {
+            // If a non-read only heap bytebuffer, use the array update method
+            int processed = update(ct.array(),
+                ct.arrayOffset() + ct.position(),
+                inLen);
+            ct.position(ct.position() + processed);
+            return processed;
         }
-        src.get(in, 0, processed);
-        update(in, 0, processed);
+
+        // Read only heap bytebuffers have to be copied and operated on
+        int to_process = inLen;
+        byte[] in = new byte[Math.min(MAX_LEN, inLen)];
+        while (to_process > MAX_LEN ) {
+            ct.get(in, 0, MAX_LEN);
+            update(in, 0 , MAX_LEN);
+            to_process -= MAX_LEN;
+        }
+        ct.get(in, 0, to_process);
+        update(in, 0, to_process);
         return inLen;
     }
 
-    void doLastBlock(ByteBuffer src, int inLen) {
-        int processed = update(src, inLen);
+    int doFinal(ByteBuffer src, int inLen) {
+        int processed = 0;
+
+        if (inLen >= AES_BLOCK_SIZE) {
+            processed = update(src, inLen);
+        }
+
         if (inLen == processed) {
-            return;
+            return processed;
         }
         byte[] block = new byte[AES_BLOCK_SIZE];
         src.get(block, 0, inLen - processed);
         update(block, 0, AES_BLOCK_SIZE);
+        return inLen;
     }
 
-    private static void ghashRangeCheck(byte[] in, int inOfs, int inLen, long[] st, long[] subH) {
+    int doFinal(byte[] in, int inOfs, int inLen) {
+        int remainder = inLen % AES_BLOCK_SIZE;
+        inOfs += update(in, inOfs, inLen - remainder);
+        if (remainder > 0) {
+            byte[] block = new byte[AES_BLOCK_SIZE];
+            System.arraycopy(in, inOfs, block, 0,
+                remainder);
+            update(block, 0, AES_BLOCK_SIZE);
+        }
+        return inLen;
+    }
+
+    private static void ghashRangeCheck(byte[] in, int inOfs, int inLen,
+        long[] st, long[] subH) {
         if (inLen < 0) {
             throw new RuntimeException("invalid input length: " + inLen);
         }
@@ -263,7 +275,8 @@ final class GHASH {
      * throw exceptions or allocate arrays as it will breaking intrinsics
      */
     @IntrinsicCandidate
-    private static void processBlocks(byte[] data, int inOfs, int blocks, long[] st, long[] subH) {
+    private static void processBlocks(byte[] data, int inOfs, int blocks,
+        long[] st, long[] subH) {
         int offset = inOfs;
         while (blocks > 0) {
             processBlock(data, offset, st, subH);
@@ -272,11 +285,61 @@ final class GHASH {
         }
     }
 
+    // ProcessBlock for Direct ByteBuffers
+    private void processBlocksDirect(ByteBuffer ct, int inLen) {
+        byte[] data = new byte[Math.min(MAX_LEN, inLen)];
+        while (inLen > MAX_LEN) {
+            ct.get(data, 0, MAX_LEN);
+            processBlocks(data, 0, MAX_LEN / AES_BLOCK_SIZE, state,
+                subkeyHtbl);
+            inLen -= MAX_LEN;
+        }
+        if (inLen >= AES_BLOCK_SIZE) {
+            int len = inLen - (inLen % AES_BLOCK_SIZE);
+            ct.get(data, 0, len);
+            processBlocks(data, 0, len / AES_BLOCK_SIZE, state,
+                subkeyHtbl);
+        }
+    }
+
     byte[] digest() {
         byte[] result = new byte[AES_BLOCK_SIZE];
-        putLong(result, 0, state[0]);
-        putLong(result, 8, state[1]);
-        reset();
+        asLongView.set(result, 0, state[0]);
+        asLongView.set(result, 8, state[1]);
+        // Reset state
+        state[0] = 0;
+        state[1] = 0;
         return result;
     }
+
+
+    /**
+     * None of the out or dst values are necessary, they are to satisfy the
+     * GCM interface requirement
+     */
+    @Override
+    public int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
+        return update(in, inOfs, inLen);
+    }
+
+    @Override
+    public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
+        return update(in, inOfs, inLen);
+    }
+
+    @Override
+    public int update(ByteBuffer src, ByteBuffer dst) {
+        return update(src, src.remaining());
+    }
+
+    @Override
+    public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+        int outOfs) {
+        return doFinal(in, inOfs, inLen);
+    }
+
+    @Override
+    public int doFinal(ByteBuffer src, ByteBuffer dst) {
+        return doFinal(src, src.remaining());
+    }
 }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
index ebfd0e531c0..f43160eb9be 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
@@ -26,20 +26,34 @@
 package com.sun.crypto.provider;
 
 import sun.nio.ch.DirectBuffer;
+import sun.security.jca.JCAUtil;
 import sun.security.util.ArrayUtil;
 
 import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherSpi;
 import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
 import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.AlgorithmParameters;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.ProviderException;
-
-import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
-
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
 
 /**
  * This class represents ciphers in GaloisCounter (GCM) mode.
@@ -54,11 +68,9 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
  *
  * @since 1.8
  */
-final class GaloisCounterMode extends FeedbackCipher {
-
-    static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE;
+abstract class GaloisCounterMode extends CipherSpi {
     static int DEFAULT_IV_LEN = 12; // in bytes
-
+    static int DEFAULT_TAG_LEN = 16; // in bytes
     // In NIST SP 800-38D, GCM input size is limited to be no longer
     // than (2^36 - 32) bytes. Otherwise, the counter will wrap
     // around and lead to a leak of plaintext.
@@ -68,46 +80,424 @@ final class GaloisCounterMode extends FeedbackCipher {
     // java byte array, e.g. Integer.MAX_VALUE, since all data
     // can only be returned by the doFinal(...) call.
     private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
-
     // data size when buffer is divided up to aid in intrinsics
     private static final int TRIGGERLEN = 65536;  // 64k
 
-    // buffer for AAD data; if null, meaning update has been called
-    private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
-    private int sizeOfAAD = 0;
+    static final byte[] EMPTY_BUF = new byte[0];
 
-    // buffer data for crypto operation
-    private ByteArrayOutputStream ibuffer = null;
+    private boolean initialized = false;
 
-    // Original dst buffer if there was an overlap situation
-    private ByteBuffer originalDst = null;
+    SymmetricCipher blockCipher;
+    // Engine instance for encryption or decryption
+    private GCMEngine engine;
+    private boolean encryption = true;
 
-    // in bytes; need to convert to bits (default value 128) when needed
-    private int tagLenBytes = DEFAULT_TAG_LEN;
+    // Default value is 128bits, this is in bytes.
+    int tagLenBytes = DEFAULT_TAG_LEN;
+    // Key size if the value is passed, in bytes.
+    int keySize;
+    // Prevent reuse of iv or key
+    boolean reInit = false;
+    byte[] lastKey = EMPTY_BUF;
+    byte[] lastIv = EMPTY_BUF;
+    byte[] iv = null;
+    SecureRandom random = null;
 
-    // these following 2 fields can only be initialized after init() is
-    // called, e.g. after cipher key k is set, and STAY UNCHANGED
-    private byte[] subkeyH = null;
-    private byte[] preCounterBlock = null;
+    /**
+     *
+     * @param keySize length of key.
+     * @param embeddedCipher Cipher object, such as AESCrypt.
+     */
+    GaloisCounterMode(int keySize, SymmetricCipher embeddedCipher) {
+        blockCipher = embeddedCipher;
+        this.keySize = keySize;
+    }
 
-    private GCTR gctrPAndC = null;
-    private GHASH ghashAllToS = null;
+    /**
+     * Initializes the cipher in the specified mode with the given key
+     * and iv.
+     */
+    void init(int opmode, Key key, GCMParameterSpec spec)
+        throws InvalidKeyException, InvalidAlgorithmParameterException {
+        encryption = (opmode == Cipher.ENCRYPT_MODE) ||
+            (opmode == Cipher.WRAP_MODE);
 
-    // length of total data, i.e. len(C)
-    private int processed = 0;
+        int tagLen = spec.getTLen();
+        if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) {
+            throw new InvalidAlgorithmParameterException
+                ("Unsupported TLen value.  Must be one of " +
+                    "{128, 120, 112, 104, 96}");
+        }
+        tagLenBytes = tagLen >> 3;
 
-    // additional variables for save/restore calls
-    private byte[] aadBufferSave = null;
-    private int sizeOfAADSave = 0;
-    private byte[] ibufferSave = null;
-    private int processedSave = 0;
+        // Check the Key object is valid and the right size
+        if (key == null) {
+            throw new InvalidKeyException("The key must not be null");
+        }
+        byte[] keyValue = key.getEncoded();
+        if (keyValue == null) {
+            throw new InvalidKeyException("Key encoding must not be null");
+        } else if (keySize != -1 && keyValue.length != keySize) {
+            Arrays.fill(keyValue, (byte) 0);
+            throw new InvalidKeyException("The key must be " +
+                keySize + " bytes");
+        }
+
+        // Check for reuse
+        if (encryption) {
+            if (MessageDigest.isEqual(keyValue, lastKey) &&
+                MessageDigest.isEqual(iv, lastIv)) {
+                Arrays.fill(keyValue, (byte) 0);
+                throw new InvalidAlgorithmParameterException(
+                    "Cannot reuse iv for GCM encryption");
+            }
+
+            // Both values are already clones
+            if (lastKey != null) {
+                Arrays.fill(lastKey, (byte) 0);
+            }
+            lastKey = keyValue;
+            lastIv = iv;
+        }
+
+        reInit = false;
+
+        // always encrypt mode for embedded cipher
+        blockCipher.init(false, key.getAlgorithm(), keyValue);
+    }
+
+    @Override
+    protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
+        if (!mode.equalsIgnoreCase("GCM")) {
+            throw new NoSuchAlgorithmException("Mode must be GCM");
+        }
+    }
+
+    @Override
+    protected void engineSetPadding(String padding)
+        throws NoSuchPaddingException {
+        if (!padding.equalsIgnoreCase("NoPadding")) {
+            throw new NoSuchPaddingException("Padding must be NoPadding");
+        }
+    }
+
+    @Override
+    protected int engineGetBlockSize() {
+        return blockCipher.getBlockSize();
+    }
+
+    @Override
+    protected int engineGetOutputSize(int inputLen) {
+        checkInit();
+        return engine.getOutputSize(inputLen, true);
+    }
+
+    @Override
+    protected int engineGetKeySize(Key key) throws InvalidKeyException {
+        byte[] encoded = key.getEncoded();
+        Arrays.fill(encoded, (byte)0);
+        if (!AESCrypt.isKeySizeValid(encoded.length)) {
+            throw new InvalidKeyException("Invalid key length: " +
+                                          encoded.length + " bytes");
+        }
+        return Math.multiplyExact(encoded.length, 8);
+    }
+
+    @Override
+    protected byte[] engineGetIV() {
+        if (iv == null) {
+            return null;
+        }
+        return iv.clone();
+    }
+
+    /**
+     * Create a random 16-byte iv.
+     *
+     * @param rand a {@code SecureRandom} object.  If {@code null} is
+     * provided a new {@code SecureRandom} object will be instantiated.
+     *
+     * @return a 16-byte array containing the random nonce.
+     */
+    private static byte[] createIv(SecureRandom rand) {
+        byte[] iv = new byte[DEFAULT_IV_LEN];
+        if (rand == null) {
+            rand = JCAUtil.getDefSecureRandom();
+        }
+        rand.nextBytes(iv);
+        return iv;
+    }
+
+    @Override
+    protected AlgorithmParameters engineGetParameters() {
+        GCMParameterSpec spec;
+        spec = new GCMParameterSpec(tagLenBytes * 8,
+            iv == null ? createIv(random) : iv.clone());
+        try {
+            AlgorithmParameters params =
+                AlgorithmParameters.getInstance("GCM",
+                    SunJCE.getInstance());
+            params.init(spec);
+            return params;
+        } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key, SecureRandom random)
+        throws InvalidKeyException {
+
+        engine = null;
+        if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) {
+            throw new InvalidKeyException("No GCMParameterSpec specified");
+        }
+        try {
+            engineInit(opmode, key, (AlgorithmParameterSpec) null, random);
+        } catch (InvalidAlgorithmParameterException e) {
+            // never happen
+        }
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key,
+        AlgorithmParameterSpec params, SecureRandom random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException {
+
+        GCMParameterSpec spec;
+        this.random = random;
+        engine = null;
+        if (params == null) {
+            iv = createIv(random);
+            spec = new GCMParameterSpec(DEFAULT_TAG_LEN * 8, iv);
+        } else {
+            if (!(params instanceof GCMParameterSpec)) {
+                throw new InvalidAlgorithmParameterException(
+                    "AlgorithmParameterSpec not of GCMParameterSpec");
+            }
+            spec = (GCMParameterSpec)params;
+            iv = spec.getIV();
+            if (iv == null) {
+                throw new InvalidAlgorithmParameterException("IV is null");
+            }
+            if (iv.length == 0) {
+                throw new InvalidAlgorithmParameterException("IV is empty");
+            }
+        }
+        init(opmode, key, spec);
+        initialized = true;
+    }
+
+    @Override
+    protected void engineInit(int opmode, Key key, AlgorithmParameters params,
+        SecureRandom random) throws InvalidKeyException,
+        InvalidAlgorithmParameterException {
+        GCMParameterSpec spec = null;
+        engine = null;
+        if (params != null) {
+            try {
+                spec = params.getParameterSpec(GCMParameterSpec.class);
+            } catch (InvalidParameterSpecException e) {
+                throw new InvalidAlgorithmParameterException(e);
+            }
+        }
+        engineInit(opmode, key, spec, random);
+    }
+
+    void checkInit() {
+        if (!initialized) {
+            throw new IllegalStateException("Operation not initialized.");
+        }
+
+        if (engine == null) {
+            if (encryption) {
+                engine = new GCMEncrypt(blockCipher);
+            } else {
+                engine = new GCMDecrypt(blockCipher);
+            }
+        }
+    }
+
+    void checkReInit() {
+        if (reInit) {
+            throw new IllegalStateException(
+                "Must use either different key or " + " iv for GCM encryption");
+        }
+    }
+
+    @Override
+    protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+        checkInit();
+        ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen);
+        return engine.doUpdate(input, inputOffset, inputLen);
+    }
+
+    @Override
+    protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
+        byte[] output, int outputOffset) throws ShortBufferException {
+        checkInit();
+        ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen);
+        ArrayUtil.nullAndBoundsCheck(output, outputOffset,
+                output.length - outputOffset);
+        int len = engine.getOutputSize(inputLen, false);
+        if (len > output.length - outputOffset) {
+            throw new ShortBufferException("Output buffer too small, must be " +
+                "at least " + len + " bytes long");
+        }
+        return engine.doUpdate(input, inputOffset, inputLen, output,
+            outputOffset);
+    }
+
+    @Override
+    protected int engineUpdate(ByteBuffer src, ByteBuffer dst)
+        throws ShortBufferException {
+        checkInit();
+        int len = engine.getOutputSize(src.remaining(), false);
+        if (len > dst.remaining()) {
+            throw new ShortBufferException(
+                "Output buffer must be at least " + len + " bytes long");
+        }
+        return engine.doUpdate(src, dst);
+    }
+
+    @Override
+    protected void engineUpdateAAD(byte[] src, int offset, int len) {
+        checkInit();
+        engine.updateAAD(src, offset, len);
+    }
+
+    @Override
+    protected void engineUpdateAAD(ByteBuffer src) {
+        checkInit();
+        if (src.hasArray()) {
+            int pos = src.position();
+            int len = src.remaining();
+            engine.updateAAD(src.array(), src.arrayOffset() + pos, len);
+            src.position(pos + len);
+        } else {
+            byte[] aad = new byte[src.remaining()];
+            src.get(aad);
+            engine.updateAAD(aad, 0, aad.length);
+        }
+    }
+
+    @Override
+    protected byte[] engineDoFinal(byte[] input, int inputOffset,
+        int inputLen) throws IllegalBlockSizeException, BadPaddingException {
+        if (input == null) {
+            input = EMPTY_BUF;
+        }
+        try {
+            ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalBlockSizeException("input array invalid");
+        }
+
+        checkInit();
+        byte[] output = new byte[engine.getOutputSize(inputLen, true)];
+
+        try {
+            engine.doFinal(input, inputOffset, inputLen, output, 0);
+        } catch (ShortBufferException e) {
+            throw new ProviderException(e);
+        } finally {
+            // Release crypto engine
+            engine = null;
+        }
+        return output;
+    }
+
+    @Override
+    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
+        byte[] output, int outputOffset) throws ShortBufferException,
+        IllegalBlockSizeException, BadPaddingException {
+
+        if (input == null) {
+            input = EMPTY_BUF;
+        }
+        try {
+            ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Release crypto engine
+            engine = null;
+            throw new IllegalBlockSizeException("input array invalid");
+        }
+        checkInit();
+        int len = engine.doFinal(input, inputOffset, inputLen, output,
+            outputOffset);
+
+        // Release crypto engine
+        engine = null;
+
+        return len;
+    }
+
+    @Override
+    protected int engineDoFinal(ByteBuffer src, ByteBuffer dst)
+        throws ShortBufferException, IllegalBlockSizeException,
+        BadPaddingException {
+        checkInit();
+
+        int len = engine.doFinal(src, dst);
+
+        // Release crypto engine
+        engine = null;
+
+        return len;
+    }
+
+    @Override
+    protected byte[] engineWrap(Key key) throws IllegalBlockSizeException,
+        InvalidKeyException {
+        byte[] encodedKey = null;
+
+        checkInit();
+        try {
+            encodedKey = key.getEncoded();
+            if ((encodedKey == null) || (encodedKey.length == 0)) {
+                throw new InvalidKeyException(
+                    "Cannot get an encoding of the key to be wrapped");
+            }
+            return engineDoFinal(encodedKey, 0, encodedKey.length);
+        } catch (BadPaddingException e) {
+            // should never happen
+        } finally {
+            // Release crypto engine
+            engine = null;
+            if (encodedKey != null) {
+                Arrays.fill(encodedKey, (byte) 0);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
+        int wrappedKeyType) throws InvalidKeyException,
+        NoSuchAlgorithmException {
+        checkInit();
+
+        byte[] encodedKey;
+        try {
+            encodedKey = engineDoFinal(wrappedKey, 0,
+                wrappedKey.length);
+        } catch (BadPaddingException ePadding) {
+            throw new InvalidKeyException(
+                "The wrapped key is not padded correctly");
+        } catch (IllegalBlockSizeException eBlockSize) {
+            throw new InvalidKeyException(
+                "The wrapped key does not have the correct length");
+        }
+        try {
+            return ConstructKeys.constructKey(encodedKey, wrappedKeyAlgorithm,
+                wrappedKeyType);
+        } finally {
+            Arrays.fill(encodedKey, (byte)0);
+        }
+    }
 
     // value must be 16-byte long; used by GCTR and GHASH as well
     static void increment32(byte[] value) {
-        if (value.length != AES_BLOCK_SIZE) {
-            // should never happen
-            throw new ProviderException("Illegal counter block length");
-        }
         // start from last byte and only go over 4 bytes, i.e. total 32 bits
         int n = value.length - 1;
         while ((n >= value.length - 4) && (++value[n] == 0)) {
@@ -115,74 +505,55 @@ final class GaloisCounterMode extends FeedbackCipher {
         }
     }
 
+    private static final VarHandle wrapToByteArray =
+        MethodHandles.byteArrayViewVarHandle(long[].class,
+            ByteOrder.BIG_ENDIAN);
+
     private static byte[] getLengthBlock(int ivLenInBytes) {
-        long ivLen = ((long)ivLenInBytes) << 3;
-        byte[] out = new byte[AES_BLOCK_SIZE];
-        out[8] = (byte)(ivLen >>> 56);
-        out[9] = (byte)(ivLen >>> 48);
-        out[10] = (byte)(ivLen >>> 40);
-        out[11] = (byte)(ivLen >>> 32);
-        out[12] = (byte)(ivLen >>> 24);
-        out[13] = (byte)(ivLen >>> 16);
-        out[14] = (byte)(ivLen >>> 8);
-        out[15] = (byte)ivLen;
+        byte[] out = new byte[16];
+        wrapToByteArray.set(out, 8, ((long)ivLenInBytes  & 0xFFFFFFFFL) << 3);
         return out;
     }
 
     private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) {
-        long aLen = ((long)aLenInBytes) << 3;
-        long cLen = ((long)cLenInBytes) << 3;
-        byte[] out = new byte[AES_BLOCK_SIZE];
-        out[0] = (byte)(aLen >>> 56);
-        out[1] = (byte)(aLen >>> 48);
-        out[2] = (byte)(aLen >>> 40);
-        out[3] = (byte)(aLen >>> 32);
-        out[4] = (byte)(aLen >>> 24);
-        out[5] = (byte)(aLen >>> 16);
-        out[6] = (byte)(aLen >>> 8);
-        out[7] = (byte)aLen;
-        out[8] = (byte)(cLen >>> 56);
-        out[9] = (byte)(cLen >>> 48);
-        out[10] = (byte)(cLen >>> 40);
-        out[11] = (byte)(cLen >>> 32);
-        out[12] = (byte)(cLen >>> 24);
-        out[13] = (byte)(cLen >>> 16);
-        out[14] = (byte)(cLen >>> 8);
-        out[15] = (byte)cLen;
+        byte[] out = new byte[16];
+        wrapToByteArray.set(out, 0, ((long)aLenInBytes & 0xFFFFFFFFL) << 3);
+        wrapToByteArray.set(out, 8, ((long)cLenInBytes & 0xFFFFFFFFL) << 3);
         return out;
     }
 
-    private static byte[] expandToOneBlock(byte[] in, int inOfs, int len) {
-        if (len > AES_BLOCK_SIZE) {
+    private static byte[] expandToOneBlock(byte[] in, int inOfs, int len,
+        int blockSize) {
+        if (len > blockSize) {
             throw new ProviderException("input " + len + " too long");
         }
-        if (len == AES_BLOCK_SIZE && inOfs == 0) {
+        if (len == blockSize && inOfs == 0) {
             return in;
         } else {
-            byte[] paddedIn = new byte[AES_BLOCK_SIZE];
+            byte[] paddedIn = new byte[blockSize];
             System.arraycopy(in, inOfs, paddedIn, 0, len);
             return paddedIn;
         }
     }
 
-    private static byte[] getJ0(byte[] iv, byte[] subkeyH) {
+    private static byte[] getJ0(byte[] iv, byte[] subkeyH, int blockSize) {
         byte[] j0;
         if (iv.length == 12) { // 96 bits
-            j0 = expandToOneBlock(iv, 0, iv.length);
-            j0[AES_BLOCK_SIZE - 1] = 1;
+            j0 = expandToOneBlock(iv, 0, iv.length, blockSize);
+            j0[blockSize - 1] = 1;
         } else {
             GHASH g = new GHASH(subkeyH);
-            int lastLen = iv.length % AES_BLOCK_SIZE;
+            int lastLen = iv.length % blockSize;
             if (lastLen != 0) {
                 g.update(iv, 0, iv.length - lastLen);
                 byte[] padded =
-                    expandToOneBlock(iv, iv.length - lastLen, lastLen);
+                    expandToOneBlock(iv, iv.length - lastLen, lastLen,
+                        blockSize);
                 g.update(padded);
             } else {
                 g.update(iv);
             }
-            byte[] lengthBlock = getLengthBlock(iv.length);
-            g.update(lengthBlock);
+            g.update(getLengthBlock(iv.length));
             j0 = g.digest();
         }
         return j0;
@@ -198,820 +569,1089 @@ final class GaloisCounterMode extends FeedbackCipher {
         for (int len : lengths) {
             max = Math.subtractExact(max, len);
         }
-        if (processed > max) {
+        if (engine.processed > max) {
             throw new ProviderException("SunJCE provider only supports " +
                 "input size up to " + MAX_BUF_SIZE + " bytes");
         }
     }
 
-    GaloisCounterMode(SymmetricCipher embeddedCipher) {
-        super(embeddedCipher);
-        aadBuffer = new ByteArrayOutputStream();
-    }
-
     /**
-     * Gets the name of the feedback mechanism
-     *
-     * @return the name of the feedback mechanism
+     * Abstract class for GCMEncrypt and GCMDecrypt internal context objects
      */
-    String getFeedback() {
-        return "GCM";
-    }
+    abstract class GCMEngine {
+        byte[] preCounterBlock;
+        GCTR gctrPAndC;
+        GHASH ghashAllToS;
 
-    /**
-     * Resets the cipher object to its original state.
-     * This is used when doFinal is called in the Cipher class, so that the
-     * cipher can be reused (with its original key and iv).
-     */
-    void reset() {
-        if (aadBuffer == null) {
-            aadBuffer = new ByteArrayOutputStream();
-        } else {
-            aadBuffer.reset();
+        // Block size of the algorithm
+        final int blockSize;
+
+        // length of total data, i.e. len(C)
+        int processed = 0;
+
+        // buffer for AAD data; if null, meaning update has been called
+        ByteArrayOutputStream aadBuffer = null;
+        int sizeOfAAD = 0;
+        boolean aadProcessed = false;
+
+        // buffer data for crypto operation
+        ByteArrayOutputStream ibuffer = null;
+
+        // Original dst buffer if there was an overlap situation
+        ByteBuffer originalDst = null;
+        byte[] originalOut = null;
+        int originalOutOfs = 0;
+
+
+        GCMEngine(SymmetricCipher blockCipher) {
+            blockSize = blockCipher.getBlockSize();
+            byte[] subkeyH = new byte[blockSize];
+            blockCipher.encryptBlock(subkeyH, 0, subkeyH,0);
+            preCounterBlock = getJ0(iv, subkeyH, blockSize);
+            byte[] j0Plus1 = preCounterBlock.clone();
+            increment32(j0Plus1);
+            gctrPAndC = new GCTR(blockCipher, j0Plus1);
+            ghashAllToS = new GHASH(subkeyH);
         }
-        if (gctrPAndC != null) gctrPAndC.reset();
-        if (ghashAllToS != null) ghashAllToS.reset();
-        processed = 0;
-        sizeOfAAD = 0;
-        if (ibuffer != null) {
-            ibuffer.reset();
-        }
-    }
 
-    /**
-     * Save the current content of this cipher.
-     */
-    void save() {
-        processedSave = processed;
-        sizeOfAADSave = sizeOfAAD;
-        aadBufferSave =
-            ((aadBuffer == null || aadBuffer.size() == 0)?
-             null : aadBuffer.toByteArray());
-        if (gctrPAndC != null) gctrPAndC.save();
-        if (ghashAllToS != null) ghashAllToS.save();
-        if (ibuffer != null) {
-            ibufferSave = ibuffer.toByteArray();
-        }
-    }
+        /**
+         * Get output buffer size
+         * @param inLen Contains the length of the input data and buffered data.
+         * @param isFinal true if this is a doFinal operation
+         * @return If it's an update operation, inLen must blockSize
+         *         divisible.  If it's a final operation, output will
+         *         include the tag.
+         */
+        abstract int getOutputSize(int inLen, boolean isFinal);
 
-    /**
-     * Restores the content of this cipher to the previous saved one.
-     */
-    void restore() {
-        processed = processedSave;
-        sizeOfAAD = sizeOfAADSave;
-        if (aadBuffer != null) {
-            aadBuffer.reset();
-            if (aadBufferSave != null) {
-                aadBuffer.write(aadBufferSave, 0, aadBufferSave.length);
+        // Update operations
+        abstract byte[] doUpdate(byte[] in, int inOff, int inLen);
+        abstract int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
+            int outOff) throws ShortBufferException;
+        abstract int doUpdate(ByteBuffer src, ByteBuffer dst)
+            throws ShortBufferException;
+
+        // Final operations
+        abstract int doFinal(byte[] in, int inOff, int inLen, byte[] out,
+            int outOff) throws IllegalBlockSizeException, AEADBadTagException,
+            ShortBufferException;
+        abstract int doFinal(ByteBuffer src, ByteBuffer dst)
+            throws IllegalBlockSizeException, AEADBadTagException,
+            ShortBufferException;
+
+        // Initialize internal data buffer, if not already.
+        void initBuffer(int len) {
+            if (ibuffer == null) {
+                ibuffer = new ByteArrayOutputStream(len);
             }
         }
-        if (gctrPAndC != null) gctrPAndC.restore();
-        if (ghashAllToS != null) ghashAllToS.restore();
-        if (ibuffer != null) {
-            ibuffer.reset();
-            ibuffer.write(ibufferSave, 0, ibufferSave.length);
-        }
-    }
 
-    /**
-     * Initializes the cipher in the specified mode with the given key
-     * and iv.
-     *
-     * @param decrypting flag indicating encryption or decryption
-     * @param algorithm the algorithm name
-     * @param key the key
-     * @param iv the iv
-     * @exception InvalidKeyException if the given key is inappropriate for
-     * initializing this cipher
-     */
-    @Override
-    void init(boolean decrypting, String algorithm, byte[] key, byte[] iv)
-            throws InvalidKeyException, InvalidAlgorithmParameterException {
-        init(decrypting, algorithm, key, iv, DEFAULT_TAG_LEN);
-    }
-
-    /**
-     * Initializes the cipher in the specified mode with the given key
-     * and iv.
-     *
-     * @param decrypting flag indicating encryption or decryption
-     * @param algorithm the algorithm name
-     * @param keyValue the key
-     * @param ivValue the iv
-     * @param tagLenBytes the length of tag in bytes
-     *
-     * @exception InvalidKeyException if the given key is inappropriate for
-     * initializing this cipher
-     */
-    void init(boolean decrypting, String algorithm, byte[] keyValue,
-              byte[] ivValue, int tagLenBytes)
-              throws InvalidKeyException, InvalidAlgorithmParameterException {
-        if (keyValue == null) {
-            throw new InvalidKeyException("Internal error");
-        }
-        if (ivValue == null) {
-            throw new InvalidAlgorithmParameterException("Internal error");
-        }
-        if (ivValue.length == 0) {
-            throw new InvalidAlgorithmParameterException("IV is empty");
+        // Helper method for getting ibuffer size
+        int getBufferedLength() {
+            return (ibuffer == null ? 0 : ibuffer.size());
         }
 
-        // always encrypt mode for embedded cipher
-        this.embeddedCipher.init(false, algorithm, keyValue);
-        this.subkeyH = new byte[AES_BLOCK_SIZE];
-        this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0,
-                this.subkeyH, 0);
-
-        this.iv = ivValue.clone();
-        preCounterBlock = getJ0(iv, subkeyH);
-        byte[] j0Plus1 = preCounterBlock.clone();
-        increment32(j0Plus1);
-        gctrPAndC = new GCTR(embeddedCipher, j0Plus1);
-        ghashAllToS = new GHASH(subkeyH);
-
-        this.tagLenBytes = tagLenBytes;
-        if (aadBuffer == null) {
-            aadBuffer = new ByteArrayOutputStream();
-        } else {
-            aadBuffer.reset();
+        /**
+         * The method takes two buffers to create one block of data.  The
+         * difference with the other mergeBlock is this will calculate
+         * the bufLen from the existing 'buffer' length & offset
+         *
+         * This is only called when buffer length is less than a blockSize
+         * @return number of bytes used from 'in'
+         */
+        int mergeBlock(byte[] buffer, int bufOfs, byte[] in, int inOfs,
+            int inLen, byte[] block) {
+            return mergeBlock(buffer, bufOfs, buffer.length - bufOfs, in,
+                inOfs, inLen, block);
         }
-        processed = 0;
-        sizeOfAAD = 0;
-        if (decrypting) {
-            ibuffer = new ByteArrayOutputStream();
+
+        /**
+         * The method takes two buffers to create one block of data
+         *
+         * This is only called when buffer length is less than a blockSize
+         * @return number of bytes used from 'in'
+         */
+        int mergeBlock(byte[] buffer, int bufOfs, int bufLen, byte[] in,
+            int inOfs, int inLen, byte[] block) {
+            if (bufLen > blockSize) {
+                throw new RuntimeException("mergeBlock called on an ibuffer " +
+                    "too big:  " + bufLen + " bytes");
+            }
+
+            System.arraycopy(buffer, bufOfs, block, 0, bufLen);
+            int inUsed = Math.min(block.length - bufLen, inLen);
+            System.arraycopy(in, inOfs, block, bufLen, inUsed);
+            return inUsed;
         }
-    }
 
-    /**
-     * Continues a multi-part update of the Additional Authentication
-     * Data (AAD), using a subset of the provided buffer. 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).
-     * <p>
-     * NOTE: Given most modes do not accept AAD, default impl for this
-     * method throws IllegalStateException.
-     *
-     * @param src the buffer containing the AAD
-     * @param offset the offset in {@code src} where the AAD input starts
-     * @param len the number of AAD bytes
-     *
-     * @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 this method
-     * has not been overridden by an implementation
-     *
-     * @since 1.8
-     */
-    void updateAAD(byte[] src, int offset, int len) {
-        if (aadBuffer != null) {
-            aadBuffer.write(src, offset, len);
-        } else {
-            // update has already been called
-            throw new IllegalStateException
-                ("Update has been called; no more AAD data");
-        }
-    }
+        /**
+         * Continues a multi-part update of the Additional Authentication
+         * Data (AAD), using a subset of the provided buffer.  All AAD must be
+         * supplied before beginning operations on the ciphertext (via the
+         * {@code update} and {@code doFinal} methods).
+         *
+         * @param src the buffer containing the AAD
+         * @param offset the offset in {@code src} where the AAD input starts
+         * @param len the number of AAD bytes
+         *
+         * @throws IllegalStateException if this cipher is in a wrong state
+         * (e.g., has not been initialized) or does not accept AAD, and one of
+         * the {@code update} methods has already been called for the active
+         * encryption/decryption operation
+         * @throws UnsupportedOperationException if this method
+         * has not been overridden by an implementation
+         */
+        void updateAAD(byte[] src, int offset, int len) {
+            if (encryption) {
+                checkReInit();
+            }
 
-    // Feed the AAD data to GHASH, pad if necessary
-    void processAAD() {
-        if (aadBuffer != null) {
-            if (aadBuffer.size() > 0) {
-                byte[] aad = aadBuffer.toByteArray();
-                sizeOfAAD = aad.length;
-
-                int lastLen = aad.length % AES_BLOCK_SIZE;
-                if (lastLen != 0) {
-                    ghashAllToS.update(aad, 0, aad.length - lastLen);
-                    byte[] padded = expandToOneBlock(aad, aad.length - lastLen,
-                                                     lastLen);
-                    ghashAllToS.update(padded);
+            if (aadBuffer == null) {
+                if (sizeOfAAD == 0 && !aadProcessed) {
+                    aadBuffer = new ByteArrayOutputStream(len);
                 } else {
-                    ghashAllToS.update(aad);
+                    // update has already been called
+                    throw new IllegalStateException
+                        ("Update has been called; no more AAD data");
                 }
             }
-            aadBuffer = null;
-        }
-    }
-
-    // Utility to process the last block; used by encryptFinal and decryptFinal
-    void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs,
-                     boolean isEncrypt) throws IllegalBlockSizeException {
-        byte[] ct;
-        int ctOfs;
-        int ilen = len;  // internal length
-
-        if (isEncrypt) {
-            ct = out;
-            ctOfs = outOfs;
-        } else {
-            ct = in;
-            ctOfs = inOfs;
+            aadBuffer.write(src, offset, len);
         }
 
-        // Divide up larger data sizes to trigger CTR & GHASH intrinsic quicker
-        if (len > TRIGGERLEN) {
-            int i = 0;
-            int tlen;  // incremental lengths
-            final int plen = AES_BLOCK_SIZE * 6;
-            // arbitrary formula to aid intrinsic without reaching buffer end
-            final int count = len / 1024;
+        // Feed the AAD data to GHASH, pad if necessary
+        void processAAD() {
+            if (aadBuffer != null) {
+                if (aadBuffer.size() > 0) {
+                    byte[] aad = aadBuffer.toByteArray();
+                    sizeOfAAD = aad.length;
 
-            while (count > i) {
-                tlen = gctrPAndC.update(in, inOfs, plen, out, outOfs);
-                ghashAllToS.update(ct, ctOfs, tlen);
-                inOfs += tlen;
-                outOfs += tlen;
-                ctOfs += tlen;
-                i++;
-            }
-            ilen -= count * plen;
-            processed += count * plen;
-        }
-
-        gctrPAndC.doFinal(in, inOfs, ilen, out, outOfs);
-        processed += ilen;
-
-        int lastLen = ilen % AES_BLOCK_SIZE;
-        if (lastLen != 0) {
-            ghashAllToS.update(ct, ctOfs, ilen - lastLen);
-            ghashAllToS.update(
-                    expandToOneBlock(ct, (ctOfs + ilen - lastLen), lastLen));
-        } else {
-            ghashAllToS.update(ct, ctOfs, ilen);
-        }
-    }
-
-    // Process en/decryption all the way to the last block.  It takes both
-    // For input it takes the ibuffer which is wrapped in 'buffer' and 'src'
-    // from doFinal.
-    void doLastBlock(ByteBuffer buffer, ByteBuffer src, ByteBuffer dst)
-        throws IllegalBlockSizeException {
-
-        if (buffer != null && buffer.remaining() > 0) {
-            // en/decrypt on how much buffer there is in AES_BLOCK_SIZE
-            processed += gctrPAndC.update(buffer, dst);
-
-            // Process the remainder in the buffer
-            if (buffer.remaining() > 0) {
-                // Copy the remainder of the buffer into the extra block
-                byte[] block = new byte[AES_BLOCK_SIZE];
-                int over = buffer.remaining();
-                int len = over;  // how much is processed by in the extra block
-                buffer.get(block, 0, over);
-
-                // if src is empty, update the final block and wait for later
-                // to finalize operation
-                if (src.remaining() > 0) {
-                    // Fill out block with what is in data
-                    if (src.remaining() > AES_BLOCK_SIZE - over) {
-                        src.get(block, over, AES_BLOCK_SIZE - over);
-                        len += AES_BLOCK_SIZE - over;
+                    int lastLen = aad.length % blockSize;
+                    if (lastLen != 0) {
+                        ghashAllToS.update(aad, 0, aad.length - lastLen);
+                        byte[] padded = expandToOneBlock(aad,
+                            aad.length - lastLen, lastLen, blockSize);
+                        ghashAllToS.update(padded);
                     } else {
-                        // If the remaining in buffer + data does not fill a
-                        // block, complete the ghash operation
-                        int l = src.remaining();
-                        src.get(block, over, l);
-                        len += l;
+                        ghashAllToS.update(aad);
                     }
                 }
-                gctrPAndC.update(block, 0, AES_BLOCK_SIZE, dst);
-                processed += len;
+                aadBuffer = null;
             }
+            aadProcessed = true;
         }
 
-        // en/decrypt whatever remains in src.
-        // If src has been consumed, this will be a no-op
-        processed += gctrPAndC.doFinal(src, dst);
-    }
+        /**
+         * Process en/decryption all the way to the last block.  It takes both
+         * For input it takes the ibuffer which is wrapped in 'buffer' and 'src'
+         * from doFinal.
+         */
+        int doLastBlock(GCM op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) {
+            int resultLen = 0;
 
-     /*
-     * This method is for CipherCore to insert the remainder of its buffer
-     * into the ibuffer before a doFinal(ByteBuffer, ByteBuffer) operation
-     */
-    int encrypt(byte[] in, int inOfs, int len) {
-        if (len > 0) {
-            // store internally until encryptFinal
-            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-            if (ibuffer == null) {
-                ibuffer = new ByteArrayOutputStream();
-            }
-            ibuffer.write(in, inOfs, len);
-        }
-        return len;
-    }
+            int bLen = (buffer != null ? buffer.remaining() : 0);
+            if (bLen > 0) {
+                // en/decrypt on how much buffer there is in AES_BLOCK_SIZE
+                if (bLen >= blockSize) {
+                    resultLen += op.update(buffer, dst);
+                }
 
-    /**
-     * Performs encryption operation.
-     *
-     * <p>The input plain text <code>in</code>, starting at <code>inOfs</code>
-     * and ending at <code>(inOfs + len - 1)</code>, is encrypted. The result
-     * is stored in <code>out</code>, starting at <code>outOfs</code>.
-     *
-     * @param in the buffer with the input data to be encrypted
-     * @param inOfs the offset in <code>in</code>
-     * @param inLen the length of the input data
-     * @param out the buffer for the result
-     * @param outOfs the offset in <code>out</code>
-     * @return the number of bytes placed into the <code>out</code> buffer
-     */
-    int encrypt(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
-        checkDataLength(inLen, getBufferedLength());
-        ArrayUtil.nullAndBoundsCheck(in, inOfs, inLen);
-        ArrayUtil.nullAndBoundsCheck(out, outOfs, inLen);
-
-        processAAD();
-        // 'inLen' stores the length to use with buffer 'in'.
-        // 'len' stores the length returned by the method.
-        int len = inLen;
-
-        // if there is enough data in the ibuffer and 'in', encrypt it.
-        if (ibuffer != null && ibuffer.size() > 0) {
-            byte[] buffer = ibuffer.toByteArray();
-            // number of bytes not filling a block
-            int remainder = ibuffer.size() % blockSize;
-            // number of bytes along block boundary
-            int blen = ibuffer.size() - remainder;
-
-            // If there is enough bytes in ibuffer for a block or more,
-            // encrypt that first.
-            if (blen > 0) {
-                encryptBlocks(buffer, 0, blen, out, outOfs);
-                outOfs += blen;
-            }
-
-            // blen is now the offset for 'buffer'
-
-            // Construct and encrypt a block if there is enough 'buffer' and
-            // 'in' to make one
-            if ((inLen + remainder) >= blockSize) {
-                byte[] block = new byte[blockSize];
-
-                System.arraycopy(buffer, blen, block, 0, remainder);
-                int inLenUsed = blockSize - remainder;
-                System.arraycopy(in, inOfs, block, remainder, inLenUsed);
-
-                encryptBlocks(block, 0, blockSize, out, outOfs);
-                inOfs += inLenUsed;
-                inLen -= inLenUsed;
-                len += (blockSize - inLenUsed);
-                outOfs += blockSize;
-                ibuffer.reset();
-                // Code below will write the remainder from 'in' to ibuffer
-            } else if (remainder > 0) {
-                // If a block or more was encrypted from 'buffer' only, but the
-                // rest of 'buffer' with 'in' could not construct a block, then
-                // put the rest of 'buffer' back into ibuffer.
-                ibuffer.reset();
-                ibuffer.write(buffer, blen, remainder);
-                // Code below will write the remainder from 'in' to ibuffer
-            }
-            // If blen == 0 and there was not enough to construct a block
-            // from 'buffer' and 'in', then let the below code append 'in' to
-            // the ibuffer.
-        }
-
-        // Write any remaining bytes outside the blockSize into ibuffer.
-        int remainder = inLen % blockSize;
-        if (remainder > 0) {
-            if (ibuffer == null) {
-                ibuffer = new ByteArrayOutputStream(inLen % blockSize);
-            }
-            len -= remainder;
-            inLen -= remainder;
-            // remainder offset is based on original buffer length
-            ibuffer.write(in, inOfs + inLen, remainder);
-        }
-
-        // Encrypt the remaining blocks inside of 'in'
-        if (inLen > 0) {
-            encryptBlocks(in, inOfs, inLen, out, outOfs);
-        }
-
-        return len;
-    }
-
-    void encryptBlocks(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
-        gctrPAndC.update(in, inOfs, len, out, outOfs);
-        processed += len;
-        ghashAllToS.update(out, outOfs, len);
-    }
-
-    /**
-     * Performs encryption operation for the last time.
-     *
-     * @param in the input buffer with the data to be encrypted
-     * @param inOfs the offset in <code>in</code>
-     * @param len the length of the input data
-     * @param out the buffer for the encryption result
-     * @param outOfs the offset in <code>out</code>
-     * @return the number of bytes placed into the <code>out</code> buffer
-     */
-    int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
-        throws IllegalBlockSizeException, ShortBufferException {
-        checkDataLength(len, getBufferedLength(), tagLenBytes);
-
-        try {
-            ArrayUtil.nullAndBoundsCheck(out, outOfs,
-                (len + tagLenBytes));
-        } catch (ArrayIndexOutOfBoundsException aiobe) {
-            throw new ShortBufferException("Output buffer too small");
-        }
-
-        processAAD();
-        if (len > 0) {
-            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-
-            doLastBlock(in, inOfs, len, out, outOfs, true);
-        }
-
-        byte[] block = getLengthBlock(sizeOfAAD, processed);
-        ghashAllToS.update(block);
-        block = ghashAllToS.digest();
-        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-        gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
-
-        System.arraycopy(block, 0, out, (outOfs + len), tagLenBytes);
-        return (len + tagLenBytes);
-    }
-
-    int encryptFinal(ByteBuffer src, ByteBuffer dst)
-        throws IllegalBlockSizeException, ShortBufferException {
-        dst = overlapDetection(src, dst);
-        int len = src.remaining();
-        len += getBufferedLength();
-
-        // 'len' includes ibuffer data
-        checkDataLength(len, tagLenBytes);
-        dst.mark();
-        if (dst.remaining() < len + tagLenBytes) {
-            throw new ShortBufferException("Output buffer too small");
-        }
-
-        processAAD();
-        if (len > 0) {
-            doLastBlock((ibuffer == null || ibuffer.size() == 0) ?
-                    null : ByteBuffer.wrap(ibuffer.toByteArray()), src, dst);
-            dst.reset();
-            ghashAllToS.doLastBlock(dst, len);
-        }
-
-        byte[] block = getLengthBlock(sizeOfAAD, processed);
-        ghashAllToS.update(block);
-        block = ghashAllToS.digest();
-        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-        gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
-        dst.put(block, 0, tagLenBytes);
-        restoreDst(dst);
-
-        return (len + tagLenBytes);
-    }
-
-    /**
-     * Performs decryption operation.
-     *
-     * <p>The input cipher text <code>in</code>, starting at
-     * <code>inOfs</code> and ending at <code>(inOfs + len - 1)</code>,
-     * is decrypted. The result is stored in <code>out</code>, starting at
-     * <code>outOfs</code>.
-     *
-     * @param in the buffer with the input data to be decrypted
-     * @param inOfs the offset in <code>in</code>
-     * @param len the length of the input data
-     * @param out the buffer for the result
-     * @param outOfs the offset in <code>out</code>
-     * @exception ProviderException if <code>len</code> is not
-     * a multiple of the block size
-     * @return the number of bytes placed into the <code>out</code> buffer
-     */
-    int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
-        processAAD();
-
-        if (len > 0) {
-            // store internally until decryptFinal is called because
-            // spec mentioned that only return recovered data after tag
-            // is successfully verified
-            ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-            ibuffer.write(in, inOfs, len);
-        }
-        return 0;
-    }
-
-    int decrypt(ByteBuffer src, ByteBuffer dst) {
-        if (src.remaining() > 0) {
-            byte[] b = new byte[src.remaining()];
-            src.get(b);
-            try {
-                ibuffer.write(b);
-            } catch (IOException e) {
-                throw new ProviderException("Unable to add remaining input to the buffer", e);
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Performs decryption operation for the last time.
-     *
-     * <p>NOTE: For cipher feedback modes which does not perform
-     * special handling for the last few blocks, this is essentially
-     * the same as <code>encrypt(...)</code>. Given most modes do
-     * not do special handling, the default impl for this method is
-     * to simply call <code>decrypt(...)</code>.
-     *
-     * @param in the input buffer with the data to be decrypted
-     * @param inOfs the offset in <code>cipher</code>
-     * @param len the length of the input data
-     * @param out the buffer for the decryption result
-     * @param outOfs the offset in <code>plain</code>
-     * @return the number of bytes placed into the <code>out</code> buffer
-     */
-    int decryptFinal(byte[] in, int inOfs, int len,
-                     byte[] out, int outOfs)
-        throws IllegalBlockSizeException, AEADBadTagException,
-        ShortBufferException {
-        if (len < tagLenBytes) {
-            throw new AEADBadTagException("Input too short - need tag");
-        }
-
-        // do this check here can also catch the potential integer overflow
-        // scenario for the subsequent output buffer capacity check.
-        checkDataLength(getBufferedLength(), (len - tagLenBytes));
-
-        try {
-            ArrayUtil.nullAndBoundsCheck(out, outOfs,
-                (getBufferedLength() + len) - tagLenBytes);
-        } catch (ArrayIndexOutOfBoundsException aiobe) {
-            throw new ShortBufferException("Output buffer too small");
-        }
-
-        processAAD();
-
-        ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-
-        // get the trailing tag bytes from 'in'
-        byte[] tag = new byte[tagLenBytes];
-        System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
-        len -= tagLenBytes;
-
-        // If decryption is in-place or there is buffered "ibuffer" data, copy
-        // the "in" byte array into the ibuffer before proceeding.
-        if (in == out || getBufferedLength() > 0) {
-            if (len > 0) {
-                ibuffer.write(in, inOfs, len);
-            }
-
-            // refresh 'in' to all buffered-up bytes
-            in = ibuffer.toByteArray();
-            inOfs = 0;
-            len = in.length;
-            ibuffer.reset();
-        }
-
-        if (len > 0) {
-            doLastBlock(in, inOfs, len, out, outOfs, false);
-        }
-
-        byte[] block = getLengthBlock(sizeOfAAD, processed);
-        ghashAllToS.update(block);
-        block = ghashAllToS.digest();
-        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-        gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
-
-        // check entire authentication tag for time-consistency
-        int mismatch = 0;
-        for (int i = 0; i < tagLenBytes; i++) {
-            mismatch |= tag[i] ^ block[i];
-        }
-
-        if (mismatch != 0) {
-            throw new AEADBadTagException("Tag mismatch!");
-        }
-
-        return len;
-    }
-
-    // Note: In-place operations do not need an intermediary copy because
-    // the GHASH check was performed before the decryption.
-    int decryptFinal(ByteBuffer src, ByteBuffer dst)
-        throws IllegalBlockSizeException, AEADBadTagException,
-        ShortBufferException {
-
-        dst = overlapDetection(src, dst);
-        // Length of the input
-        ByteBuffer tag;
-        ByteBuffer ct = src.duplicate();
-
-        ByteBuffer buffer = ((ibuffer == null || ibuffer.size() == 0) ? null :
-            ByteBuffer.wrap(ibuffer.toByteArray()));
-        int len;
-
-        if (ct.remaining() >= tagLenBytes) {
-            tag = src.duplicate();
-            tag.position(ct.limit() - tagLenBytes);
-            ct.limit(ct.limit() - tagLenBytes);
-            len = ct.remaining();
-            if (buffer != null) {
-                len += buffer.remaining();
-            }
-        } else if (buffer != null && ct.remaining() < tagLenBytes) {
-            // It's unlikely the tag will be between the buffer and data
-            tag = ByteBuffer.allocate(tagLenBytes);
-            int limit = buffer.remaining() - (tagLenBytes - ct.remaining());
-            buffer.mark();
-            buffer.position(limit);
-            // Read from "new" limit to buffer's end
-            tag.put(buffer);
-            // reset buffer to data only
-            buffer.reset();
-            buffer.limit(limit);
-            tag.put(ct);
-            tag.flip();
-            // Limit is how much of the ibuffer has been chopped off.
-            len = buffer.remaining();
-        } else {
-            throw new AEADBadTagException("Input too short - need tag");
-        }
-
-        // 'len' contains the length in ibuffer and src
-        checkDataLength(len);
-
-        if (len > dst.remaining()) {
-            throw new ShortBufferException("Output buffer too small");
-        }
-
-        processAAD();
-        // Set the mark for a later reset. Either it will be zero, or the tag
-        // buffer creation above will have consume some or all of it.
-        ct.mark();
-
-        // If there is data stored in the buffer
-        if (buffer != null && buffer.remaining() > 0) {
-            ghashAllToS.update(buffer, buffer.remaining());
-            // Process the overage
-            if (buffer.remaining() > 0) {
-                // Fill out block between two buffers
-                if (ct.remaining() > 0) {
+                // Process the remainder in the buffer
+                if (bLen - resultLen > 0) {
+                    // Copy the buffer remainder into an extra block
+                    byte[] block = new byte[blockSize];
                     int over = buffer.remaining();
-                    byte[] block = new byte[AES_BLOCK_SIZE];
-                    // Copy the remainder of the buffer into the extra block
                     buffer.get(block, 0, over);
 
-                    // Fill out block with what is in data
-                    if (ct.remaining() > AES_BLOCK_SIZE - over) {
-                        ct.get(block, over, AES_BLOCK_SIZE - over);
-                        ghashAllToS.update(block, 0, AES_BLOCK_SIZE);
-                    } else {
-                        // If the remaining in buffer + data does not fill a
-                        // block, complete the ghash operation
-                        int l = ct.remaining();
-                        ct.get(block, over, l);
-                        ghashAllToS.doLastBlock(ByteBuffer.wrap(block), over + l);
+                    // If src has data, complete the block;
+                    int slen = Math.min(src.remaining(), blockSize - over);
+                    if (slen > 0) {
+                        src.get(block, over, slen);
                     }
+                    int len = slen + over;
+                    if (len == blockSize) {
+                        resultLen += op.update(block, 0, blockSize, dst);
+                    } else {
+                        resultLen += op.doFinal(block, 0, len, block,
+                                0);
+                        if (dst != null) {
+                            dst.put(block, 0, len);
+                        }
+                        processed += resultLen;
+                        return resultLen;
+                    }
+                }
+            }
+
+            // en/decrypt whatever remains in src.
+            // If src has been consumed, this will be a no-op
+            if (src.remaining() > TRIGGERLEN) {
+                resultLen += throttleData(op, src, dst);
+            }
+
+            resultLen += op.doFinal(src, dst);
+            processed += resultLen;
+            return resultLen;
+        }
+
+
+        /**
+         * This segments large data into smaller chunks so hotspot will start
+         * using GCTR and GHASH intrinsics sooner.  This is a problem for app
+         * and perf tests that only use large input sizes.
+         */
+        int throttleData(GCM op, byte[] in, int inOfs, int inLen,
+            byte[] out, int outOfs) {
+
+            int segments = (inLen / 6);
+            segments -= segments % blockSize;
+            int len = 0;
+            int i = 0;
+            do {
+                len += op.update(in, inOfs + len, segments, out,outOfs + len);
+            } while (++i < 5);
+
+            len += op.update(in, inOfs + len, inLen - len, out, outOfs + len);
+            return len;
+        }
+
+
+        /**
+         * This segments large data into smaller chunks so hotspot will start
+         * using GCTR and GHASH intrinsics sooner.  This is a problem for app
+         * and perf tests that only use large input sizes.
+         */
+        int throttleData(GCM op, ByteBuffer src, ByteBuffer dst) {
+            int inLen = src.limit();
+            int segments = (src.remaining() / 6);
+            segments -= segments % blockSize;
+            int i = 0, resultLen = 0;
+            do {
+                src.limit(src.position() + segments);
+                resultLen += op.update(src, dst);
+            } while (++i < 5);
+
+            src.limit(inLen);
+            // If there is still at least a blockSize left
+            if (src.remaining() > blockSize) {
+                resultLen += op.update(src, dst);
+            }
+
+            return resultLen;
+        }
+
+        /**
+         * Check for overlap. If the src and dst buffers are using shared data
+         * and if dst will overwrite src data before src can be processed.
+         * If so, make a copy to put the dst data in.
+         */
+        ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) {
+            if (src.isDirect() && dst.isDirect()) {
+                DirectBuffer dsrc = (DirectBuffer) src;
+                DirectBuffer ddst = (DirectBuffer) dst;
+
+                // Get the current memory address for the given ByteBuffers
+                long srcaddr = dsrc.address();
+                long dstaddr = ddst.address();
+
+                // Find the lowest attachment that is the base memory address
+                // of the shared memory for the src object
+                while (dsrc.attachment() != null) {
+                    srcaddr = ((DirectBuffer) dsrc.attachment()).address();
+                    dsrc = (DirectBuffer) dsrc.attachment();
+                }
+
+                // Find the lowest attachment that is the base memory address
+                // of the shared memory for the dst object
+                while (ddst.attachment() != null) {
+                    dstaddr = ((DirectBuffer) ddst.attachment()).address();
+                    ddst = (DirectBuffer) ddst.attachment();
+                }
+
+                // If the base addresses are not the same, there is no overlap
+                if (srcaddr != dstaddr) {
+                    return dst;
+                }
+                // At this point we know these objects share the same memory.
+                // This checks the starting position of the src and dst address
+                // for overlap.
+                // It uses the base address minus the passed object's address to
+                // get the offset from the base address, then add the position()
+                // from the passed object.  That gives up the true offset from
+                // the base address.  As long as the src side is >= the dst
+                // side, we are not in overlap.
+                if (((DirectBuffer) src).address() - srcaddr + src.position() >=
+                    ((DirectBuffer) dst).address() - dstaddr + dst.position()) {
+                    return dst;
+                }
+
+            } else if (!src.isDirect() && !dst.isDirect()) {
+                // if src is read only, then we need a copy
+                if (!src.isReadOnly()) {
+                    // If using the heap, check underlying byte[] address.
+                    if (!src.array().equals(dst.array()) ) {
+                        return dst;
+                    }
+
+                    // Position plus arrayOffset() will give us the true offset
+                    // from the underlying byte[] address.
+                    if (src.position() + src.arrayOffset() >=
+                        dst.position() + dst.arrayOffset()) {
+                        return dst;
+                    }
+                }
+            } else {
+                // buffer types are not the same and can be used as-is
+                return dst;
+            }
+
+            // Create a copy
+            ByteBuffer tmp = dst.duplicate();
+            // We can use a heap buffer for internal use, save on alloc cost
+            ByteBuffer bb = ByteBuffer.allocate(dst.remaining());
+            tmp.limit(dst.limit());
+            tmp.position(dst.position());
+            bb.put(tmp);
+            bb.flip();
+            originalDst = dst;
+            return bb;
+        }
+
+        /**
+         * Overlap detection for data using byte array.
+         * If an intermediate array is needed, the original out array length is
+         * allocated because for code simplicity.
+         */
+        byte[] overlapDetection(byte[] in, int inOfs, byte[] out, int outOfs) {
+            if (in == out && inOfs < outOfs) {
+                originalOut = out;
+                originalOutOfs = outOfs;
+                return new byte[out.length];
+            }
+            return out;
+        }
+
+        /**
+         * If originalDst is not null, 'dst' is an internal buffer and it's
+         * data will be copied to the original dst buffer
+         */
+        void restoreDst(ByteBuffer dst) {
+            if (originalDst == null) {
+                return;
+            }
+
+            dst.flip();
+            originalDst.put(dst);
+            originalDst = null;
+        }
+
+        /**
+         * If originalOut is not null, the 'out' is an internal buffer and it's
+         * data will be copied into original out byte[];
+         */
+        void restoreOut(byte[] out, int len) {
+            if (originalOut == null) {
+                return;
+            }
+
+            System.arraycopy(out, originalOutOfs, originalOut, originalOutOfs,
+                len);
+            originalOut = null;
+        }
+    }
+
+    /**
+     * Encryption Engine object
+     */
+    class GCMEncrypt extends GCMEngine {
+        GCTRGHASH gctrghash;
+
+        GCMEncrypt(SymmetricCipher blockCipher) {
+            super(blockCipher);
+            gctrghash = new GCTRGHASH(gctrPAndC, ghashAllToS);
+        }
+
+        @Override
+        public int getOutputSize(int inLen, boolean isFinal) {
+            int len = getBufferedLength();
+            if (isFinal) {
+                return len + inLen + tagLenBytes;
+            } else {
+                len += inLen;
+                return len - (len % blockCipher.getBlockSize());
+            }
+        }
+
+        @Override
+        byte[] doUpdate(byte[] in, int inOff, int inLen) {
+            checkReInit();
+            byte[] output = new byte[getOutputSize(inLen, false)];
+            try {
+                doUpdate(in, inOff, inLen, output, 0);
+            } catch (ShortBufferException e) {
+                // This should never happen because we just allocated output
+                throw new ProviderException("output buffer creation failed", e);
+            }
+            return output;
+        }
+
+        /**
+         * Encrypt update operation.  This uses both the ibuffer and 'in' to
+         * encrypt as many blocksize data as possible.  Any remaining data is
+         * put into the ibuffer.
+         */
+        @Override
+        public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out,
+            int outOfs) throws ShortBufferException {
+
+            checkReInit();
+
+            // 'inLen' stores the length to use with buffer 'in'.
+            // 'len' stores the length returned by the method.
+            int len = 0;
+            int bLen = getBufferedLength();
+            checkDataLength(inLen, bLen);
+
+            processAAD();
+            out = overlapDetection(in, inOfs, out, outOfs);
+
+            // if there is enough data in the ibuffer and 'in', encrypt it.
+            if (bLen > 0) {
+                byte[] buffer = ibuffer.toByteArray();
+                // number of bytes not filling a block
+                int remainder = blockSize - bLen;
+
+                // Construct and encrypt a block if there is enough 'buffer' and
+                // 'in' to make one
+                if ((inLen + bLen) >= blockSize) {
+                    byte[] block = new byte[blockSize];
+
+                    System.arraycopy(buffer, 0, block, 0, bLen);
+                    System.arraycopy(in, inOfs, block, bLen, remainder);
+
+                    len = gctrghash.update(block, 0, blockSize, out, outOfs);
+                    inOfs += remainder;
+                    inLen -= remainder;
+                    outOfs += blockSize;
+                    ibuffer.reset();
+                }
+            }
+
+            // Encrypt the remaining blocks inside of 'in'
+            if (inLen >= blockSize) {
+                len += gctrghash.update(in, inOfs, inLen, out, outOfs);
+            }
+
+            // Write any remaining bytes less than a blockSize into ibuffer.
+            int remainder = inLen % blockSize;
+            if (remainder > 0) {
+                initBuffer(remainder);
+                inLen -= remainder;
+                // remainder offset is based on original buffer length
+                ibuffer.write(in, inOfs + inLen, remainder);
+            }
+
+            restoreOut(out, len);
+            processed += len;
+            return len;
+        }
+
+        /**
+         * Encrypt update operation.  This uses both the ibuffer and 'src' to
+         * encrypt as many blocksize data as possible.  Any remaining data is
+         * put into the ibuffer.
+         */
+        @Override
+        public int doUpdate(ByteBuffer src, ByteBuffer dst)
+            throws ShortBufferException {
+            checkReInit();
+            int bLen = getBufferedLength();
+            checkDataLength(src.remaining(), bLen);
+
+            // 'len' stores the length returned by the method.
+            int len = 0;
+
+            processAAD();
+
+            dst = overlapDetection(src, dst);
+            // if there is enough data in the ibuffer and 'in', encrypt it.
+            if (bLen > 0) {
+                // number of bytes not filling a block
+                int remainder = blockSize - bLen;
+                // Check if there is enough 'src' and 'buffer' to fill a block
+                if (src.remaining() >= remainder) {
+                    byte[] block = new byte[blockSize];
+                    ByteBuffer buffer = ByteBuffer.wrap(ibuffer.toByteArray());
+                    buffer.get(block, 0, bLen);
+                    src.get(block, bLen, remainder);
+                    len += cryptBlocks(
+                            ByteBuffer.wrap(block, 0, blockSize), dst);
+                    ibuffer.reset();
+                }
+            }
+
+            // encrypt any blocksized data in 'src'
+            if (src.remaining() >= blockSize) {
+                len += cryptBlocks(src, dst);
+            }
+
+            // Write the remaining bytes into the 'ibuffer'
+            if (src.remaining() > 0) {
+                initBuffer(src.remaining());
+                byte[] b = new byte[src.remaining()];
+                src.get(b);
+                // remainder offset is based on original buffer length
+                try {
+                    ibuffer.write(b);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            restoreDst(dst);
+            return len;
+        }
+
+        /**
+         * Return final encrypted data with auth tag using byte[]
+         */
+        @Override
+        public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+            int outOfs) throws IllegalBlockSizeException, ShortBufferException {
+            checkReInit();
+            try {
+                ArrayUtil.nullAndBoundsCheck(out, outOfs, getOutputSize(inLen,
+                    true));
+            } catch (ArrayIndexOutOfBoundsException aiobe) {
+                throw new ShortBufferException("Output buffer invalid");
+            }
+
+            int bLen = getBufferedLength();
+            checkDataLength(inLen, bLen, tagLenBytes);
+            processAAD();
+            out = overlapDetection(in, inOfs, out, outOfs);
+
+            int resultLen = 0;
+            byte[] block;
+
+            // process what is in the ibuffer
+            if (bLen > 0) {
+                byte[] buffer = ibuffer.toByteArray();
+
+                // Make a block if the remaining ibuffer and 'in' can make one.
+                if (bLen + inLen >= blockSize) {
+                    int r, bufOfs = 0;
+                    block = new byte[blockSize];
+                    r = mergeBlock(buffer, bufOfs, in, inOfs, inLen, block);
+                    inOfs += r;
+                    inLen -= r;
+                    r = gctrghash.update(block, 0, blockSize, out,
+                        outOfs);
+                    outOfs += r;
+                    resultLen += r;
+                    processed += r;
                 } else {
-                    // data is empty, so complete the ghash op with the
-                    // remaining buffer
-                    ghashAllToS.doLastBlock(buffer, buffer.remaining());
+                    // Need to consume all the ibuffer here to prepare for doFinal()
+                    block = new byte[bLen + inLen];
+                    System.arraycopy(buffer, 0, block, 0, bLen);
+                    System.arraycopy(in, inOfs, block, bLen, inLen);
+                    inLen += bLen;
+                    in = block;
+                    inOfs = 0;
                 }
             }
-            // Prepare buffer for decryption
-            buffer.flip();
+
+            // process what is left in the input buffer
+            if (inLen > TRIGGERLEN) {
+                int r = throttleData(gctrghash, in, inOfs, inLen, out, outOfs);
+                inOfs += r;
+                inLen -= r;
+                outOfs += r;
+                resultLen += r;
+                processed += r;
+            }
+
+            processed += gctrghash.doFinal(in, inOfs, inLen, out, outOfs);
+            outOfs += inLen;
+            resultLen += inLen;
+
+            block = getLengthBlock(sizeOfAAD, processed);
+            ghashAllToS.update(block);
+            block = ghashAllToS.digest();
+            new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+                tagLenBytes, block, 0);
+
+            // copy the tag to the end of the buffer
+            System.arraycopy(block, 0, out, outOfs, tagLenBytes);
+            int len = resultLen + tagLenBytes;
+            restoreOut(out, len);
+
+            reInit = true;
+            return len;
         }
 
-        if (ct.remaining() > 0) {
-            ghashAllToS.doLastBlock(ct, ct.remaining());
-        }
-        // Prepare buffer for decryption if available
-        ct.reset();
+        /**
+         * Return final encrypted data with auth tag using bytebuffers
+         */
+        @Override
+        public int doFinal(ByteBuffer src, ByteBuffer dst) throws
+            IllegalBlockSizeException, ShortBufferException {
+            checkReInit();
+            dst = overlapDetection(src, dst);
+            int len = src.remaining() + getBufferedLength();
 
-        byte[] block = getLengthBlock(sizeOfAAD, len);
-        ghashAllToS.update(block);
-        block = ghashAllToS.digest();
-        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-        gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
+            // 'len' includes ibuffer data
+            checkDataLength(len, tagLenBytes);
+            if (dst.remaining() < len + tagLenBytes) {
+                throw new ShortBufferException("Output buffer too small, must" +
+                    "be at least " + (len + tagLenBytes) + " bytes long");
+            }
 
-        // check entire authentication tag for time-consistency
-        int mismatch = 0;
-        for (int i = 0; i < tagLenBytes; i++) {
-            mismatch |= tag.get() ^ block[i];
+            processAAD();
+            if (len > 0) {
+                processed += doLastBlock(gctrghash,
+                    (ibuffer == null || ibuffer.size() == 0) ? null :
+                        ByteBuffer.wrap(ibuffer.toByteArray()), src, dst);
+            }
+
+            // release buffer if needed
+            if (ibuffer != null) {
+                ibuffer.reset();
+            }
+
+            byte[] block = getLengthBlock(sizeOfAAD, processed);
+            ghashAllToS.update(block);
+            block = ghashAllToS.digest();
+            new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+                tagLenBytes, block, 0);
+            dst.put(block, 0, tagLenBytes);
+            restoreDst(dst);
+
+            reInit = true;
+            return (len + tagLenBytes);
         }
 
-        if (mismatch != 0) {
-            throw new AEADBadTagException("Tag mismatch!");
+        // Handler method for encrypting blocks
+        int cryptBlocks(ByteBuffer src, ByteBuffer dst) {
+            int len;
+            if (src.remaining() > TRIGGERLEN) {
+                len = throttleData(gctrghash, src, dst);
+            } else {
+                len = gctrghash.update(src, dst);
+            }
+            processed += len;
+            return len;
         }
-
-        // Decrypt the all the input data and put it into dst
-        doLastBlock(buffer, ct, dst);
-        restoreDst(dst);
-        src.position(src.limit());
-        // 'processed' from the gctr decryption operation, not ghash
-        return processed;
     }
 
-    // return tag length in bytes
-    int getTagLen() {
-        return this.tagLenBytes;
-    }
+    /**
+     * Decryption Engine object
+     */
+    class GCMDecrypt extends GCMEngine {
+        // byte array of tag
+        byte[] tag;
+        // offset for byte[] operations
+        int tagOfs = 0;
 
-    int getBufferedLength() {
-        if (ibuffer == null) {
+        GCMDecrypt(SymmetricCipher blockCipher) {
+            super(blockCipher);
+        }
+
+        @Override
+        public int getOutputSize(int inLen, boolean isFinal) {
+            if (!isFinal) {
+                return 0;
+            }
+            return Math.max(inLen + getBufferedLength() - tagLenBytes, 0);
+        }
+
+        /**
+         * Find the tag in a given input buffer
+         *
+         * If tagOfs > 0, the tag is inside 'in' along with some encrypted data
+         * If tagOfs = 0, 'in' contains only the tag
+         * If tagOfs < 0, that tag is split between ibuffer and 'in'
+         * If tagOfs = -tagLenBytes, the tag is in the ibuffer, 'in' is empty.
+         */
+        void findTag(byte[] in, int inOfs, int inLen) {
+            tag = new byte[tagLenBytes];
+            if (inLen >= tagLenBytes) {
+                tagOfs = inLen - tagLenBytes;
+                System.arraycopy(in, inOfs + tagOfs, tag, 0,
+                    tagLenBytes);
+            } else {
+                // tagOfs will be negative
+                byte[] buffer = ibuffer.toByteArray();
+                tagOfs = mergeBlock(buffer,
+                    buffer.length - (tagLenBytes - inLen), in, inOfs, inLen,
+                    tag) - tagLenBytes;
+            }
+        }
+
+        // Put the input data into the ibuffer
+        @Override
+        byte[] doUpdate(byte[] in, int inOff, int inLen) {
+            try {
+                doUpdate(in, inOff, inLen, null, 0);
+            } catch (ShortBufferException e) {
+                // update decryption has no output
+            }
+            return new byte[0];
+        }
+
+        // Put the input data into the ibuffer
+        @Override
+        public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out,
+            int outOfs) throws ShortBufferException {
+
+            processAAD();
+            if (inLen > 0) {
+                // store internally until decryptFinal is called because
+                // spec mentioned that only return recovered data after tag
+                // is successfully verified
+                initBuffer(inLen);
+                ibuffer.write(in, inOfs, inLen);
+            }
             return 0;
-        } else {
-            return ibuffer.size();
+        }
+
+
+        // Put the src data into the ibuffer
+        @Override
+        public int doUpdate(ByteBuffer src, ByteBuffer dst)
+            throws ShortBufferException {
+
+            processAAD();
+            if (src.remaining() > 0) {
+                // If there is an array, use that to avoid the extra copy to
+                // take the src data out of the bytebuffer.
+                if (src.hasArray()) {
+                    doUpdate(src.array(), src.arrayOffset() + src.position(),
+                        src.remaining(), null, 0);
+                    src.position(src.limit());
+                } else {
+                    byte[] b = new byte[src.remaining()];
+                    src.get(b);
+                    initBuffer(b.length);
+                    try {
+                        ibuffer.write(b);
+                    } catch (IOException e) {
+                        throw new ProviderException(
+                            "Unable to add remaining input to the buffer", e);
+                    }
+                }
+            }
+            return 0;
+        }
+
+        /**
+         * Use any data from ibuffer and 'in' to first verify the auth tag. If
+         * the tag is valid, decrypt the data.
+         */
+        @Override
+        public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+            int outOfs) throws IllegalBlockSizeException, AEADBadTagException,
+            ShortBufferException {
+            GHASH save = null;
+
+            int len = inLen + getBufferedLength();
+            try {
+                ArrayUtil.nullAndBoundsCheck(out, outOfs, len - tagLenBytes);
+            } catch (ArrayIndexOutOfBoundsException aiobe) {
+                throw new ShortBufferException("Output buffer invalid");
+            }
+
+            if (len < tagLenBytes) {
+                throw new AEADBadTagException("Input too short - need tag");
+            }
+
+            if (len - tagLenBytes > out.length - outOfs) {
+                save = ghashAllToS.clone();
+            }
+
+            checkDataLength(len - tagLenBytes);
+            processAAD();
+
+            findTag(in, inOfs, inLen);
+            byte[] block = getLengthBlock(sizeOfAAD,
+                decryptBlocks(ghashAllToS, in, inOfs, inLen, null, 0));
+            ghashAllToS.update(block);
+            block = ghashAllToS.digest();
+            new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+                tagLenBytes, block, 0);
+
+            // check entire authentication tag for time-consistency
+            int mismatch = 0;
+            for (int i = 0; i < tagLenBytes; i++) {
+                mismatch |= tag[i] ^ block[i];
+            }
+
+            if (mismatch != 0) {
+                throw new AEADBadTagException("Tag mismatch!");
+            }
+
+            if (save != null) {
+                ghashAllToS = save;
+                throw new ShortBufferException("Output buffer too small, must" +
+                    "be at least " + (len - tagLenBytes) + " bytes long");
+            }
+
+            out = overlapDetection(in, inOfs, out, outOfs);
+            len = decryptBlocks(gctrPAndC, in, inOfs, inLen, out, outOfs);
+            restoreOut(out, len);
+            return len;
+        }
+
+        /**
+         * Use any data from ibuffer and 'src' to first verify the auth tag. If
+         * the tag is valid, decrypt the data.
+         */
+        @Override
+        public int doFinal(ByteBuffer src, ByteBuffer dst)
+            throws IllegalBlockSizeException, AEADBadTagException,
+            ShortBufferException {
+            GHASH save = null;
+
+            ByteBuffer tag;
+            ByteBuffer ct = src.duplicate();
+            ByteBuffer buffer = null;
+
+            // The 'len' the total amount of ciphertext
+            int len = ct.remaining() - tagLenBytes;
+
+            // Check if ibuffer has data
+            if (getBufferedLength() != 0) {
+                buffer = ByteBuffer.wrap(ibuffer.toByteArray());
+                len += buffer.remaining();
+            }
+
+            checkDataLength(len);
+
+            // Save GHASH context to allow the tag to be checked even though
+            // the dst buffer is too short.  Context will be restored so the
+            // method can be called again with the proper sized dst buffer.
+            if (len > dst.remaining()) {
+                save = ghashAllToS.clone();
+            }
+
+            // Create buffer 'tag' that contains only the auth tag
+            if (ct.remaining() >= tagLenBytes) {
+                tag = src.duplicate();
+                tag.position(ct.limit() - tagLenBytes);
+                ct.limit(ct.limit() - tagLenBytes);
+            } else if (buffer != null) {
+                // It's unlikely the tag will be between the buffer and data
+                tag = ByteBuffer.allocate(tagLenBytes);
+                int limit = buffer.remaining() - (tagLenBytes - ct.remaining());
+                buffer.mark();
+                buffer.position(limit);
+                // Read from "new" limit to buffer's end
+                tag.put(buffer);
+                // reset buffer to data only
+                buffer.reset();
+                // Set the limit to where the ciphertext ends
+                buffer.limit(limit);
+                tag.put(ct);
+                tag.flip();
+            } else {
+                throw new AEADBadTagException("Input too short - need tag");
+            }
+
+            // Set the mark for a later reset. Either it will be zero, or the
+            // tag buffer creation above will have consume some or all of it.
+            ct.mark();
+
+            processAAD();
+            // Perform GHASH check on data
+            doLastBlock(ghashAllToS, buffer, ct, null);
+
+            byte[] block = getLengthBlock(sizeOfAAD, len);
+            ghashAllToS.update(block);
+            block = ghashAllToS.digest();
+            new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+                tagLenBytes, block, 0);
+
+            // check entire authentication tag for time-consistency
+            int mismatch = 0;
+            for (int i = 0; i < tagLenBytes; i++) {
+                mismatch |= tag.get() ^ block[i];
+            }
+
+            if (mismatch != 0) {
+                throw new AEADBadTagException("Tag mismatch!");
+            }
+
+            if (save != null) {
+                ghashAllToS = save;
+                throw new ShortBufferException("Output buffer too small, must" +
+                    " be at least " + len + " bytes long");
+            }
+
+            // Prepare for decryption
+            if (buffer != null) {
+                buffer.flip();
+            }
+            ct.reset();
+            processed = 0;
+            // Check for overlap in the bytebuffers
+            dst = overlapDetection(src, dst);
+
+            // Decrypt the all the input data and put it into dst
+            doLastBlock(gctrPAndC, buffer, ct, dst);
+            restoreDst(dst);
+            src.position(src.limit());
+            if (ibuffer != null) {
+                ibuffer.reset();
+            }
+            return processed;
+        }
+
+        /**
+         * This method organizes the data from the ibuffer and 'in' to
+         * blocksize operations for GHASH and GCTR decryption operations.
+         * When this method is used, all the data is either in the ibuffer
+         * or in 'in'.
+         */
+        int decryptBlocks(GCM op, byte[] in, int inOfs, int inLen,
+            byte[] out, int outOfs) {
+            byte[] buffer;
+            byte[] block;
+            int len = 0;
+
+            // Calculate the encrypted data length inside the ibuffer
+            // considering the tag location
+            int bLen = getBufferedLength();
+
+            // Change the inLen based of the tag location.
+            if (tagOfs < 0) {
+                inLen = 0;
+                bLen += tagOfs;
+            } else {
+                inLen -= tagLenBytes;
+            }
+
+            if (bLen > 0) {
+                buffer = ibuffer.toByteArray();
+
+                if (bLen >= blockSize) {
+                    len += op.update(buffer, 0, bLen, out, outOfs);
+                    outOfs += len; // noop for ghash
+                    // Use len as it becomes the ibuffer offset, if
+                    // needed, in the next op
+                }
+
+                // merge the remaining ibuffer with the 'in'
+                int bufRemainder = bLen - len;
+                if (bufRemainder > 0) {
+                    block = new byte[blockSize];
+                    int inUsed = mergeBlock(buffer, len, bufRemainder, in,
+                        inOfs, inLen, block);
+                    // update the input parameters for what was taken out of 'in'
+                    inOfs += inUsed;
+                    inLen -= inUsed;
+                    // If is more than block between the merged data and 'in',
+                    // update(), otherwise setup for final
+                    if (inLen > 0) {
+                        int resultLen = op.update(block, 0, blockSize,
+                            out, outOfs);
+                        outOfs += resultLen; // noop for ghash
+                        len += resultLen;
+                    } else {
+                        in = block;
+                        inOfs = 0;
+                        inLen = inUsed + bufRemainder;
+                    }
+                }
+            }
+
+            // Finish off the operation
+            if (inLen > TRIGGERLEN) {
+                int l = throttleData(op, in, inOfs, inLen, out, outOfs);
+                inOfs += l;
+                inLen -= l;
+                outOfs += l; // noop for ghash
+                len += l;
+            }
+            return len + op.doFinal(in, inOfs, inLen, out, outOfs);
+        }
+    }
+
+    public static final class AESGCM extends GaloisCounterMode {
+        public AESGCM() {
+            super(-1, new AESCrypt());
+        }
+    }
+
+    public static final class AES128 extends GaloisCounterMode {
+        public AES128() {
+            super(16, new AESCrypt());
+        }
+    }
+
+    public static final class AES192 extends GaloisCounterMode {
+        public AES192() {
+            super(24, new AESCrypt());
+        }
+    }
+
+    public static final class AES256 extends GaloisCounterMode {
+        public AES256() {
+            super(32, new AESCrypt());
         }
     }
 
     /**
-     * Check for overlap. If the src and dst buffers are using shared data and
-     * if dst will overwrite src data before src can be processed.  If so, make
-     * a copy to put the dst data in.
+     * This class is for encryption when both GCTR and GHASH
+     * can operation in parallel.
      */
-    ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) {
-        if (src.isDirect() && dst.isDirect()) {
-            DirectBuffer dsrc = (DirectBuffer) src;
-            DirectBuffer ddst = (DirectBuffer) dst;
+    static final class GCTRGHASH implements GCM {
+        GCTR gctr;
+        GHASH ghash;
 
-            // Get the current memory address for the given ByteBuffers
-            long srcaddr = dsrc.address();
-            long dstaddr = ddst.address();
-
-            // Find the lowest attachment that is the base memory address of the
-            // shared memory for the src object
-            while (dsrc.attachment() != null) {
-                srcaddr = ((DirectBuffer) dsrc.attachment()).address();
-                dsrc = (DirectBuffer) dsrc.attachment();
-            }
-
-            // Find the lowest attachment that is the base memory address of the
-            // shared memory for the dst object
-            while (ddst.attachment() != null) {
-                dstaddr = ((DirectBuffer) ddst.attachment()).address();
-                ddst = (DirectBuffer) ddst.attachment();
-            }
-
-            // If the base addresses are not the same, there is no overlap
-            if (srcaddr != dstaddr) {
-                return dst;
-            }
-            // At this point we know these objects share the same memory.
-            // This checks the starting position of the src and dst address for
-            // overlap.
-            // It uses the base address minus the passed object's address to get
-            // the offset from the base address, then add the position() from
-            // the passed object.  That gives up the true offset from the base
-            // address.  As long as the src side is >= the dst side, we are not
-            // in overlap.
-            if (((DirectBuffer) src).address() - srcaddr + src.position() >=
-                ((DirectBuffer) dst).address() - dstaddr + dst.position()) {
-                return dst;
-            }
-
-        } else if (!src.isDirect() && !dst.isDirect()) {
-            if (!src.isReadOnly()) {
-                // If using the heap, check underlying byte[] address.
-                if (src.array() != dst.array()) {
-                    return dst;
-                }
-
-                // Position plus arrayOffset() will give us the true offset from
-                // the underlying byte[] address.
-                if (src.position() + src.arrayOffset() >=
-                    dst.position() + dst.arrayOffset()) {
-                    return dst;
-                }
-            }
-        } else {
-            // buffer types aren't the same
-            return dst;
+        GCTRGHASH(GCTR c, GHASH g) {
+            gctr = c;
+            ghash = g;
         }
 
-        // Create a copy
-        ByteBuffer tmp = dst.duplicate();
-        // We can use a heap buffer for internal use, save on alloc cost
-        ByteBuffer bb = ByteBuffer.allocate(dst.remaining());
-        tmp.limit(dst.limit());
-        tmp.position(dst.position());
-        bb.put(tmp);
-        bb.flip();
-        originalDst = dst;
-        return bb;
-    }
-
-    /**
-     * If originalDst exists, dst is an internal dst buffer, then copy the data
-     * into the original dst buffer
-     */
-    void restoreDst(ByteBuffer dst) {
-        if (originalDst == null) {
-            return;
+        @Override
+        public int update(byte[] in, int inOfs, int inLen, byte[] out,
+            int outOfs) {
+            int len = gctr.update(in, inOfs, inLen, out, outOfs);
+            ghash.update(out, outOfs, len);
+            return len;
         }
 
-        dst.flip();
-        originalDst.put(dst);
-        originalDst = null;
+        @Override
+        public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
+            dst.mark();
+            int len = gctr.update(in, inOfs, inLen, dst);
+            dst.reset();
+            ghash.update(dst, len);
+            return len;
+        }
+
+        @Override
+        public int update(ByteBuffer src, ByteBuffer dst) {
+            dst.mark();
+            int len = gctr.update(src, dst);
+            dst.reset();
+            ghash.update(dst, len);
+            return len;
+        }
+
+        @Override
+        public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
+            int len = gctr.doFinal(in, inOfs, inLen, out, outOfs);
+            ghash.doFinal(out, outOfs, len);
+            return len;
+        }
+
+        @Override
+        public int doFinal(ByteBuffer src, ByteBuffer dst) {
+            dst.mark();
+            int l = gctr.doFinal(src, dst);
+            dst.reset();
+            ghash.doFinal(dst, l);
+            return l;
+        }
     }
 }
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
index 521afa4b696..a020e1c15d8 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
@@ -165,7 +165,7 @@ public final class SunJCE extends Provider {
             "|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64" +
             "|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64";
         final String BLOCK_MODES128 = BLOCK_MODES +
-            "|GCM|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
+            "|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
             "|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128";
         final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
 
@@ -214,9 +214,6 @@ public final class SunJCE extends Provider {
         psA("Cipher", "AES_128/CFB/NoPadding",
                 "com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding",
                 attrs);
-        psA("Cipher", "AES_128/GCM/NoPadding",
-                "com.sun.crypto.provider.AESCipher$AES128_GCM_NoPadding",
-                attrs);
         psA("Cipher", "AES_128/KW/NoPadding",
                 "com.sun.crypto.provider.KeyWrapCipher$AES128_KW_NoPadding",
                 attrs);
@@ -239,9 +236,6 @@ public final class SunJCE extends Provider {
         psA("Cipher", "AES_192/CFB/NoPadding",
                 "com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding",
                 attrs);
-        psA("Cipher", "AES_192/GCM/NoPadding",
-                "com.sun.crypto.provider.AESCipher$AES192_GCM_NoPadding",
-                attrs);
         psA("Cipher", "AES_192/KW/NoPadding",
                 "com.sun.crypto.provider.KeyWrapCipher$AES192_KW_NoPadding",
                 attrs);
@@ -264,9 +258,6 @@ public final class SunJCE extends Provider {
         psA("Cipher", "AES_256/CFB/NoPadding",
                 "com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding",
                 attrs);
-        psA("Cipher", "AES_256/GCM/NoPadding",
-                "com.sun.crypto.provider.AESCipher$AES256_GCM_NoPadding",
-                attrs);
         psA("Cipher", "AES_256/KW/NoPadding",
                 "com.sun.crypto.provider.KeyWrapCipher$AES256_KW_NoPadding",
                 attrs);
@@ -277,6 +268,23 @@ public final class SunJCE extends Provider {
                 "com.sun.crypto.provider.KeyWrapCipher$AES256_KWP_NoPadding",
                 attrs);
 
+        attrs.clear();
+        attrs.put("SupportedModes", "GCM");
+        attrs.put("SupportedKeyFormats", "RAW");
+
+        ps("Cipher", "AES/GCM/NoPadding",
+                "com.sun.crypto.provider.GaloisCounterMode$AESGCM", null,
+                attrs);
+        psA("Cipher", "AES_128/GCM/NoPadding",
+                "com.sun.crypto.provider.GaloisCounterMode$AES128",
+                attrs);
+        psA("Cipher", "AES_192/GCM/NoPadding",
+                "com.sun.crypto.provider.GaloisCounterMode$AES192",
+                attrs);
+        psA("Cipher", "AES_256/GCM/NoPadding",
+                "com.sun.crypto.provider.GaloisCounterMode$AES256",
+                attrs);
+
         attrs.clear();
         attrs.put("SupportedModes", "CBC");
         attrs.put("SupportedPaddings", "NOPADDING");
diff --git a/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java b/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
index 6c65c19ba85..ed826dff24a 100644
--- a/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
+++ b/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
@@ -217,8 +217,6 @@ public final class SecurityProviderConstants {
 
         store("DiffieHellman", KnownOIDs.DiffieHellman);
 
-        store("AES", KnownOIDs.AES, "Rijndael");
-
         store("EC", KnownOIDs.EC, "EllipticCurve");
 
         store("X.509", null, "X509");
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
index d96cb00a6f4..58267c2ffba 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
@@ -27,6 +27,7 @@ import java.security.Provider;
 import java.security.Security;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HexFormat;
 import java.util.List;
 import javax.crypto.SecretKey;
 import javax.crypto.Cipher;
@@ -84,7 +85,7 @@ import javax.crypto.KeyGenerator;
  */
 public class Encrypt {
 
-    private static final String ALGORITHMS[] = { "AES", "Rijndael" };
+    private static final String ALGORITHMS[] = { "AES" };
     private static final int KEY_STRENGTHS[] = { 128, 192, 256 };
     private static final int TEXT_LENGTHS[] = { 0, 256, 1024 };
     private static final int AAD_LENGTHS[] = { 0, 8, 128, 256, 1024 };
@@ -230,8 +231,12 @@ public class Encrypt {
         combination_16(outputTexts, mode, AAD, inputText, params);
 
         for (int k = 0; k < outputTexts.size(); k++) {
+            HexFormat hex = HexFormat.of().withUpperCase();
             if (!Arrays.equals(output, outputTexts.get(k))) {
-                throw new RuntimeException("Combination #" + k + " failed");
+                System.out.println("Combination #" + (k + 1) + "\nresult    " +
+                    hex.formatHex(outputTexts.get(k)) +
+                    "\nexpected: " + hex.formatHex(output));
+                throw new RuntimeException("Combination #" + (k + 1) + " failed");
             }
         }
         return output;
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
index 54d271d63ae..2c0dcae2807 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -36,11 +36,11 @@ import javax.crypto.SecretKey;
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 import java.io.ByteArrayOutputStream;
-import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HexFormat;
 import java.util.List;
 
 public class GCMBufferTest implements Cloneable {
@@ -65,6 +65,7 @@ public class GCMBufferTest implements Cloneable {
     boolean theoreticalCheck;
     List<Data> dataSet;
     int inOfs = 0, outOfs = 0;
+    static HexFormat hex = HexFormat.of();
 
     static class Data {
         int id;
@@ -108,14 +109,21 @@ public class GCMBufferTest implements Cloneable {
                     new GCMParameterSpec(tag.length * 8, this.iv));
                 tct = c.doFinal(pt);
             } catch (Exception e) {
-                System.out.println("Error in generating data for length " +
-                    ptlen);
+                throw new RuntimeException("Error in generating data for length " +
+                    ptlen, e);
             }
             ct = new byte[ptlen];
             System.arraycopy(tct, 0, ct, 0, ct.length);
             System.arraycopy(tct, ct.length, tag, 0, tag.length);
         }
 
+        private static final byte[] HexToBytes(String hexVal) {
+            if (hexVal == null) {
+                return new byte[0];
+            }
+            return hex.parseHex(hexVal);
+        }
+
     }
 
     /**
@@ -176,7 +184,7 @@ public class GCMBufferTest implements Cloneable {
                 return this;
             }
         }
-        throw new Exception("Unaeble to find dataSet id = " + id);
+        throw new Exception("Unable to find dataSet id = " + id);
     }
 
     /**
@@ -244,7 +252,7 @@ public class GCMBufferTest implements Cloneable {
 
     void test() throws Exception {
         int i = 1;
-        System.out.println("Algo: " + algo + " \tOps: " + ops.toString());
+        System.err.println("Algo: " + algo + " \tOps: " + ops.toString());
         for (Data data : dataSet) {
 
             // If incrementalSegments is enabled, run through that test only
@@ -256,31 +264,31 @@ public class GCMBufferTest implements Cloneable {
                 sizes = new int[ops.size()];
 
                 while (incrementSizes(data.pt.length)) {
-                    System.out.print("Encrypt:  Data Index: " + i + " \tSizes[ ");
+                    System.err.print("Encrypt:  Data Index: " + i + " \tSizes[ ");
                     for (int v : sizes) {
-                        System.out.print(v + " ");
+                        System.err.print(v + " ");
                     }
-                    System.out.println("]");
+                    System.err.println("]");
                     encrypt(data);
                 }
                 Arrays.fill(sizes, 0);
 
                 while (incrementSizes(data.ct.length + data.tag.length)) {
-                    System.out.print("Decrypt:  Data Index: " + i + " \tSizes[ ");
+                    System.err.print("Decrypt:  Data Index: " + i + " \tSizes[ ");
                     for (int v : sizes) {
-                        System.out.print(v + " ");
+                        System.err.print(v + " ");
                     }
-                    System.out.println("]");
+                    System.err.println("]");
                     decrypt(data);
                 }
 
             } else {
                 // Default test of 0 and 2 offset doing in place and different
                 // i/o buffers
-                System.out.println("Encrypt:  Data Index: " + i);
+                System.err.println("Encrypt:  Data Index: " + i);
                 encrypt(data);
 
-                System.out.println("Decrypt:  Data Index: " + i);
+                System.err.println("Decrypt:  Data Index: " + i);
                 decrypt(data);
             }
             i++;
@@ -298,13 +306,13 @@ public class GCMBufferTest implements Cloneable {
             data.tag.length);
 
         // Test different input/output buffers
-        System.out.println("\tinput len: " + input.length + "  inOfs " +
+        System.err.println("\tinput len: " + input.length + "  inOfs " +
             inOfs + "  outOfs " + outOfs + "  in/out buffer: different");
         crypto(true, data, input, output);
 
         // Test with in-place buffers
         if (same) {
-            System.out.println("\tinput len: " + input.length + "  inOfs " +
+            System.err.println("\tinput len: " + input.length + "  inOfs " +
             inOfs + "  outOfs " + outOfs + "  in/out buffer: in-place");
             cryptoSameBuffer(true, data, input, output);
         }
@@ -320,13 +328,13 @@ public class GCMBufferTest implements Cloneable {
         output = data.pt;
 
         // Test different input/output buffers
-        System.out.println("\tinput len: " + input.length + "  inOfs " +
-            inOfs + "  outOfs " + outOfs + "  in-place: different");
+        System.err.println("\tinput len: " + input.length + "  inOfs " +
+            inOfs + "  outOfs " + outOfs + "  in/out buffer: different");
         crypto(false, data, input, output);
 
         // Test with in-place buffers
         if (same) {
-            System.out.println("\tinput len: " + input.length + "  inOfs " +
+            System.err.println("\tinput len: " + input.length + "  inOfs " +
             inOfs + "  outOfs " + outOfs + "  in-place: same");
             cryptoSameBuffer(false, data, input, output);
         }
@@ -484,12 +492,10 @@ public class GCMBufferTest implements Cloneable {
                 if (ctresult.length != expectedOut.length ||
                     Arrays.compare(ctresult, expectedOut) != 0) {
                     String s = "Ciphertext mismatch (" + v.name() +
-                        "):\nresult   (len=" + ctresult.length + "):" +
-                        String.format("%0" + (ctresult.length << 1) + "x",
-                            new BigInteger(1, ctresult)) +
-                        "\nexpected (len=" + output.length + "):" +
-                    String.format("%0" + (output.length << 1) + "x",
-                        new BigInteger(1, output));
+                        "):\nresult   (len=" + ctresult.length + "): " +
+                        hex.formatHex(ctresult) +
+                        "\nexpected (len=" + output.length + "): " +
+                        hex.formatHex(output);
                     System.err.println(s);
                     throw new Exception(s);
 
@@ -605,10 +611,9 @@ public class GCMBufferTest implements Cloneable {
                         output.length) != 0) {
                     String s = "Ciphertext mismatch (" + v.name() +
                         "):\nresult (len=" + len + "):\n" +
-                        byteToHex(out) +
+                        hex.formatHex(out) +
                         "\nexpected (len=" + output.length + "):\n" +
-                        String.format("%0" + (output.length << 1) + "x",
-                            new BigInteger(1, output));
+                        hex.formatHex(output);
                     System.err.println(s);
                     throw new Exception(s);
                 }
@@ -623,7 +628,10 @@ public class GCMBufferTest implements Cloneable {
     }
 
     public static void main(String args[]) throws Exception {
+        GCMBufferTest t;
+
         initTest();
+
         // Test single byte array
         new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)).test();
         offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)));
@@ -662,7 +670,7 @@ public class GCMBufferTest implements Cloneable {
             List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)));
 
         // Test update-update-doFinal with byte arrays and preset data sizes
-        GCMBufferTest t = new GCMBufferTest("AES/GCM/NoPadding",
+        t = new GCMBufferTest("AES/GCM/NoPadding",
             List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).dataSegments(
             new int[] { 1, 1, GCMBufferTest.REMAINDER});
         t.clone().test();
@@ -678,6 +686,7 @@ public class GCMBufferTest implements Cloneable {
             List.of(dtype.BYTE, dtype.HEAP, dtype.DIRECT)).differentBufferOnly();
         t.clone().test();
         offsetTests(t.clone());
+
         // Test update-doFinal with a direct bytebuffer and a byte array.
         t = new GCMBufferTest("AES/GCM/NoPadding",
             List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly();
@@ -710,26 +719,10 @@ public class GCMBufferTest implements Cloneable {
         new GCMBufferTest("AES/GCM/NoPadding",
             List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
             incrementalSegments().dataSet(0).test();
-    }
 
-    private static byte[] HexToBytes(String hexVal) {
-        if (hexVal == null) {
-            return new byte[0];
-        }
-        byte[] result = new byte[hexVal.length()/2];
-        for (int i = 0; i < result.length; i++) {
-            String byteVal = hexVal.substring(2*i, 2*i +2);
-            result[i] = Integer.valueOf(byteVal, 16).byteValue();
-        }
-        return result;
-    }
-
-    private static String byteToHex(byte[] barray) {
-        StringBuilder s = new StringBuilder();
-        for (byte b : barray) {
-            s.append(String.format("%02x", b));
-        }
-        return s.toString();
+        new GCMBufferTest("AES/GCM/NoPadding",
+            List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
+            dataSegments(new int[] { 49, 0, 2 }).dataSet(0).test();
     }
 
     // Test data
@@ -762,8 +755,7 @@ public class GCMBufferTest implements Cloneable {
                 "b6e6f197168f5049aeda32dafbdaeb"),
             // zero'd test data
             new Data("AES", 3, "272f16edb81a7abbea887357a58c1917",
-                "794ec588176c703d3d2a7a07",
-                new byte[256], null,
+                "794ec588176c703d3d2a7a07", new byte[256], null,
                 "15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" +
                 "e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" +
                 "07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" +
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java
new file mode 100644
index 00000000000..3cfaed9bd9d
--- /dev/null
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.crypto.Cipher;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HexFormat;
+
+/*
+ * @test
+ * @summary Call decrypt doFinal() with different output values to see if the
+ * the operation can complete after a ShortBufferException
+ */
+public class GCMShortBuffer {
+    static Cipher c;
+    static final GCMParameterSpec iv = new GCMParameterSpec(128, new byte[16]);
+    static final SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+    static byte cipherText[], plaintext[] = new byte[51];
+    boolean error = false;
+
+    GCMShortBuffer(byte[] out) throws Exception {
+        int len = cipherText.length - 1;
+
+        c.init(Cipher.DECRYPT_MODE, keySpec, iv);
+        byte[] pt = new byte[c.getOutputSize(cipherText.length)];
+        c.update(cipherText, 0, 1);
+        try {
+            c.doFinal(cipherText, 1, len, out, 0);
+        } catch (ShortBufferException e) {
+            System.out.println("ShortBuffer caught");
+        } catch (Exception e) {
+            throw e;
+        }
+        int r = c.doFinal(cipherText, 1, len, pt, 0);
+        if (r != pt.length) {
+            System.out.println(
+                "doFinal() return ( " + r + ") is not the same" +
+                    "as getOutputSize returned" + pt.length);
+            error = true;
+        }
+        if (Arrays.compare(pt, plaintext) != 0) {
+            System.out.println("output  : " + HexFormat.of().formatHex(pt));
+            System.out.println("expected: " +
+                HexFormat.of().formatHex(plaintext));
+            System.out.println("output and plaintext do not match");
+            error = true;
+        }
+        if (error) {
+            throw new Exception("An error has occurred");
+        }
+    }
+
+    GCMShortBuffer(ByteBuffer dst) throws Exception {
+        int len = cipherText.length - 1;
+        ByteBuffer out = ByteBuffer.allocate(plaintext.length);
+
+        c.init(Cipher.DECRYPT_MODE, keySpec, iv);
+        c.update(cipherText, 0, 1);
+        ByteBuffer ct = ByteBuffer.wrap(cipherText, 1, len);
+        try {
+            c.doFinal(ct , dst);
+        } catch (ShortBufferException e) {
+            System.out.println("ShortBuffer caught");
+        } catch (Exception e) {
+            throw e;
+        }
+        int r = c.doFinal(ByteBuffer.wrap(cipherText, 1, len), out);
+        out.flip();
+        if (r != out.capacity()) {
+            System.out.println(
+                "doFinal() return ( " + r + ") is not the same" +
+                    " as getOutputSize returned" + out.capacity());
+            error = true;
+        }
+        if (out.compareTo(ByteBuffer.wrap(plaintext)) != 0) {
+            System.out.println("output and plaintext do not match");
+            System.out.println("output  : " +
+                HexFormat.of().formatHex(out.array()));
+            System.out.println("expected: " +
+                HexFormat.of().formatHex(plaintext));
+            error = true;
+        }
+        if (error) {
+            throw new Exception("An error has occurred");
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+        c = Cipher.getInstance("AES/GCM/NoPadding");
+        c.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+        cipherText = c.doFinal(plaintext);
+
+        new GCMShortBuffer(new byte[13]);
+        new GCMShortBuffer(new byte[50]);
+        new GCMShortBuffer(ByteBuffer.allocate(13));
+        new GCMShortBuffer(ByteBuffer.allocate(50));
+    }
+
+
+}
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
index 9e84887c990..c8af2d2fbd5 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
@@ -107,6 +107,9 @@ public class OverlapByteBuffer {
                     }
                 }
 
+                System.out.println("inOfsInBuf  = " + inOfsInBuf);
+                System.out.println("outOfsInBuf = " + outOfsInBuf);
+
                 // Copy data into shared buffer
                 input.put(baseBuf);
                 input.flip();
@@ -132,8 +135,6 @@ public class OverlapByteBuffer {
                     cipher.doFinal(in, output);
 
                     output.flip();
-                    System.out.println("inOfsInBuf  = " + inOfsInBuf);
-                    System.out.println("outOfsInBuf = " + outOfsInBuf);
                     ByteBuffer b = ByteBuffer.wrap(baseBuf);
                     if (b.compareTo(output) != 0) {
                         System.err.println(
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
index 0ea8d01bc92..dd0d97101c1 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,15 +26,16 @@ import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.spec.AlgorithmParameterSpec;
-import java.util.Random;
+import java.util.HexFormat;
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
  * @test
@@ -43,7 +44,6 @@ import javax.crypto.spec.IvParameterSpec;
  *          doesn't use IV).
  * @author Liwen Wang
  * @author Parag Salvi
- * @key randomness
  */
 public class TestAESCipher {
 
@@ -56,7 +56,9 @@ public class TestAESCipher {
         "OFB32", "OFB40", "OFB48", "OFB56", "OFB64", "OFB72", "OFB80",
         "OFB88", "OFB96", "OFB104", "OFB112", "OFB120", "GCM" };
     private static final String[] PADDING = { "NoPadding", "PKCS5Padding" };
-    private static final int KEY_LENGTH = 128;
+    private static final int KEY_LENGTH = 16;
+    static byte[] plainText = new byte[128];
+    static byte[] key = new byte[KEY_LENGTH];
 
     public static void main(String argv[]) throws Exception {
         TestAESCipher test = new TestAESCipher();
@@ -73,32 +75,31 @@ public class TestAESCipher {
         }
     }
 
+
     public void runTest(String algo, String mo, String pad) throws Exception {
-        Cipher ci = null;
-        byte[] iv = null;
-        AlgorithmParameterSpec aps = null;
-        SecretKey key = null;
+        Cipher ci;
+        System.out.println("Testing " + algo + "/" + mo + "/" + pad);
+
+        byte[] iv = new byte[16];
+        AlgorithmParameterSpec aps = new GCMParameterSpec(128, iv);
+        SecretKey key = new SecretKeySpec(this.key, 0, KEY_LENGTH,"AES");
+
         try {
             // Initialization
-            Random rdm = new Random();
-            byte[] plainText = new byte[128];
-            rdm.nextBytes(plainText);
-
             ci = Cipher.getInstance(algo + "/" + mo + "/" + pad, PROVIDER);
-            KeyGenerator kg = KeyGenerator.getInstance(algo, PROVIDER);
-            kg.init(KEY_LENGTH);
-            key = kg.generateKey();
 
             // encrypt
-            if (!mo.equalsIgnoreCase("GCM")) {
+            if (mo.equalsIgnoreCase("GCM")) {
                 ci.init(Cipher.ENCRYPT_MODE, key, aps);
+            } else if (mo.equalsIgnoreCase("ECB")) {
+                ci.init(Cipher.ENCRYPT_MODE, key, (AlgorithmParameterSpec)null);
             } else {
-                ci.init(Cipher.ENCRYPT_MODE, key);
+                ci.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
             }
 
             byte[] cipherText = new byte[ci.getOutputSize(plainText.length)];
             int offset = ci.update(plainText, 0, plainText.length, cipherText,
-                    0);
+                0);
             ci.doFinal(cipherText, offset);
 
             if (!mo.equalsIgnoreCase("ECB")) {
@@ -117,25 +118,24 @@ public class TestAESCipher {
             byte[] recoveredText = new byte[ci.getOutputSize(cipherText.length)];
             int len = ci.doFinal(cipherText, 0, cipherText.length,
                     recoveredText);
-            byte[] tmp = new byte[len];
-            System.arraycopy(recoveredText, 0, tmp, 0, len);
 
             // Comparison
-            if (!java.util.Arrays.equals(plainText, tmp)) {
+            if (!java.util.Arrays.equals(plainText, 0 , plainText.length,
+                recoveredText, 0, len)) {
                 System.out.println("Original: ");
-                dumpBytes(plainText);
+                System.out.println(HexFormat.of().formatHex(plainText));
                 System.out.println("Recovered: ");
-                dumpBytes(tmp);
-                throw new RuntimeException(
-                        "Original text is not equal with recovered text, with mode:"
-                                + mo);
+                System.out.println(HexFormat.of().
+                    formatHex(recoveredText, 0, len));
+                throw new RuntimeException("Original text is not equal with " +
+                    "recovered text, with mode:" + mo);
             }
 
         } catch (NoSuchAlgorithmException e) {
             //CFB7 and OFB150 are for negative testing
             if (!mo.equalsIgnoreCase("CFB7") && !mo.equalsIgnoreCase("OFB150")) {
-                System.out.println("Unexpected NoSuchAlgorithmException with mode: "
-                        + mo);
+                System.out.println("Unexpected NoSuchAlgorithmException with" +
+                    " mode: " + mo);
                 throw new RuntimeException("Test failed!");
             }
         }  catch ( NoSuchProviderException | NoSuchPaddingException
@@ -146,12 +146,4 @@ public class TestAESCipher {
             throw e;
         }
     }
-
-    private void dumpBytes(byte[] bytes) {
-        for (byte b : bytes) {
-            System.out.print(Integer.toHexString(b));
-        }
-
-        System.out.println();
-    }
 }
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
index f4cc9276bbc..e158bc54652 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -46,7 +46,7 @@ import javax.crypto.spec.IvParameterSpec;
  */
 public class TestSameBuffer {
 
-    private static final String ALGORITHM = "Rijndael";
+    private static final String ALGORITHM = "AES";
     private static final String PROVIDER = "SunJCE";
     private static final String[] MODES = { "ECb", "CbC", "OFB", "CFB150",
         "cFB", "CFB7", " cFB8", "cFB16", "cFB24", "cFB32", "Cfb40",