/*
 * 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
 * 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 jdk.internal.misc.Unsafe;
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 java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;

import jdk.internal.vm.annotation.IntrinsicCandidate;

This class represents ciphers in GaloisCounter (GCM) mode.

This mode currently should only be used w/ AES cipher. Although no checking is done, caller should only pass AES Cipher to the constructor.

NOTE: Unlike other modes, when used for decryption, this class will buffer all processed outputs internally and won't return them until the tag has been successfully verified.

Since:1.8
/** * This class represents ciphers in GaloisCounter (GCM) mode. * * <p>This mode currently should only be used w/ AES cipher. * Although no checking is done, caller should only pass AES * Cipher to the constructor. * * <p>NOTE: Unlike other modes, when used for decryption, this class * will buffer all processed outputs internally and won't return them * until the tag has been successfully verified. * * @since 1.8 */
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. // However, given the current GCM spec requirement that recovered // text can only be returned after successful tag verification, // we are bound by limiting the data size to the size limit of // 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 // x86-64 parallel intrinsic data size private static final int PARALLEL_LEN = 8192; // max data size for x86-64 intrinsic private static final int SPLIT_LEN = 1048576; // 1MB static final byte[] EMPTY_BUF = new byte[0]; private boolean initialized = false; SymmetricCipher blockCipher; // Engine instance for encryption or decryption private GCMEngine engine; private boolean encryption = true; // 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;
Params:
  • keySize – length of key.
  • embeddedCipher – Cipher object, such as AESCrypt.
/** * * @param keySize length of key. * @param embeddedCipher Cipher object, such as AESCrypt. */
GaloisCounterMode(int keySize, SymmetricCipher embeddedCipher) { blockCipher = embeddedCipher; this.keySize = keySize; }
Initializes the cipher in the specified mode with the given key and iv.
/** * 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); 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; // 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 try { blockCipher.init(false, key.getAlgorithm(), keyValue); } finally { if (!encryption) { Arrays.fill(keyValue, (byte) 0); } } } @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.
Params:
  • rand – a SecureRandom object. If null is provided a new SecureRandom object will be instantiated.
Returns:a 16-byte array containing the random nonce.
/** * 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) { // 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)) { n--; } } private static final VarHandle wrapToByteArray = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); private static byte[] getLengthBlock(int ivLenInBytes) { byte[] out = new byte[16]; wrapToByteArray.set(out, 8, ((long)ivLenInBytes & 0xFFFFFFFFL) << 3); return out; } private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) { 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, int blockSize) { if (len > blockSize) { throw new ProviderException("input " + len + " too long"); } if (len == blockSize && inOfs == 0) { return in; } else { byte[] paddedIn = new byte[blockSize]; System.arraycopy(in, inOfs, paddedIn, 0, len); return paddedIn; } } private static byte[] getJ0(byte[] iv, byte[] subkeyH, int blockSize) { byte[] j0; if (iv.length == 12) { // 96 bits j0 = expandToOneBlock(iv, 0, iv.length, blockSize); j0[blockSize - 1] = 1; } else { GHASH g = new GHASH(subkeyH); int lastLen = iv.length % blockSize; if (lastLen != 0) { g.update(iv, 0, iv.length - lastLen); byte[] padded = expandToOneBlock(iv, iv.length - lastLen, lastLen, blockSize); g.update(padded); } else { g.update(iv); } g.update(getLengthBlock(iv.length)); j0 = g.digest(); } return j0; } // Wrapper function around AES-GCM interleaved intrinsic that splits // large chunks of data into 1MB sized chunks. This is to place // an upper limit on the number of blocks encrypted in the intrinsic. private static int implGCMCrypt(byte[] in, int inOfs, int inLen, byte[] ct, int ctOfs, byte[] out, int outOfs, GCTR gctr, GHASH ghash) { int len = 0; if (inLen > SPLIT_LEN) { while (inLen >= SPLIT_LEN) { int partlen = implGCMCrypt0(in, inOfs + len, SPLIT_LEN, ct, ctOfs + len, out, outOfs + len, gctr, ghash); len += partlen; inLen -= partlen; } } if (inLen > 0) { len += implGCMCrypt0(in, inOfs + len, inLen, ct, ctOfs + len, out, outOfs + len, gctr, ghash); } return len; }
Intrinsic for Vector AES Galois Counter Mode implementation. AES and GHASH operations are interleaved in the intrinsic implementation. return - number of processed bytes Requires 768 bytes (48 AES blocks) to efficiently use the intrinsic. inLen that is less than 768 size block sizes, before or after this intrinsic is used, will be done by the calling method
Params:
  • in – input buffer
  • inOfs – input offset
  • inLen – input length
  • ct – buffer that ghash will read (in for encrypt, out for decrypt)
  • ctOfs – offset for ct buffer
  • out – output buffer
  • outOfs – output offset
  • gctr – object for the GCTR operation
  • ghash – object for the ghash operation
Returns:number of processed bytes
/** * Intrinsic for Vector AES Galois Counter Mode implementation. * AES and GHASH operations are interleaved in the intrinsic implementation. * return - number of processed bytes * * Requires 768 bytes (48 AES blocks) to efficiently use the intrinsic. * inLen that is less than 768 size block sizes, before or after this * intrinsic is used, will be done by the calling method * @param in input buffer * @param inOfs input offset * @param inLen input length * @param ct buffer that ghash will read (in for encrypt, out for decrypt) * @param ctOfs offset for ct buffer * @param out output buffer * @param outOfs output offset * @param gctr object for the GCTR operation * @param ghash object for the ghash operation * @return number of processed bytes */
@IntrinsicCandidate private static int implGCMCrypt0(byte[] in, int inOfs, int inLen, byte[] ct, int ctOfs, byte[] out, int outOfs, GCTR gctr, GHASH ghash) { inLen -= (inLen % PARALLEL_LEN); int len = 0; int cOfs = ctOfs; if (inLen >= TRIGGERLEN) { int i = 0; int segments = (inLen / 6); segments -= segments % gctr.blockSize; do { len += gctr.update(in, inOfs + len, segments, out, outOfs + len); ghash.update(ct, cOfs, segments); cOfs = ctOfs + len; } while (++i < 5); inLen -= len; } len += gctr.update(in, inOfs + len, inLen, out, outOfs + len); ghash.update(ct, cOfs, inLen); return len; }
Abstract class for GCMEncrypt and GCMDecrypt internal context objects
/** * Abstract class for GCMEncrypt and GCMDecrypt internal context objects */
abstract class GCMEngine { byte[] preCounterBlock; GCTR gctr; GHASH ghash; // Block size of the algorithm final int blockSize; // 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); gctr = new GCTR(blockCipher, j0Plus1); ghash = new GHASH(subkeyH); }
Get output buffer size
Params:
  • inLen – Contains the length of the input data and buffered data.
  • isFinal – true if this is a doFinal operation
Returns:If it's an update operation, inLen must blockSize divisible. If it's a final operation, output will include the tag.
/** * 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); // Update operations abstract byte[] doUpdate(byte[] in, int inOfs, int inLen); abstract int doUpdate(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) throws ShortBufferException; abstract int doUpdate(ByteBuffer src, ByteBuffer dst) throws ShortBufferException; // Final operations abstract int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) 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); } } // Helper method for getting ibuffer size int getBufferedLength() { return (ibuffer == null ? 0 : ibuffer.size()); }
ByteBuffer wrapper for intrinsic implGCMCrypt. It will operate on 768 byte blocks and let the calling method operate on smaller sizes.
/** * ByteBuffer wrapper for intrinsic implGCMCrypt. It will operate * on 768 byte blocks and let the calling method operate on smaller * sizes. */
int implGCMCrypt(ByteBuffer src, ByteBuffer dst) { int srcLen = src.remaining() - (src.remaining() % PARALLEL_LEN); if (srcLen < PARALLEL_LEN) { return 0; } int len; if (src.hasArray() && dst.hasArray()) { ByteBuffer ct = (encryption ? dst : src); len = GaloisCounterMode.implGCMCrypt(src.array(), src.arrayOffset() + src.position(), srcLen, ct.array(), ct.arrayOffset() + ct.position(), dst.array(), dst.arrayOffset() + dst.position(), gctr, ghash); src.position(src.position() + len); dst.position(dst.position() + len); return len; } else { byte[] bin = new byte[PARALLEL_LEN]; byte[] bout = new byte[PARALLEL_LEN]; byte[] ct = (encryption ? bout : bin); len = srcLen; do { src.get(bin, 0, PARALLEL_LEN); len -= GaloisCounterMode.implGCMCrypt(bin, 0, PARALLEL_LEN, ct, 0, bout, 0, gctr, ghash); dst.put(bout, 0, PARALLEL_LEN); } while (len >= PARALLEL_LEN); return srcLen - len; } }
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
Returns:number of bytes used from 'in'
/** * 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); }
The method takes two buffers to create one block of data This is only called when buffer length is less than a blockSize
Returns:number of bytes used from 'in'
/** * 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. All AAD must be supplied before beginning operations on the ciphertext (via the update and doFinal methods).
Params:
  • src – the buffer containing the AAD
  • offset – the offset in src where the AAD input starts
  • 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 update methods has already been called for the active encryption/decryption operation
/** * 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 */
void updateAAD(byte[] src, int offset, int len) { if (encryption) { checkReInit(); } if (aadBuffer == null) { if (sizeOfAAD == 0 && !aadProcessed) { aadBuffer = new ByteArrayOutputStream(len); } else { // update has already been called throw new IllegalStateException ("Update has been called; no more AAD data"); } } aadBuffer.write(src, offset, len); } // 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 % blockSize; if (lastLen != 0) { ghash.update(aad, 0, aad.length - lastLen); byte[] padded = expandToOneBlock(aad, aad.length - lastLen, lastLen, blockSize); ghash.update(padded); } else { ghash.update(aad); } } aadBuffer = null; } aadProcessed = true; }
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.
/** * 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(GCMOperation op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) { int len = 0; int resultLen; int bLen = (buffer != null ? buffer.remaining() : 0); if (bLen > 0) { // en/decrypt any PARALLEL_LEN sized data in the buffer if (bLen >= PARALLEL_LEN) { len = implGCMCrypt(buffer, dst); bLen -= len; } // en/decrypt any blocksize data in the buffer if (bLen >= blockSize) { resultLen = op.update(buffer, dst); bLen -= resultLen; len += resultLen; } // Process the remainder in the buffer if (bLen > 0) { // Copy the buffer remainder into an extra block byte[] block = new byte[blockSize]; int over = buffer.remaining(); buffer.get(block, 0, over); // If src has data, complete the block; int slen = Math.min(src.remaining(), blockSize - over); if (slen > 0) { src.get(block, over, slen); } int l = slen + over; if (l == blockSize) { len += op.update(block, 0, blockSize, dst); } else { len += op.doFinal(block, 0, l, block,0); if (dst != null) { dst.put(block, 0, l); } return len; } } } // en/decrypt whatever remains in src. // If src has been consumed, this will be a no-op if (src.remaining() >= PARALLEL_LEN) { len += implGCMCrypt(src, dst); } return len + op.doFinal(src, dst); }
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.
/** * 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() != dst.array()) { return dst; } // Position plus arrayOffset() will give us the true offset // from the underlying byte[] address. // If during encryption and the input offset is behind or // the same as the output offset, the same buffer can be // used. But during decryption always create a new // buffer in case of a bad auth tag. if (encryption && 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; }
This is used for both overlap detection for the data or decryption during in-place crypto, so to not overwrite the input if the auth tag is invalid. If an intermediate array is needed, the original out array length is allocated because for code simplicity.
/** * This is used for both overlap detection for the data or decryption * during in-place crypto, so to not overwrite the input if the auth tag * is invalid. * * 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 && (!encryption || 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
/** * 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[];
/** * 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
/** * Encryption Engine object */
class GCMEncrypt extends GCMEngine { GCMOperation op; // data processed during encryption int processed = 0; GCMEncrypt(SymmetricCipher blockCipher) { super(blockCipher); op = new EncryptOp(gctr, ghash); }
Calculate if the given data lengths and the already processed data exceeds the maximum allowed processed data by GCM.
Params:
  • lengths – lengths of unprocessed data.
/** * Calculate if the given data lengths and the already processed data * exceeds the maximum allowed processed data by GCM. * @param lengths lengths of unprocessed data. */
private void checkDataLength(int ... lengths) { int max = MAX_BUF_SIZE; for (int len : lengths) { max = Math.subtractExact(max, len); if (processed > max) { throw new ProviderException("SunJCE provider only " + "supports input size up to " + MAX_BUF_SIZE + " bytes"); } } } @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.
/** * 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 = op.update(block, 0, blockSize, out, outOfs); inOfs += remainder; inLen -= remainder; outOfs += blockSize; ibuffer.reset(); } } // Encrypt the remaining blocks inside of 'in' if (inLen >= PARALLEL_LEN) { int r = GaloisCounterMode.implGCMCrypt(in, inOfs, inLen, out, outOfs, out, outOfs, gctr, ghash); len += r; inOfs += r; inLen -= r; outOfs += r; } if (inLen >= blockSize) { int r = op.update(in, inOfs, inLen, out, outOfs); len += r; inOfs += r; inLen -= r; } // 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.
/** * 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 += op.update(ByteBuffer.wrap(block, 0, blockSize), dst); ibuffer.reset(); } } int srcLen = src.remaining(); int resultLen; // encrypt any PARALLEL_LEN sized data in 'src' if (srcLen >= PARALLEL_LEN) { resultLen = implGCMCrypt(src, dst); srcLen -= resultLen; len += resultLen; } // encrypt any blocksize data in 'src' if (srcLen >= blockSize) { resultLen = op.update(src, dst); srcLen -= resultLen; len += resultLen; } // Write the remaining bytes into the 'ibuffer' if (srcLen > 0) { initBuffer(srcLen); byte[] b = new byte[srcLen]; src.get(b); // remainder offset is based on original buffer length try { ibuffer.write(b); } catch (IOException e) { throw new RuntimeException(e); } } restoreDst(dst); processed += len; return len; }
Return final encrypted data with auth tag using byte[]
/** * 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 e) { throw new ShortBufferException("Output buffer invalid"); } int bLen = getBufferedLength(); checkDataLength(inLen, bLen, tagLenBytes); processAAD(); out = overlapDetection(in, inOfs, out, outOfs); int len = 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; block = new byte[blockSize]; r = mergeBlock(buffer, 0, in, inOfs, inLen, block); inOfs += r; inLen -= r; op.update(block, 0, blockSize, out, outOfs); outOfs += blockSize; len += blockSize; } else { // Need to consume 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; } } // process what is left in the input buffer len += op.doFinal(in, inOfs, inLen, out, outOfs); outOfs += inLen; block = getLengthBlock(sizeOfAAD, processed + len); ghash.update(block); block = ghash.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); len += tagLenBytes; restoreOut(out, len); reInit = true; return len; }
Return final encrypted data with auth tag using bytebuffers
/** * 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(); // '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"); } processAAD(); if (len > 0) { processed += doLastBlock(op, (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); ghash.update(block); block = ghash.digest(); new GCTR(blockCipher, preCounterBlock).doFinal(block, 0, tagLenBytes, block, 0); dst.put(block, 0, tagLenBytes); restoreDst(dst); reInit = true; return (len + tagLenBytes); } }
Decryption Engine object
/** * Decryption Engine object */
class GCMDecrypt extends GCMEngine { // byte array of tag byte[] tag; // offset for byte[] operations int tagOfs = 0; GCMDecrypt(SymmetricCipher blockCipher) { super(blockCipher); }
Calculate if the given data lengths exceeds the maximum allowed processed data by GCM.
Params:
  • lengths – lengths of unprocessed data.
/** * Calculate if the given data lengths exceeds the maximum allowed * processed data by GCM. * @param lengths lengths of unprocessed data. */
private void checkDataLength(int ... lengths) { int max = MAX_BUF_SIZE; for (int len : lengths) { max = Math.subtractExact(max, len); if (max < 0) { throw new ProviderException("SunJCE provider only " + "supports input size up to " + MAX_BUF_SIZE + " bytes"); } } } @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.
/** * 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 doFinal. Per the spec, data is // returned after tag is successfully verified. initBuffer(inLen); ibuffer.write(in, inOfs, inLen); } return 0; } // 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 available data from ibuffer and 'in' to verify and decrypt the data. If the verification fails, the 'out' left to it's original values if crypto was in-place; otherwise 'out' is zeroed
/** * Use available data from ibuffer and 'in' to verify and decrypt the * data. If the verification fails, the 'out' left to it's original * values if crypto was in-place; otherwise 'out' is zeroed */
@Override public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) throws IllegalBlockSizeException, AEADBadTagException, ShortBufferException { int len = inLen + getBufferedLength(); if (len < tagLenBytes) { throw new AEADBadTagException("Input data too short to " + "contain an expected tag length of " + tagLenBytes + "bytes"); } try { ArrayUtil.nullAndBoundsCheck(out, outOfs, len - tagLenBytes); } catch (ArrayIndexOutOfBoundsException e) { throw new ShortBufferException("Output buffer invalid"); } if (len - tagLenBytes > out.length - outOfs) { throw new ShortBufferException("Output buffer too small, must" + "be at least " + (len - tagLenBytes) + " bytes long"); } checkDataLength(len - tagLenBytes); processAAD(); findTag(in, inOfs, inLen); out = overlapDetection(in, inOfs, out, outOfs); len = decryptBlocks(new DecryptOp(gctr, ghash), in, inOfs, inLen, out, outOfs); byte[] block = getLengthBlock(sizeOfAAD, len); ghash.update(block); block = ghash.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) { // Clear output data Arrays.fill(out, outOfs, outOfs + len, (byte) 0); throw new AEADBadTagException("Tag mismatch"); } restoreOut(out, len); return len; }
Use available data from ibuffer and 'src' to verify and decrypt the data. If the verification fails, the 'dst' left to it's original values if crypto was in-place; otherwise 'dst' is zeroed
/** * Use available data from ibuffer and 'src' to verify and decrypt the * data. If the verification fails, the 'dst' left to it's original * values if crypto was in-place; otherwise 'dst' is zeroed */
@Override public int doFinal(ByteBuffer src, ByteBuffer dst) throws IllegalBlockSizeException, AEADBadTagException, ShortBufferException { 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); // Verify dst is large enough if (len > dst.remaining()) { throw new ShortBufferException("Output buffer too small, " + "must be at least " + len + " bytes long"); } // 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 data too short to " + "contain an expected tag length of " + tagLenBytes + "bytes"); } dst = overlapDetection(src, dst); dst.mark(); processAAD(); len = doLastBlock(new DecryptOp(gctr, ghash), buffer, ct, dst); byte[] block = getLengthBlock(sizeOfAAD, len); ghash.update(block); block = ghash.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) { // Clear output data dst.reset(); if (dst.hasArray()) { int ofs = dst.arrayOffset() + dst.position(); Arrays.fill(dst.array(), ofs , ofs + len, (byte)0); } else { Unsafe.getUnsafe().setMemory(((DirectBuffer)dst).address(), len + dst.position(), (byte)0); } throw new AEADBadTagException("Tag mismatch"); } src.position(src.limit()); engine = null; restoreDst(dst); return len; }
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'.
/** * 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(GCMOperation op, byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { byte[] buffer; byte[] block; int len = 0; int resultLen; // 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 >= PARALLEL_LEN) { len = GaloisCounterMode.implGCMCrypt(buffer, 0, bLen, buffer, 0, out, outOfs, gctr, ghash); outOfs += len; // Use len as it becomes the ibuffer offset, if // needed, in the next op } int bufRemainder = bLen - len; if (bufRemainder >= blockSize) { resultLen = op.update(buffer, len, bufRemainder, out, outOfs); len += resultLen; outOfs += resultLen; bufRemainder -= resultLen; } // merge the remaining ibuffer with the 'in' 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) { resultLen = op.update(block, 0, blockSize, out, outOfs); outOfs += resultLen; len += resultLen; } else { in = block; inOfs = 0; inLen = inUsed + bufRemainder; } } } 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()); } }
This class is for encryption when both GCTR and GHASH can operation in parallel.
/** * This class is for encryption when both GCTR and GHASH * can operation in parallel. */
static final class EncryptOp implements GCMOperation { GCTR gctr; GHASH ghash; EncryptOp(GCTR c, GHASH g) { gctr = c; ghash = g; } @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; } @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 = 0; if (inLen >= PARALLEL_LEN) { len = implGCMCrypt(in, inOfs, inLen, out, outOfs, out, outOfs, gctr, ghash); inLen -= len; outOfs += len; } gctr.doFinal(in, inOfs + len, inLen, out, outOfs); return len + ghash.doFinal(out, outOfs, inLen); } @Override public int doFinal(ByteBuffer src, ByteBuffer dst) { dst.mark(); int len = gctr.doFinal(src, dst); dst.reset(); ghash.doFinal(dst, len); return len; } }
This class is for decryption when both GCTR and GHASH can operation in parallel.
/** * This class is for decryption when both GCTR and GHASH * can operation in parallel. */
static final class DecryptOp implements GCMOperation { GCTR gctr; GHASH ghash; DecryptOp(GCTR c, GHASH g) { gctr = c; ghash = g; } @Override public int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { ghash.update(in, inOfs, inLen); return gctr.update(in, inOfs, inLen, out, outOfs); } @Override public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) { ghash.update(in, inOfs, inLen); return gctr.update(in, inOfs, inLen, dst); } @Override public int update(ByteBuffer src, ByteBuffer dst) { src.mark(); ghash.update(src, src.remaining()); src.reset(); return gctr.update(src, dst); } @Override public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { int len = 0; if (inLen >= PARALLEL_LEN) { len += implGCMCrypt(in, inOfs, inLen, in, inOfs, out, outOfs, gctr, ghash); } ghash.doFinal(in, inOfs + len, inLen - len); return len + gctr.doFinal(in, inOfs + len, inLen - len, out, outOfs + len); } @Override public int doFinal(ByteBuffer src, ByteBuffer dst) { src.mark(); ghash.doFinal(src, src.remaining()); src.reset(); return gctr.doFinal(src, dst); } }
Interface to organize encryption and decryption operations in the proper order for GHASH and GCTR.
/** * Interface to organize encryption and decryption operations in the * proper order for GHASH and GCTR. */
public interface GCMOperation { 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); } }