mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
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:
parent
6ace805f8c
commit
0e7288ffbf
21 changed files with 1318 additions and 246 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue