/* * Copyright (c) 2013, 2022, 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.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; 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 */ 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 = 7680; // max data size for x86-64 intrinsic private static final int SPLIT_LEN = 1048576; // 1MB static final byte[] EMPTY_BUF = new byte[0]; private static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess(); 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; } // 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 * @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 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 * @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. */ 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 * @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 */ 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. */ 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. */ ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) { if (src.isDirect() && dst.isDirect()) { // The use of DirectBuffer::address below need not be guarded as // no access is made to actual memory. 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. */ 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 */ 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 { 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. * @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. */ @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. */ @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[] */ @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 */ @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 */ 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. * @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. */ 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 */ @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 */ @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 { NIO_ACCESS.acquireSession(dst); try { Unsafe.getUnsafe().setMemory(((DirectBuffer)dst).address(), len + dst.position(), (byte) 0); } finally { NIO_ACCESS.releaseSession(dst); } } 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'. */ 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. */ 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. */ 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. */ 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); } }