8267125: AES Galois CounterMode (GCM) interleaved implementation using AVX512 + VAES instructions

Co-authored-by: Smita Kamath <svkamath@openjdk.org>
Co-authored-by: Tomasz Kantecki <tomasz.kantecki@intel.com>
Co-authored-by: Anthony Scarpino <ascarpino@openjdk.org>
Reviewed-by: kvn, valeriep
This commit is contained in:
Smita Kamath 2021-08-24 18:48:31 +00:00 committed by Anthony Scarpino
parent 6ace805f8c
commit 0e7288ffbf
21 changed files with 1318 additions and 246 deletions

View file

@ -122,7 +122,7 @@ final class GHASH implements Cloneable, GCM {
/* subkeyHtbl and state are stored in long[] for GHASH intrinsic use */
// hashtable subkeyHtbl holds 2*9 powers of subkeyH computed using
// hashtable subkeyHtbl holds 2*57 powers of subkeyH computed using
// carry-less multiplication
private long[] subkeyHtbl;
@ -143,7 +143,9 @@ final class GHASH implements Cloneable, GCM {
throw new ProviderException("Internal error");
}
state = new long[2];
subkeyHtbl = new long[2*9];
// 48 keys for the interleaved implementation,
// 8 for avx-ghash implementation and 1 for the original key
subkeyHtbl = new long[2*57];
subkeyHtbl[0] = (long)asLongView.get(subkeyH, 0);
subkeyHtbl[1] = (long)asLongView.get(subkeyH, 8);
}
@ -264,7 +266,7 @@ final class GHASH implements Cloneable, GCM {
throw new RuntimeException("internal state has invalid length: " +
st.length);
}
if (subH.length != 18) {
if (subH.length != 114) {
throw new RuntimeException("internal subkeyHtbl has invalid length: " +
subH.length);
}

View file

@ -25,6 +25,7 @@
package com.sun.crypto.provider;
import jdk.internal.misc.Unsafe;
import sun.nio.ch.DirectBuffer;
import sun.security.jca.JCAUtil;
import sun.security.util.ArrayUtil;
@ -55,6 +56,8 @@ 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.
*
@ -82,6 +85,8 @@ abstract class GaloisCounterMode extends CipherSpi {
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 = 768;
static final byte[] EMPTY_BUF = new byte[0];
@ -566,35 +571,64 @@ abstract class GaloisCounterMode extends CipherSpi {
}
/**
* 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.
* 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
*/
private void checkDataLength(int ... lengths) {
int max = MAX_BUF_SIZE;
for (int len : lengths) {
max = Math.subtractExact(max, len);
}
if (engine.processed > max) {
throw new ProviderException("SunJCE provider only supports " +
"input size up to " + MAX_BUF_SIZE + " bytes");
@IntrinsicCandidate
private static int implGCMCrypt(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 gctrPAndC;
GHASH ghashAllToS;
GCTR gctr;
GHASH ghash;
// Block size of the algorithm
final int blockSize;
// length of total data, i.e. len(C)
int processed = 0;
// buffer for AAD data; if null, meaning update has been called
ByteArrayOutputStream aadBuffer = null;
int sizeOfAAD = 0;
@ -608,7 +642,6 @@ abstract class GaloisCounterMode extends CipherSpi {
byte[] originalOut = null;
int originalOutOfs = 0;
GCMEngine(SymmetricCipher blockCipher) {
blockSize = blockCipher.getBlockSize();
byte[] subkeyH = new byte[blockSize];
@ -616,8 +649,8 @@ abstract class GaloisCounterMode extends CipherSpi {
preCounterBlock = getJ0(iv, subkeyH, blockSize);
byte[] j0Plus1 = preCounterBlock.clone();
increment32(j0Plus1);
gctrPAndC = new GCTR(blockCipher, j0Plus1);
ghashAllToS = new GHASH(subkeyH);
gctr = new GCTR(blockCipher, j0Plus1);
ghash = new GHASH(subkeyH);
}
/**
@ -631,15 +664,15 @@ abstract class GaloisCounterMode extends CipherSpi {
abstract int getOutputSize(int inLen, boolean isFinal);
// Update operations
abstract byte[] doUpdate(byte[] in, int inOff, int inLen);
abstract int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
int outOff) throws ShortBufferException;
abstract 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 inOff, int inLen, byte[] out,
int outOff) throws IllegalBlockSizeException, AEADBadTagException,
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,
@ -657,6 +690,48 @@ abstract class GaloisCounterMode extends CipherSpi {
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
@ -704,8 +779,6 @@ abstract class GaloisCounterMode extends CipherSpi {
* (e.g., has not been initialized) or does not accept AAD, and one of
* the {@code update} methods has already been called for the active
* encryption/decryption operation
* @throws UnsupportedOperationException if this method
* has not been overridden by an implementation
*/
void updateAAD(byte[] src, int offset, int len) {
if (encryption) {
@ -733,12 +806,12 @@ abstract class GaloisCounterMode extends CipherSpi {
int lastLen = aad.length % blockSize;
if (lastLen != 0) {
ghashAllToS.update(aad, 0, aad.length - lastLen);
ghash.update(aad, 0, aad.length - lastLen);
byte[] padded = expandToOneBlock(aad,
aad.length - lastLen, lastLen, blockSize);
ghashAllToS.update(padded);
ghash.update(padded);
} else {
ghashAllToS.update(aad);
ghash.update(aad);
}
}
aadBuffer = null;
@ -751,18 +824,28 @@ abstract class GaloisCounterMode extends CipherSpi {
* For input it takes the ibuffer which is wrapped in 'buffer' and 'src'
* from doFinal.
*/
int doLastBlock(GCM op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) {
int resultLen = 0;
int 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 on how much buffer there is in AES_BLOCK_SIZE
// 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);
resultLen = op.update(buffer, dst);
bLen -= resultLen;
len += resultLen;
}
// Process the remainder in the buffer
if (bLen - resultLen > 0) {
if (bLen > 0) {
// Copy the buffer remainder into an extra block
byte[] block = new byte[blockSize];
int over = buffer.remaining();
@ -773,76 +856,26 @@ abstract class GaloisCounterMode extends CipherSpi {
if (slen > 0) {
src.get(block, over, slen);
}
int len = slen + over;
if (len == blockSize) {
resultLen += op.update(block, 0, blockSize, dst);
int l = slen + over;
if (l == blockSize) {
len += op.update(block, 0, blockSize, dst);
} else {
resultLen += op.doFinal(block, 0, len, block,
0);
len += op.doFinal(block, 0, l, block,0);
if (dst != null) {
dst.put(block, 0, len);
dst.put(block, 0, l);
}
processed += resultLen;
return resultLen;
return len;
}
}
}
// en/decrypt whatever remains in src.
// If src has been consumed, this will be a no-op
if (src.remaining() > TRIGGERLEN) {
resultLen += throttleData(op, src, dst);
if (src.remaining() >= PARALLEL_LEN) {
len += implGCMCrypt(src, dst);
}
resultLen += op.doFinal(src, dst);
processed += resultLen;
return resultLen;
}
/**
* This segments large data into smaller chunks so hotspot will start
* using GCTR and GHASH intrinsics sooner. This is a problem for app
* and perf tests that only use large input sizes.
*/
int throttleData(GCM op, byte[] in, int inOfs, int inLen,
byte[] out, int outOfs) {
int segments = (inLen / 6);
segments -= segments % blockSize;
int len = 0;
int i = 0;
do {
len += op.update(in, inOfs + len, segments, out,outOfs + len);
} while (++i < 5);
len += op.update(in, inOfs + len, inLen - len, out, outOfs + len);
return len;
}
/**
* This segments large data into smaller chunks so hotspot will start
* using GCTR and GHASH intrinsics sooner. This is a problem for app
* and perf tests that only use large input sizes.
*/
int throttleData(GCM op, ByteBuffer src, ByteBuffer dst) {
int inLen = src.limit();
int segments = (src.remaining() / 6);
segments -= segments % blockSize;
int i = 0, resultLen = 0;
do {
src.limit(src.position() + segments);
resultLen += op.update(src, dst);
} while (++i < 5);
src.limit(inLen);
// If there is still at least a blockSize left
if (src.remaining() > blockSize) {
resultLen += op.update(src, dst);
}
return resultLen;
return len + op.doFinal(src, dst);
}
/**
@ -900,7 +933,11 @@ abstract class GaloisCounterMode extends CipherSpi {
// Position plus arrayOffset() will give us the true offset
// from the underlying byte[] address.
if (src.position() + src.arrayOffset() >=
// 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;
}
@ -923,12 +960,15 @@ abstract class GaloisCounterMode extends CipherSpi {
}
/**
* Overlap detection for data using byte array.
* 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 && inOfs < outOfs) {
if (in == out && (!encryption || inOfs < outOfs)) {
originalOut = out;
originalOutOfs = outOfs;
return new byte[out.length];
@ -969,11 +1009,31 @@ abstract class GaloisCounterMode extends CipherSpi {
* Encryption Engine object
*/
class GCMEncrypt extends GCMEngine {
GCTRGHASH gctrghash;
GCMOperation op;
// data processed during encryption
int processed = 0;
GCMEncrypt(SymmetricCipher blockCipher) {
super(blockCipher);
gctrghash = new GCTRGHASH(gctrPAndC, ghashAllToS);
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
@ -1034,7 +1094,7 @@ abstract class GaloisCounterMode extends CipherSpi {
System.arraycopy(buffer, 0, block, 0, bLen);
System.arraycopy(in, inOfs, block, bLen, remainder);
len = gctrghash.update(block, 0, blockSize, out, outOfs);
len = op.update(block, 0, blockSize, out, outOfs);
inOfs += remainder;
inLen -= remainder;
outOfs += blockSize;
@ -1043,8 +1103,20 @@ abstract class GaloisCounterMode extends CipherSpi {
}
// 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) {
len += gctrghash.update(in, inOfs, inLen, out, outOfs);
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.
@ -1089,21 +1161,32 @@ abstract class GaloisCounterMode extends CipherSpi {
ByteBuffer buffer = ByteBuffer.wrap(ibuffer.toByteArray());
buffer.get(block, 0, bLen);
src.get(block, bLen, remainder);
len += cryptBlocks(
ByteBuffer.wrap(block, 0, blockSize), dst);
len += op.update(ByteBuffer.wrap(block, 0, blockSize),
dst);
ibuffer.reset();
}
}
// encrypt any blocksized data in 'src'
if (src.remaining() >= blockSize) {
len += cryptBlocks(src, dst);
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 (src.remaining() > 0) {
initBuffer(src.remaining());
byte[] b = new byte[src.remaining()];
if (srcLen > 0) {
initBuffer(srcLen);
byte[] b = new byte[srcLen];
src.get(b);
// remainder offset is based on original buffer length
try {
@ -1114,6 +1197,7 @@ abstract class GaloisCounterMode extends CipherSpi {
}
restoreDst(dst);
processed += len;
return len;
}
@ -1127,7 +1211,7 @@ abstract class GaloisCounterMode extends CipherSpi {
try {
ArrayUtil.nullAndBoundsCheck(out, outOfs, getOutputSize(inLen,
true));
} catch (ArrayIndexOutOfBoundsException aiobe) {
} catch (ArrayIndexOutOfBoundsException e) {
throw new ShortBufferException("Output buffer invalid");
}
@ -1136,7 +1220,7 @@ abstract class GaloisCounterMode extends CipherSpi {
processAAD();
out = overlapDetection(in, inOfs, out, outOfs);
int resultLen = 0;
int len = 0;
byte[] block;
// process what is in the ibuffer
@ -1145,18 +1229,16 @@ abstract class GaloisCounterMode extends CipherSpi {
// Make a block if the remaining ibuffer and 'in' can make one.
if (bLen + inLen >= blockSize) {
int r, bufOfs = 0;
int r;
block = new byte[blockSize];
r = mergeBlock(buffer, bufOfs, in, inOfs, inLen, block);
r = mergeBlock(buffer, 0, in, inOfs, inLen, block);
inOfs += r;
inLen -= r;
r = gctrghash.update(block, 0, blockSize, out,
outOfs);
outOfs += r;
resultLen += r;
processed += r;
op.update(block, 0, blockSize, out, outOfs);
outOfs += blockSize;
len += blockSize;
} else {
// Need to consume all the ibuffer here to prepare for doFinal()
// 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);
@ -1167,28 +1249,18 @@ abstract class GaloisCounterMode extends CipherSpi {
}
// process what is left in the input buffer
if (inLen > TRIGGERLEN) {
int r = throttleData(gctrghash, in, inOfs, inLen, out, outOfs);
inOfs += r;
inLen -= r;
outOfs += r;
resultLen += r;
processed += r;
}
processed += gctrghash.doFinal(in, inOfs, inLen, out, outOfs);
len += op.doFinal(in, inOfs, inLen, out, outOfs);
outOfs += inLen;
resultLen += inLen;
block = getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(block);
block = ghashAllToS.digest();
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);
int len = resultLen + tagLenBytes;
len += tagLenBytes;
restoreOut(out, len);
reInit = true;
@ -1214,7 +1286,7 @@ abstract class GaloisCounterMode extends CipherSpi {
processAAD();
if (len > 0) {
processed += doLastBlock(gctrghash,
processed += doLastBlock(op,
(ibuffer == null || ibuffer.size() == 0) ? null :
ByteBuffer.wrap(ibuffer.toByteArray()), src, dst);
}
@ -1225,8 +1297,8 @@ abstract class GaloisCounterMode extends CipherSpi {
}
byte[] block = getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(block);
block = ghashAllToS.digest();
ghash.update(block);
block = ghash.digest();
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
tagLenBytes, block, 0);
dst.put(block, 0, tagLenBytes);
@ -1235,18 +1307,6 @@ abstract class GaloisCounterMode extends CipherSpi {
reInit = true;
return (len + tagLenBytes);
}
// Handler method for encrypting blocks
int cryptBlocks(ByteBuffer src, ByteBuffer dst) {
int len;
if (src.remaining() > TRIGGERLEN) {
len = throttleData(gctrghash, src, dst);
} else {
len = gctrghash.update(src, dst);
}
processed += len;
return len;
}
}
/**
@ -1262,6 +1322,22 @@ abstract class GaloisCounterMode extends CipherSpi {
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) {
@ -1311,9 +1387,8 @@ abstract class GaloisCounterMode extends CipherSpi {
processAAD();
if (inLen > 0) {
// store internally until decryptFinal is called because
// spec mentioned that only return recovered data after tag
// is successfully verified
// store internally until doFinal. Per the spec, data is
// returned after tag is successfully verified.
initBuffer(inLen);
ibuffer.write(in, inOfs, inLen);
}
@ -1350,38 +1425,43 @@ abstract class GaloisCounterMode extends CipherSpi {
}
/**
* Use any data from ibuffer and 'in' to first verify the auth tag. If
* the tag is valid, decrypt the data.
* 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 {
GHASH save = null;
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 aiobe) {
} catch (ArrayIndexOutOfBoundsException e) {
throw new ShortBufferException("Output buffer invalid");
}
if (len < tagLenBytes) {
throw new AEADBadTagException("Input too short - need tag");
}
if (len - tagLenBytes > out.length - outOfs) {
save = ghashAllToS.clone();
throw new ShortBufferException("Output buffer too small, must" +
"be at least " + (len - tagLenBytes) + " bytes long");
}
checkDataLength(len - tagLenBytes);
processAAD();
findTag(in, inOfs, inLen);
byte[] block = getLengthBlock(sizeOfAAD,
decryptBlocks(ghashAllToS, in, inOfs, inLen, null, 0));
ghashAllToS.update(block);
block = ghashAllToS.digest();
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);
@ -1392,30 +1472,24 @@ abstract class GaloisCounterMode extends CipherSpi {
}
if (mismatch != 0) {
throw new AEADBadTagException("Tag mismatch!");
// Clear output data
Arrays.fill(out, outOfs, outOfs + len, (byte) 0);
throw new AEADBadTagException("Tag mismatch");
}
if (save != null) {
ghashAllToS = save;
throw new ShortBufferException("Output buffer too small, must" +
"be at least " + (len - tagLenBytes) + " bytes long");
}
out = overlapDetection(in, inOfs, out, outOfs);
len = decryptBlocks(gctrPAndC, in, inOfs, inLen, out, outOfs);
restoreOut(out, len);
return len;
}
/**
* Use any data from ibuffer and 'src' to first verify the auth tag. If
* the tag is valid, decrypt the data.
* 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 {
GHASH save = null;
ByteBuffer tag;
ByteBuffer ct = src.duplicate();
@ -1432,11 +1506,10 @@ abstract class GaloisCounterMode extends CipherSpi {
checkDataLength(len);
// Save GHASH context to allow the tag to be checked even though
// the dst buffer is too short. Context will be restored so the
// method can be called again with the proper sized dst buffer.
// Verify dst is large enough
if (len > dst.remaining()) {
save = ghashAllToS.clone();
throw new ShortBufferException("Output buffer too small, " +
"must be at least " + len + " bytes long");
}
// Create buffer 'tag' that contains only the auth tag
@ -1459,20 +1532,19 @@ abstract class GaloisCounterMode extends CipherSpi {
tag.put(ct);
tag.flip();
} else {
throw new AEADBadTagException("Input too short - need tag");
throw new AEADBadTagException("Input data too short to " +
"contain an expected tag length of " + tagLenBytes +
"bytes");
}
// Set the mark for a later reset. Either it will be zero, or the
// tag buffer creation above will have consume some or all of it.
ct.mark();
dst = overlapDetection(src, dst);
dst.mark();
processAAD();
// Perform GHASH check on data
doLastBlock(ghashAllToS, buffer, ct, null);
len = doLastBlock(new DecryptOp(gctr, ghash), buffer, ct, dst);
byte[] block = getLengthBlock(sizeOfAAD, len);
ghashAllToS.update(block);
block = ghashAllToS.digest();
ghash.update(block);
block = ghash.digest();
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
tagLenBytes, block, 0);
@ -1483,32 +1555,22 @@ abstract class GaloisCounterMode extends CipherSpi {
}
if (mismatch != 0) {
throw new AEADBadTagException("Tag mismatch!");
// Clear output data
dst.reset();
if (dst.hasArray()) {
int ofs = dst.arrayOffset() + dst.position();
Arrays.fill(dst.array(), ofs , ofs + len, (byte)0);
} else {
Unsafe.getUnsafe().setMemory(((DirectBuffer)dst).address(),
len + dst.position(), (byte)0);
}
throw new AEADBadTagException("Tag mismatch");
}
if (save != null) {
ghashAllToS = save;
throw new ShortBufferException("Output buffer too small, must" +
" be at least " + len + " bytes long");
}
// Prepare for decryption
if (buffer != null) {
buffer.flip();
}
ct.reset();
processed = 0;
// Check for overlap in the bytebuffers
dst = overlapDetection(src, dst);
// Decrypt the all the input data and put it into dst
doLastBlock(gctrPAndC, buffer, ct, dst);
restoreDst(dst);
src.position(src.limit());
if (ibuffer != null) {
ibuffer.reset();
}
return processed;
engine = null;
restoreDst(dst);
return len;
}
/**
@ -1517,11 +1579,12 @@ abstract class GaloisCounterMode extends CipherSpi {
* When this method is used, all the data is either in the ibuffer
* or in 'in'.
*/
int decryptBlocks(GCM op, byte[] in, int inOfs, int inLen,
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
@ -1538,15 +1601,24 @@ abstract class GaloisCounterMode extends CipherSpi {
if (bLen > 0) {
buffer = ibuffer.toByteArray();
if (bLen >= blockSize) {
len += op.update(buffer, 0, bLen, out, outOfs);
outOfs += len; // noop for ghash
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
}
// merge the remaining ibuffer with the 'in'
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,
@ -1557,9 +1629,9 @@ abstract class GaloisCounterMode extends CipherSpi {
// If is more than block between the merged data and 'in',
// update(), otherwise setup for final
if (inLen > 0) {
int resultLen = op.update(block, 0, blockSize,
resultLen = op.update(block, 0, blockSize,
out, outOfs);
outOfs += resultLen; // noop for ghash
outOfs += resultLen;
len += resultLen;
} else {
in = block;
@ -1569,14 +1641,6 @@ abstract class GaloisCounterMode extends CipherSpi {
}
}
// Finish off the operation
if (inLen > TRIGGERLEN) {
int l = throttleData(op, in, inOfs, inLen, out, outOfs);
inOfs += l;
inLen -= l;
outOfs += l; // noop for ghash
len += l;
}
return len + op.doFinal(in, inOfs, inLen, out, outOfs);
}
}
@ -1609,11 +1673,11 @@ abstract class GaloisCounterMode extends CipherSpi {
* This class is for encryption when both GCTR and GHASH
* can operation in parallel.
*/
static final class GCTRGHASH implements GCM {
static final class EncryptOp implements GCMOperation {
GCTR gctr;
GHASH ghash;
GCTRGHASH(GCTR c, GHASH g) {
EncryptOp(GCTR c, GHASH g) {
gctr = c;
ghash = g;
}
@ -1645,19 +1709,96 @@ abstract class GaloisCounterMode extends CipherSpi {
}
@Override
public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
int len = gctr.doFinal(in, inOfs, inLen, out, outOfs);
ghash.doFinal(out, outOfs, len);
return len;
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 l = gctr.doFinal(src, dst);
int len = gctr.doFinal(src, dst);
dst.reset();
ghash.doFinal(dst, l);
return l;
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);
}
}