| /* | |
| * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.sun.crypto.provider; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.VarHandle; | |
| import java.nio.Buffer; | |
| import java.nio.ByteBuffer; | |
| import java.nio.ByteOrder; | |
| import java.security.*; | |
| import java.security.spec.AlgorithmParameterSpec; | |
| import java.security.spec.InvalidParameterSpecException; | |
| import java.util.Arrays; | |
| import javax.crypto.*; | |
| import javax.crypto.spec.GCMParameterSpec; | |
| import jdk.internal.access.JavaNioAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.misc.Unsafe; | |
| import jdk.internal.vm.annotation.IntrinsicCandidate; | |
| import sun.nio.ch.DirectBuffer; | |
| import sun.security.jca.JCAUtil; | |
| import sun.security.util.ArrayUtil; | |
| /** | |
| * 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 { | |
| private static final int DEFAULT_IV_LEN = 12; // in bytes | |
| private static final 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 = 512; | |
| // 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; | |
| final 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. | |
| private final 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); // iv.clone() not necessary | |
| try { | |
| AlgorithmParameters params = | |
| AlgorithmParameters.getInstance("GCM", | |
| SunJCE.getInstance()); | |
| params.init(spec); | |
| return params; | |
| } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { | |
| throw new ProviderException(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 Combined AES-GCM intrinsic method 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. | |
| * | |
| * The combined intrinsic is not used when decrypting in-place heap | |
| * bytebuffers because 'ct' will be the same as 'in' and overwritten by | |
| * GCTR before GHASH calculates the encrypted tag. | |
| */ | |
| 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; | |
| // Loop if input length is greater than the SPLIT_LEN | |
| if (inLen > SPLIT_LEN && ct != null) { | |
| int partlen; | |
| while (inLen >= SPLIT_LEN) { | |
| partlen = implGCMCrypt0(in, inOfs + len, SPLIT_LEN, ct, | |
| ctOfs + len, out, outOfs + len, gctr, ghash); | |
| len += partlen; | |
| inLen -= partlen; | |
| } | |
| } | |
| // Finish any remaining data | |
| if (inLen > 0) { | |
| if (ct == null) { | |
| ghash.update(in, inOfs + len, inLen); | |
| len += gctr.update(in, inOfs + len, inLen, out, outOfs); | |
| } else { | |
| len += implGCMCrypt0(in, inOfs + len, inLen, ct, | |
| ctOfs + len, out, outOfs + len, gctr, ghash); | |
| } | |
| } | |
| return len; | |
| } | |
| /** | |
| * Intrinsic for the combined AES Galois Counter Mode implementation. | |
| * AES and GHASH operations are combined in the intrinsic implementation. | |
| * | |
| * Requires PARALLEN_LEN bytes to efficiently use the intrinsic. | |
| * The intrinsic returns the number of bytes processed. | |
| * The remaining bytes will be processed by the calling method. | |
| * | |
| * Note: | |
| * Intel processors with AVX2 support and above trigger this intrinsic. | |
| * Some AARCH64 processors also trigger this intrinsic. | |
| * Other processors will always use GHASH and GCTR which may have their own | |
| * intrinsic support | |
| * | |
| * @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 | |
| AEADBufferedStream aadBuffer = null; | |
| int sizeOfAAD = 0; | |
| boolean aadProcessed = false; | |
| // buffer data for crypto operation | |
| AEADBufferedStream ibuffer = null; | |
| // Original dst buffer if there was an overlap situation | |
| ByteBuffer originalDst = null; | |
| byte[] originalOut = null; | |
| int originalOutOfs = 0; | |
| // True if ops is an in-place array decryption with the offset between | |
| // input & output the same or the input greater. This is to | |
| // avoid the AVX512 intrinsic. | |
| boolean inPlaceArray = false; | |
| 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 AEADBufferedStream(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, | |
| inPlaceArray ? null : 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 | |
| * | |
| * 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 ProviderException("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 multipart 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 AEADBufferedStream(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) { | |
| sizeOfAAD = aadBuffer.size(); | |
| if (sizeOfAAD > 0) { | |
| byte[] aad = aadBuffer.getBuffer(); | |
| int lastLen = sizeOfAAD % blockSize; | |
| if (lastLen != 0) { | |
| ghash.update(aad, 0, sizeOfAAD - lastLen); | |
| byte[] padded = expandToOneBlock(aad, | |
| sizeOfAAD - lastLen, lastLen, blockSize); | |
| ghash.update(padded); | |
| } else { | |
| ghash.update(aad, 0, sizeOfAAD); | |
| } | |
| } | |
| 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 addresses 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 = NIO_ACCESS.getBufferAddress(src); | |
| long dstaddr = NIO_ACCESS.getBufferAddress(dst); | |
| // Find the lowest attachment that is the base memory address | |
| // of the shared memory for the src object | |
| while (dsrc.attachment() != null) { | |
| srcaddr = NIO_ACCESS.getBufferAddress((Buffer) dsrc.attachment()); | |
| 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 = NIO_ACCESS.getBufferAddress((Buffer) ddst.attachment()); | |
| 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. | |
| // NOTE: inPlaceArray does not apply here as direct buffers run | |
| // through a byte[] to get to the combined intrinsic | |
| if (NIO_ACCESS.getBufferAddress(src) - srcaddr + src.position() >= | |
| NIO_ACCESS.getBufferAddress(dst) - 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. | |
| // Set 'inPlaceArray' true for decryption operations to | |
| // avoid the AVX512 combined intrinsic | |
| if (src.position() + src.arrayOffset() >= | |
| dst.position() + dst.arrayOffset()) { | |
| inPlaceArray = (!encryption); | |
| 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) { | |
| if (inOfs < outOfs) { | |
| originalOut = out; | |
| originalOutOfs = outOfs; | |
| return new byte[out.length]; | |
| } | |
| inPlaceArray = (!encryption); | |
| } | |
| 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.getBuffer(); | |
| // 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 '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.getBuffer(), | |
| 0, ibuffer.size()); | |
| 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); | |
| // remainder offset is based on original buffer length | |
| ibuffer.write(src); | |
| } | |
| 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.getBuffer(); | |
| // 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, ibuffer.size(), 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.getBuffer(), 0, | |
| ibuffer.size()), 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.getBuffer(); | |
| int ofs = ibuffer.size() - (tagLenBytes - inLen); | |
| tagOfs = mergeBlock(buffer, ofs, ibuffer.size() - ofs, | |
| 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 { | |
| initBuffer(src.remaining()); | |
| ibuffer.write(src); | |
| } | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Use available data from ibuffer and 'in' to verify and decrypt the | |
| * data. If the verification fails, the 'out' left to its 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) { | |
| // If this is an in-place array, don't zero the input | |
| if (!inPlaceArray) { | |
| // 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 its 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.getBuffer(), 0, | |
| ibuffer.size()); | |
| len += buffer.remaining(); | |
| } | |
| // Check that input data is long enough to fit the expected tag. | |
| if (len < 0) { | |
| throw new AEADBadTagException("Input data too short to " + | |
| "contain an expected tag length of " + tagLenBytes + | |
| "bytes"); | |
| } | |
| 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 this is an in-place array, don't zero the src | |
| if (!inPlaceArray) { | |
| 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( | |
| NIO_ACCESS.getBufferAddress(dst), | |
| 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.getBuffer(); | |
| 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) { | |
| // Since GCMDecrypt.inPlaceArray cannot be accessed, check that | |
| // 'in' and 'out' are the same. All other in-place situations | |
| // have been resolved by overlapDetection() | |
| len += implGCMCrypt(in, inOfs, inLen, (in == out ? null : 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); | |
| } | |
| } |