All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.crypto.provider.GaloisCounterMode Maven / Gradle / Ivy

/*
 * 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 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;

/**
 * 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 */ 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 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; /** * * @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. */ 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. * * @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; } /** * 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 (engine.processed > max) { throw new ProviderException("SunJCE provider only supports " + "input size up to " + MAX_BUF_SIZE + " bytes"); } } /** * Abstract class for GCMEncrypt and GCMDecrypt internal context objects */ abstract class GCMEngine { byte[] preCounterBlock; GCTR gctrPAndC; GHASH ghashAllToS; // 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); } /** * 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 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); } } // Helper method for getting ibuffer size int getBufferedLength() { return (ibuffer == null ? 0 : ibuffer.size()); } /** * 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 * @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 * {@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(); } 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) { ghashAllToS.update(aad, 0, aad.length - lastLen); byte[] padded = expandToOneBlock(aad, aad.length - lastLen, lastLen, blockSize); ghashAllToS.update(padded); } else { ghashAllToS.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. */ int doLastBlock(GCM op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) { int resultLen = 0; 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); } // 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(); 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 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() != 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 { // 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; } } // 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; } /** * 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(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); } // 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; } } /** * 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); } @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; } // 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()); } } /** * This class is for encryption when both GCTR and GHASH * can operation in parallel. */ static final class GCTRGHASH implements GCM { GCTR gctr; GHASH ghash; GCTRGHASH(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 = 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; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy