mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 14:24:46 +02:00
1838 lines
64 KiB
Java
1838 lines
64 KiB
Java
/*
|
|
* 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.
|
|
*
|
|
* <p>This mode currently should only be used w/ AES cipher.
|
|
* Although no checking is done, caller should only pass AES
|
|
* Cipher to the constructor.
|
|
*
|
|
* <p>NOTE: Unlike other modes, when used for decryption, this class
|
|
* will buffer all processed outputs internally and won't return them
|
|
* until the tag has been successfully verified.
|
|
*
|
|
* @since 1.8
|
|
*/
|
|
abstract class GaloisCounterMode extends CipherSpi {
|
|
static int DEFAULT_IV_LEN = 12; // in bytes
|
|
static int DEFAULT_TAG_LEN = 16; // in bytes
|
|
// In NIST SP 800-38D, GCM input size is limited to be no longer
|
|
// than (2^36 - 32) bytes. Otherwise, the counter will wrap
|
|
// around and lead to a leak of plaintext.
|
|
// However, given the current GCM spec requirement that recovered
|
|
// text can only be returned after successful tag verification,
|
|
// we are bound by limiting the data size to the size limit of
|
|
// java byte array, e.g. Integer.MAX_VALUE, since all data
|
|
// can only be returned by the doFinal(...) call.
|
|
private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
|
|
// data size when buffer is divided up to aid in intrinsics
|
|
private static final int TRIGGERLEN = 65536; // 64k
|
|
// x86-64 parallel intrinsic data size
|
|
private static final int PARALLEL_LEN = 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);
|
|
}
|
|
}
|