8253821: Improve ByteBuffer performance with GCM

Reviewed-by: xuelei, valeriep
This commit is contained in:
Anthony Scarpino 2020-12-02 23:10:32 +00:00
parent 3da30e991a
commit cc1915b3b3
15 changed files with 2135 additions and 158 deletions

@ -25,12 +25,21 @@
package com.sun.crypto.provider;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.BadPaddingException;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
/**
* This class implements the AES algorithm in its various modes
@ -411,6 +420,7 @@ abstract class AESCipher extends CipherSpi {
outputOffset);
}
/**
* Encrypts or decrypts data in a single-part operation,
* or finishes a multiple-part operation.
@ -641,5 +651,26 @@ abstract class AESCipher extends CipherSpi {
}
}
}
}
/**
* Finalize crypto operation with ByteBuffers
*
* @param input the input ByteBuffer
* @param output the output ByteBuffer
*
* @return output length
* @throws ShortBufferException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
@Override
protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
if (core.getMode() == CipherCore.GCM_MODE && !input.hasArray()) {
return core.gcmDoFinal(input, output);
} else {
return super.engineDoFinal(input, output);
}
}
}

@ -25,6 +25,7 @@
package com.sun.crypto.provider;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
@ -722,8 +723,7 @@ final class CipherCore {
len = (len > 0 ? (len - (len % unitBytes)) : 0);
// check output buffer capacity
if ((output == null) ||
((output.length - outputOffset) < len)) {
if (output == null || (output.length - outputOffset) < len) {
throw new ShortBufferException("Output buffer must be "
+ "(at least) " + len
+ " bytes long");
@ -917,10 +917,10 @@ final class CipherCore {
int estOutSize = getOutputSizeByOperation(inputLen, true);
int outputCapacity = checkOutputCapacity(output, outputOffset,
estOutSize);
int offset = decrypting ? 0 : outputOffset; // 0 for decrypting
int offset = outputOffset;
byte[] finalBuf = prepareInputBuffer(input, inputOffset,
inputLen, output, outputOffset);
byte[] outWithPadding = null; // for decrypting only
byte[] internalOutput = null; // for decrypting only
int finalOffset = (finalBuf == input) ? inputOffset : 0;
int finalBufLen = (finalBuf == input) ? inputLen : finalBuf.length;
@ -934,11 +934,14 @@ final class CipherCore {
if (outputCapacity < estOutSize) {
cipher.save();
}
// create temporary output buffer so that only "real"
// data bytes are passed to user's output buffer.
outWithPadding = new byte[estOutSize];
if (getMode() != GCM_MODE || outputCapacity < estOutSize) {
// create temporary output buffer if the estimated size is larger
// than the user-provided buffer.
internalOutput = new byte[estOutSize];
offset = 0;
}
}
byte[] outBuffer = decrypting ? outWithPadding : output;
byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer,
offset, finalBufLen, input);
@ -954,9 +957,11 @@ final class CipherCore {
+ " bytes needed");
}
// copy the result into user-supplied output buffer
System.arraycopy(outWithPadding, 0, output, outputOffset, outLen);
// decrypt mode. Zero out output data that's not required
Arrays.fill(outWithPadding, (byte) 0x00);
if (internalOutput != null) {
System.arraycopy(internalOutput, 0, output, outputOffset, outLen);
// decrypt mode. Zero out output data that's not required
Arrays.fill(internalOutput, (byte) 0x00);
}
}
endDoFinal();
return outLen;
@ -970,16 +975,15 @@ final class CipherCore {
}
}
private int unpad(int outLen, byte[] outWithPadding)
private int unpad(int outLen, int off, byte[] outWithPadding)
throws BadPaddingException {
int padStart = padding.unpad(outWithPadding, 0, outLen);
int padStart = padding.unpad(outWithPadding, off, outLen);
if (padStart < 0) {
throw new BadPaddingException("Given final block not " +
"properly padded. Such issues can arise if a bad key " +
"is used during decryption.");
}
outLen = padStart;
return outLen;
return padStart - off;
}
private byte[] prepareInputBuffer(byte[] input, int inputOffset,
@ -1055,7 +1059,7 @@ final class CipherCore {
len = finalNoPadding(finalBuf, finalOffset, output,
outOfs, finalBufLen);
if (decrypting && padding != null) {
len = unpad(len, output);
len = unpad(len, outOfs, output);
}
return len;
} finally {
@ -1225,4 +1229,27 @@ final class CipherCore {
checkReinit();
cipher.updateAAD(src, offset, len);
}
// This must only be used with GCM.
// If some data has been buffered from an update call, operate on the buffer
// then run doFinal.
int gcmDoFinal(ByteBuffer src, ByteBuffer dst) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
int estOutSize = getOutputSizeByOperation(src.remaining(), true);
if (estOutSize > dst.remaining()) {
throw new ShortBufferException("output buffer too small");
}
if (decrypting) {
if (buffered > 0) {
cipher.decrypt(buffer, 0, buffered, new byte[0], 0);
}
return cipher.decryptFinal(src, dst);
} else {
if (buffered > 0) {
((GaloisCounterMode)cipher).encrypt(buffer, 0, buffered);
}
return cipher.encryptFinal(src, dst);
}
}
}

@ -25,6 +25,7 @@
package com.sun.crypto.provider;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidAlgorithmParameterException;
import javax.crypto.*;
@ -242,4 +243,27 @@ abstract class FeedbackCipher {
// internally during decryption mode
return 0;
}
/*
* ByteBuffer methods should not be accessed as CipherCore and AESCipher
* copy the data to byte arrays. These methods are to satisfy the compiler.
*/
int encrypt(ByteBuffer src, ByteBuffer dst) {
throw new UnsupportedOperationException("ByteBuffer not supported");
};
int decrypt(ByteBuffer src, ByteBuffer dst) {
throw new UnsupportedOperationException("ByteBuffer not supported");
};
int encryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, ShortBufferException {
throw new UnsupportedOperationException("ByteBuffer not supported");
};
int decryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, AEADBadTagException,
ShortBufferException {
throw new UnsupportedOperationException("ByteBuffer not supported");
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2020, 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
@ -54,11 +54,15 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
*/
final class GCTR extends CounterMode {
// Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
private static final int MAX_LEN = 1024;
GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
super(cipher);
if (initialCounterBlk.length != AES_BLOCK_SIZE) {
throw new RuntimeException("length of initial counter block (" + initialCounterBlk.length +
") not equal to AES_BLOCK_SIZE (" + AES_BLOCK_SIZE + ")");
throw new RuntimeException("length of initial counter block (" +
initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" +
AES_BLOCK_SIZE + ")");
}
iv = initialCounterBlk;
@ -112,9 +116,89 @@ final class GCTR extends CounterMode {
}
}
// input must be multiples of AES blocks, 128-bit, when calling update
int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
if (inLen - inOfs > in.length) {
throw new RuntimeException("input length out of bound");
}
if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) {
throw new RuntimeException("input length unsupported");
}
// See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for
// details on the check for 'dst' having enough space for the result.
long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE;
if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) {
int index = (i * AES_BLOCK_SIZE + n);
dst.put((byte) ((in[inOfs + index] ^ encryptedCntr[n])));
}
GaloisCounterMode.increment32(counter);
}
return inLen;
} else {
int len = inLen - inLen % AES_BLOCK_SIZE;
int processed = len;
byte[] out = new byte[Math.min(MAX_LEN, len)];
int offset = inOfs;
while (processed > MAX_LEN) {
encrypt(in, offset, MAX_LEN, out, 0);
dst.put(out, 0, MAX_LEN);
processed -= MAX_LEN;
offset += MAX_LEN;
}
encrypt(in, offset, processed, out, 0);
// If dst is less than blocksize, insert only what it can. Extra
// bytes would cause buffers with enough size to fail with a
// short buffer
dst.put(out, 0, Math.min(dst.remaining(), processed));
return len;
}
}
// input operates on multiples of AES blocks, 128-bit, when calling update.
// The remainder is left in the src buffer.
int update(ByteBuffer src, ByteBuffer dst) {
long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE;
if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) {
dst.put((byte) (src.get() ^ encryptedCntr[n]));
}
GaloisCounterMode.increment32(counter);
}
return numOfCompleteBlocks * AES_BLOCK_SIZE;
}
int len = src.remaining() - (src.remaining() % AES_BLOCK_SIZE);
int processed = len;
byte[] in = new byte[Math.min(MAX_LEN, len)];
while (processed > MAX_LEN) {
src.get(in, 0, MAX_LEN);
encrypt(in, 0, MAX_LEN, in, 0);
dst.put(in, 0, MAX_LEN);
processed -= MAX_LEN;
}
src.get(in, 0, processed);
encrypt(in, 0, processed, in, 0);
dst.put(in, 0, processed);
return len;
}
// input can be arbitrary size when calling doFinal
int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
int outOfs) throws IllegalBlockSizeException {
int outOfs) throws IllegalBlockSizeException {
try {
if (inLen < 0) {
throw new IllegalBlockSizeException("Negative input size!");
@ -130,7 +214,7 @@ final class GCTR extends CounterMode {
for (int n = 0; n < lastBlockSize; n++) {
out[outOfs + completeBlkLen + n] =
(byte) ((in[inOfs + completeBlkLen + n] ^
encryptedCntr[n]));
encryptedCntr[n]));
}
}
}
@ -139,4 +223,24 @@ final class GCTR extends CounterMode {
}
return inLen;
}
// src can be arbitrary size when calling doFinal
int doFinal(ByteBuffer src, ByteBuffer dst) {
int len = src.remaining();
int lastBlockSize = len % AES_BLOCK_SIZE;
try {
update(src, dst);
if (lastBlockSize != 0) {
// do the last partial block
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < lastBlockSize; n++) {
dst.put((byte) (src.get() ^ encryptedCntr[n]));
}
}
} finally {
reset();
}
return len;
}
}

@ -29,6 +29,7 @@
package com.sun.crypto.provider;
import java.nio.ByteBuffer;
import java.security.ProviderException;
import jdk.internal.vm.annotation.IntrinsicCandidate;
@ -198,6 +199,38 @@ final class GHASH {
processBlocks(in, inOfs, inLen/AES_BLOCK_SIZE, state, subkeyHtbl);
}
// Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
private static final int MAX_LEN = 1024;
// Will process as many blocks it can and will leave the remaining.
int update(ByteBuffer src, int inLen) {
inLen -= (inLen % AES_BLOCK_SIZE);
if (inLen == 0) {
return 0;
}
int processed = inLen;
byte[] in = new byte[Math.min(MAX_LEN, inLen)];
while (processed > MAX_LEN ) {
src.get(in, 0, MAX_LEN);
update(in, 0 , MAX_LEN);
processed -= MAX_LEN;
}
src.get(in, 0, processed);
update(in, 0, processed);
return inLen;
}
void doLastBlock(ByteBuffer src, int inLen) {
int processed = update(src, inLen);
if (inLen == processed) {
return;
}
byte[] block = new byte[AES_BLOCK_SIZE];
src.get(block, 0, inLen - processed);
update(block, 0, AES_BLOCK_SIZE);
}
private static void ghashRangeCheck(byte[] in, int inOfs, int inLen, long[] st, long[] subH) {
if (inLen < 0) {
throw new RuntimeException("invalid input length: " + inLen);

@ -25,13 +25,21 @@
package com.sun.crypto.provider;
import java.util.Arrays;
import java.io.*;
import java.security.*;
import javax.crypto.*;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
import sun.nio.ch.DirectBuffer;
import sun.security.util.ArrayUtil;
import javax.crypto.AEADBadTagException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
/**
* This class represents ciphers in GaloisCounter (GCM) mode.
@ -68,9 +76,12 @@ final class GaloisCounterMode extends FeedbackCipher {
private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
private int sizeOfAAD = 0;
// buffer for storing input in decryption, not used for encryption
// buffer data for crypto operation
private ByteArrayOutputStream ibuffer = null;
// Original dst buffer if there was an overlap situation
private ByteBuffer originalDst = null;
// in bytes; need to convert to bits (default value 128) when needed
private int tagLenBytes = DEFAULT_TAG_LEN;
@ -177,8 +188,17 @@ final class GaloisCounterMode extends FeedbackCipher {
return j0;
}
private static void checkDataLength(int processed, int len) {
if (processed > MAX_BUF_SIZE - len) {
/**
* 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");
}
@ -426,6 +446,64 @@ final class GaloisCounterMode extends FeedbackCipher {
}
}
// 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.
void doLastBlock(ByteBuffer buffer, ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException {
if (buffer != null && buffer.remaining() > 0) {
// en/decrypt on how much buffer there is in AES_BLOCK_SIZE
processed += gctrPAndC.update(buffer, dst);
// Process the remainder in the buffer
if (buffer.remaining() > 0) {
// Copy the remainder of the buffer into the extra block
byte[] block = new byte[AES_BLOCK_SIZE];
int over = buffer.remaining();
int len = over; // how much is processed by in the extra block
buffer.get(block, 0, over);
// if src is empty, update the final block and wait for later
// to finalize operation
if (src.remaining() > 0) {
// Fill out block with what is in data
if (src.remaining() > AES_BLOCK_SIZE - over) {
src.get(block, over, AES_BLOCK_SIZE - over);
len += AES_BLOCK_SIZE - over;
} else {
// If the remaining in buffer + data does not fill a
// block, complete the ghash operation
int l = src.remaining();
src.get(block, over, l);
len += l;
}
}
gctrPAndC.update(block, 0, AES_BLOCK_SIZE, dst);
processed += len;
}
}
// en/decrypt whatever remains in src.
// If src has been consumed, this will be a no-op
processed += gctrPAndC.doFinal(src, dst);
}
/*
* This method is for CipherCore to insert the remainder of its buffer
* into the ibuffer before a doFinal(ByteBuffer, ByteBuffer) operation
*/
int encrypt(byte[] in, int inOfs, int len) {
if (len > 0) {
// store internally until encryptFinal
ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
if (ibuffer == null) {
ibuffer = new ByteArrayOutputStream();
}
ibuffer.write(in, inOfs, len);
}
return len;
}
/**
* Performs encryption operation.
@ -436,32 +514,93 @@ final class GaloisCounterMode extends FeedbackCipher {
*
* @param in the buffer with the input data to be encrypted
* @param inOfs the offset in <code>in</code>
* @param len the length of the input data
* @param inLen the length of the input data
* @param out the buffer for the result
* @param outOfs the offset in <code>out</code>
* @exception ProviderException if <code>len</code> is not
* a multiple of the block size
* @return the number of bytes placed into the <code>out</code> buffer
*/
int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
ArrayUtil.blockSizeCheck(len, blockSize);
checkDataLength(processed, len);
int encrypt(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
checkDataLength(inLen, getBufferedLength());
ArrayUtil.nullAndBoundsCheck(in, inOfs, inLen);
ArrayUtil.nullAndBoundsCheck(out, outOfs, inLen);
processAAD();
// 'inLen' stores the length to use with buffer 'in'.
// 'len' stores the length returned by the method.
int len = inLen;
if (len > 0) {
ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
ArrayUtil.nullAndBoundsCheck(out, outOfs, len);
// if there is enough data in the ibuffer and 'in', encrypt it.
if (ibuffer != null && ibuffer.size() > 0) {
byte[] buffer = ibuffer.toByteArray();
// number of bytes not filling a block
int remainder = ibuffer.size() % blockSize;
// number of bytes along block boundary
int blen = ibuffer.size() - remainder;
gctrPAndC.update(in, inOfs, len, out, outOfs);
processed += len;
ghashAllToS.update(out, outOfs, len);
// If there is enough bytes in ibuffer for a block or more,
// encrypt that first.
if (blen > 0) {
encryptBlocks(buffer, 0, blen, out, outOfs);
outOfs += blen;
}
// blen is now the offset for 'buffer'
// Construct and encrypt a block if there is enough 'buffer' and
// 'in' to make one
if ((inLen + remainder) >= blockSize) {
byte[] block = new byte[blockSize];
System.arraycopy(buffer, blen, block, 0, remainder);
int inLenUsed = blockSize - remainder;
System.arraycopy(in, inOfs, block, remainder, inLenUsed);
encryptBlocks(block, 0, blockSize, out, outOfs);
inOfs += inLenUsed;
inLen -= inLenUsed;
len += (blockSize - inLenUsed);
outOfs += blockSize;
ibuffer.reset();
// Code below will write the remainder from 'in' to ibuffer
} else if (remainder > 0) {
// If a block or more was encrypted from 'buffer' only, but the
// rest of 'buffer' with 'in' could not construct a block, then
// put the rest of 'buffer' back into ibuffer.
ibuffer.reset();
ibuffer.write(buffer, blen, remainder);
// Code below will write the remainder from 'in' to ibuffer
}
// If blen == 0 and there was not enough to construct a block
// from 'buffer' and 'in', then let the below code append 'in' to
// the ibuffer.
}
// Write any remaining bytes outside the blockSize into ibuffer.
int remainder = inLen % blockSize;
if (remainder > 0) {
if (ibuffer == null) {
ibuffer = new ByteArrayOutputStream(inLen % blockSize);
}
len -= remainder;
inLen -= remainder;
// remainder offset is based on original buffer length
ibuffer.write(in, inOfs + inLen, remainder);
}
// Encrypt the remaining blocks inside of 'in'
if (inLen > 0) {
encryptBlocks(in, inOfs, inLen, out, outOfs);
}
return len;
}
void encryptBlocks(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
gctrPAndC.update(in, inOfs, len, out, outOfs);
processed += len;
ghashAllToS.update(out, outOfs, len);
}
/**
* Performs encryption operation for the last time.
*
@ -474,10 +613,8 @@ final class GaloisCounterMode extends FeedbackCipher {
*/
int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
throws IllegalBlockSizeException, ShortBufferException {
if (len > MAX_BUF_SIZE - tagLenBytes) {
throw new ShortBufferException
("Can't fit both data and tag into one buffer");
}
checkDataLength(len, getBufferedLength(), tagLenBytes);
try {
ArrayUtil.nullAndBoundsCheck(out, outOfs,
(len + tagLenBytes));
@ -485,8 +622,6 @@ final class GaloisCounterMode extends FeedbackCipher {
throw new ShortBufferException("Output buffer too small");
}
checkDataLength(processed, len);
processAAD();
if (len > 0) {
ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
@ -494,15 +629,45 @@ final class GaloisCounterMode extends FeedbackCipher {
doLastBlock(in, inOfs, len, out, outOfs, true);
}
byte[] lengthBlock =
getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(lengthBlock);
byte[] s = ghashAllToS.digest();
byte[] sOut = new byte[s.length];
byte[] block = getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(block);
block = ghashAllToS.digest();
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
System.arraycopy(block, 0, out, (outOfs + len), tagLenBytes);
return (len + tagLenBytes);
}
int encryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, ShortBufferException {
dst = overlapDetection(src, dst);
int len = src.remaining();
len += getBufferedLength();
// 'len' includes ibuffer data
checkDataLength(len, tagLenBytes);
dst.mark();
if (dst.remaining() < len + tagLenBytes) {
throw new ShortBufferException("Output buffer too small");
}
processAAD();
if (len > 0) {
doLastBlock((ibuffer == null || ibuffer.size() == 0) ?
null : ByteBuffer.wrap(ibuffer.toByteArray()), src, dst);
dst.reset();
ghashAllToS.doLastBlock(dst, len);
}
byte[] block = getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(block);
block = ghashAllToS.digest();
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0);
dst.put(block, 0, tagLenBytes);
restoreDst(dst);
System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
return (len + tagLenBytes);
}
@ -524,10 +689,6 @@ final class GaloisCounterMode extends FeedbackCipher {
* @return the number of bytes placed into the <code>out</code> buffer
*/
int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
ArrayUtil.blockSizeCheck(len, blockSize);
checkDataLength(ibuffer.size(), len);
processAAD();
if (len > 0) {
@ -540,6 +701,19 @@ final class GaloisCounterMode extends FeedbackCipher {
return 0;
}
int decrypt(ByteBuffer src, ByteBuffer dst) {
if (src.remaining() > 0) {
byte[] b = new byte[src.remaining()];
src.get(b);
try {
ibuffer.write(b);
} catch (IOException e) {
throw new ProviderException("Unable to add remaining input to the buffer", e);
}
}
return 0;
}
/**
* Performs decryption operation for the last time.
*
@ -566,11 +740,11 @@ final class GaloisCounterMode extends FeedbackCipher {
// do this check here can also catch the potential integer overflow
// scenario for the subsequent output buffer capacity check.
checkDataLength(ibuffer.size(), (len - tagLenBytes));
checkDataLength(getBufferedLength(), (len - tagLenBytes));
try {
ArrayUtil.nullAndBoundsCheck(out, outOfs,
(ibuffer.size() + len) - tagLenBytes);
(getBufferedLength() + len) - tagLenBytes);
} catch (ArrayIndexOutOfBoundsException aiobe) {
throw new ShortBufferException("Output buffer too small");
}
@ -586,7 +760,7 @@ final class GaloisCounterMode extends FeedbackCipher {
// If decryption is in-place or there is buffered "ibuffer" data, copy
// the "in" byte array into the ibuffer before proceeding.
if (in == out || ibuffer.size() > 0) {
if (in == out || getBufferedLength() > 0) {
if (len > 0) {
ibuffer.write(in, inOfs, len);
}
@ -602,19 +776,16 @@ final class GaloisCounterMode extends FeedbackCipher {
doLastBlock(in, inOfs, len, out, outOfs, false);
}
byte[] lengthBlock =
getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(lengthBlock);
byte[] s = ghashAllToS.digest();
byte[] sOut = new byte[s.length];
byte[] block = getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(block);
block = ghashAllToS.digest();
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
gctrForSToTag.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] ^ sOut[i];
mismatch |= tag[i] ^ block[i];
}
if (mismatch != 0) {
@ -624,6 +795,122 @@ final class GaloisCounterMode extends FeedbackCipher {
return len;
}
// Note: In-place operations do not need an intermediary copy because
// the GHASH check was performed before the decryption.
int decryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, AEADBadTagException,
ShortBufferException {
dst = overlapDetection(src, dst);
// Length of the input
ByteBuffer tag;
ByteBuffer ct = src.duplicate();
ByteBuffer buffer = ((ibuffer == null || ibuffer.size() == 0) ? null :
ByteBuffer.wrap(ibuffer.toByteArray()));
int len;
if (ct.remaining() >= tagLenBytes) {
tag = src.duplicate();
tag.position(ct.limit() - tagLenBytes);
ct.limit(ct.limit() - tagLenBytes);
len = ct.remaining();
if (buffer != null) {
len += buffer.remaining();
}
} else if (buffer != null && ct.remaining() < tagLenBytes) {
// 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();
buffer.limit(limit);
tag.put(ct);
tag.flip();
// Limit is how much of the ibuffer has been chopped off.
len = buffer.remaining();
} else {
throw new AEADBadTagException("Input too short - need tag");
}
// 'len' contains the length in ibuffer and src
checkDataLength(len);
if (len > dst.remaining()) {
throw new ShortBufferException("Output buffer too small");
}
processAAD();
// 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();
// If there is data stored in the buffer
if (buffer != null && buffer.remaining() > 0) {
ghashAllToS.update(buffer, buffer.remaining());
// Process the overage
if (buffer.remaining() > 0) {
// Fill out block between two buffers
if (ct.remaining() > 0) {
int over = buffer.remaining();
byte[] block = new byte[AES_BLOCK_SIZE];
// Copy the remainder of the buffer into the extra block
buffer.get(block, 0, over);
// Fill out block with what is in data
if (ct.remaining() > AES_BLOCK_SIZE - over) {
ct.get(block, over, AES_BLOCK_SIZE - over);
ghashAllToS.update(block, 0, AES_BLOCK_SIZE);
} else {
// If the remaining in buffer + data does not fill a
// block, complete the ghash operation
int l = ct.remaining();
ct.get(block, over, l);
ghashAllToS.doLastBlock(ByteBuffer.wrap(block), over + l);
}
} else {
// data is empty, so complete the ghash op with the
// remaining buffer
ghashAllToS.doLastBlock(buffer, buffer.remaining());
}
}
// Prepare buffer for decryption
buffer.flip();
}
if (ct.remaining() > 0) {
ghashAllToS.doLastBlock(ct, ct.remaining());
}
// Prepare buffer for decryption if available
ct.reset();
byte[] block = getLengthBlock(sizeOfAAD, len);
ghashAllToS.update(block);
block = ghashAllToS.digest();
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
gctrForSToTag.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) {
throw new AEADBadTagException("Tag mismatch!");
}
// Decrypt the all the input data and put it into dst
doLastBlock(buffer, ct, dst);
restoreDst(dst);
// 'processed' from the gctr decryption operation, not ghash
return processed;
}
// return tag length in bytes
int getTagLen() {
return this.tagLenBytes;
@ -636,4 +923,94 @@ final class GaloisCounterMode extends FeedbackCipher {
return ibuffer.size();
}
}
/**
* 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()) {
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.isReadOnly()) {
// If using the heap, check underlying byte[] address.
if (!src.array().equals(dst.array()) ) {
return dst;
}
// Position plus arrayOffset() will give us the true offset from
// the underlying byte[] address.
if (src.position() + src.arrayOffset() >=
dst.position() + dst.arrayOffset()) {
return dst;
}
}
} else {
// buffer types aren't the same
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;
}
/**
* If originalDst exists, dst is an internal dst buffer, then copy the data
* into the original dst buffer
*/
void restoreDst(ByteBuffer dst) {
if (originalDst == null) {
return;
}
dst.flip();
originalDst.put(dst);
originalDst = null;
}
}

@ -0,0 +1,851 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/
/*
* @test
* @summary Use Cipher update and doFinal with a mixture of byte[], bytebuffer,
* and offset while verifying return values. Also using different and
* in-place buffers.
*
* in-place is not tested with different buffer types as it is not a logical
* scenario and is complicated by getOutputSize calculations.
*/
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class GCMBufferTest implements Cloneable {
// Data type for the operation
enum dtype { BYTE, HEAP, DIRECT };
// Data map
static HashMap<String, List<Data>> datamap = new HashMap<>();
// List of enum values for order of operation
List<dtype> ops;
static final int AESBLOCK = 16;
// The remaining input data length is inserted at the particular index
// in sizes[] during execution.
static final int REMAINDER = -1;
String algo;
boolean same = true;
int[] sizes;
boolean incremental = false;
// In some cases the theoretical check is too complicated to verify
boolean theoreticalCheck;
List<Data> dataSet;
int inOfs = 0, outOfs = 0;
static class Data {
int id;
SecretKey key;
byte[] iv;
byte[] pt;
byte[] aad;
byte[] ct;
byte[] tag;
Data(String keyalgo, int id, String key, String iv, byte[] pt, String aad,
String ct, String tag) {
this.id = id;
this.key = new SecretKeySpec(HexToBytes(key), keyalgo);
this.iv = HexToBytes(iv);
this.pt = pt;
this.aad = HexToBytes(aad);
this.ct = HexToBytes(ct);
this.tag = HexToBytes(tag);
}
Data(String keyalgo, int id, String key, String iv, String pt, String aad,
String ct, String tag) {
this(keyalgo, id, key, iv, HexToBytes(pt), aad, ct, tag);
}
Data(String keyalgo, int id, String key, int ptlen) {
this.id = id;
this.key = new SecretKeySpec(HexToBytes(key), keyalgo);
iv = new byte[16];
pt = new byte[ptlen];
tag = new byte[12];
aad = new byte[0];
byte[] tct = null;
try {
SecureRandom r = new SecureRandom();
r.nextBytes(iv);
r.nextBytes(pt);
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.ENCRYPT_MODE, this.key,
new GCMParameterSpec(tag.length * 8, this.iv));
tct = c.doFinal(pt);
} catch (Exception e) {
System.out.println("Error in generating data for length " +
ptlen);
}
ct = new byte[ptlen];
System.arraycopy(tct, 0, ct, 0, ct.length);
System.arraycopy(tct, ct.length, tag, 0, tag.length);
}
}
/**
* Construct a test with an algorithm and a list of dtype.
* @param algo Algorithm string
* @param ops List of dtypes. If only one dtype is specified, only a
* doFinal operation will occur. If multiple dtypes are
* specified, the last is a doFinal, the others are updates.
*/
GCMBufferTest(String algo, List<dtype> ops) {
this.algo = algo;
this.ops = ops;
theoreticalCheck = true;
dataSet = datamap.get(algo);
}
public GCMBufferTest clone() throws CloneNotSupportedException{
return (GCMBufferTest)super.clone();
}
/**
* Define particular data sizes to be tested. "REMAINDER", which has a
* value of -1, can be used to insert the remaining input text length at
* that index during execution.
* @param sizes Data sizes for each dtype in the list.
*/
GCMBufferTest dataSegments(int[] sizes) {
this.sizes = sizes;
return this;
}
/**
* Do not perform in-place operations
*/
GCMBufferTest differentBufferOnly() {
this.same = false;
return this;
}
/**
* Enable incrementing through each data size available. This can only be
* used when the List has more than one dtype entry.
*/
GCMBufferTest incrementalSegments() {
this.incremental = true;
return this;
}
/**
* Specify a particular test dataset.
*
* @param id id value for the test data to used in this test.
*/
GCMBufferTest dataSet(int id) throws Exception {
for (Data d : datamap.get(algo)) {
if (d.id == id) {
dataSet = List.of(d);
return this;
}
}
throw new Exception("Unaeble to find dataSet id = " + id);
}
/**
* Set both input and output offsets to the same offset
* @param offset value for inOfs and outOfs
* @return
*/
GCMBufferTest offset(int offset) {
this.inOfs = offset;
this.outOfs = offset;
return this;
}
/**
* Set the input offset
* @param offset value for input offset
* @return
*/
GCMBufferTest inOfs(int offset) {
this.inOfs = offset;
return this;
}
/**
* Set the output offset
* @param offset value for output offset
* @return
*/
GCMBufferTest outOfs(int offset) {
this.outOfs = offset;
return this;
}
/**
* Reverse recursive loop that starts at the end-1 index, going to 0, in
* the size array to calculate all the possible sizes.
* It returns the remaining data size not used in the loop. This remainder
* is used for the end index which is the doFinal op.
*/
int inc(int index, int max, int total) {
if (sizes[index] == max - total) {
sizes[index + 1]++;
total++;
sizes[index] = 0;
} else if (index == 0) {
sizes[index]++;
}
total += sizes[index];
if (index > 0) {
return inc(index - 1, max, total);
}
return total;
}
// Call recursive loop and take returned remainder value for last index
boolean incrementSizes(int max) {
sizes[ops.size() - 1] = max - inc(ops.size() - 2, max, 0);
if (sizes[ops.size() - 2] == max) {
// We are at the end, exit test loop
return false;
}
return true;
}
void test() throws Exception {
int i = 1;
System.out.println("Algo: " + algo + " \tOps: " + ops.toString());
for (Data data : dataSet) {
// If incrementalSegments is enabled, run through that test only
if (incremental) {
if (ops.size() < 2) {
throw new Exception("To do incrementalSegments you must" +
"have more that 1 dtype in the list");
}
sizes = new int[ops.size()];
while (incrementSizes(data.pt.length)) {
System.out.print("Encrypt: Data Index: " + i + " \tSizes[ ");
for (int v : sizes) {
System.out.print(v + " ");
}
System.out.println("]");
encrypt(data);
}
Arrays.fill(sizes, 0);
while (incrementSizes(data.ct.length + data.tag.length)) {
System.out.print("Decrypt: Data Index: " + i + " \tSizes[ ");
for (int v : sizes) {
System.out.print(v + " ");
}
System.out.println("]");
decrypt(data);
}
} else {
// Default test of 0 and 2 offset doing in place and different
// i/o buffers
System.out.println("Encrypt: Data Index: " + i);
encrypt(data);
System.out.println("Decrypt: Data Index: " + i);
decrypt(data);
}
i++;
}
}
// Setup data for encryption
void encrypt(Data data) throws Exception {
byte[] input, output;
input = data.pt;
output = new byte[data.ct.length + data.tag.length];
System.arraycopy(data.ct, 0, output, 0, data.ct.length);
System.arraycopy(data.tag, 0, output, data.ct.length,
data.tag.length);
// Test different input/output buffers
System.out.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in/out buffer: different");
crypto(true, data, input, output);
// Test with in-place buffers
if (same) {
System.out.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in/out buffer: in-place");
cryptoSameBuffer(true, data, input, output);
}
}
// Setup data for decryption
void decrypt(Data data) throws Exception {
byte[] input, output;
input = new byte[data.ct.length + data.tag.length];
System.arraycopy(data.ct, 0, input, 0, data.ct.length);
System.arraycopy(data.tag, 0, input, data.ct.length, data.tag.length);
output = data.pt;
// Test different input/output buffers
System.out.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in-place: different");
crypto(false, data, input, output);
// Test with in-place buffers
if (same) {
System.out.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in-place: same");
cryptoSameBuffer(false, data, input, output);
}
}
/**
* Perform cipher operation using different input and output buffers.
* This method allows mixing of data types (byte, heap, direct).
*/
void crypto(boolean encrypt, Data d, byte[] input, byte[] output)
throws Exception {
byte[] pt = new byte[input.length + inOfs];
System.arraycopy(input, 0, pt, inOfs, input.length);
byte[] expectedOut = new byte[output.length + outOfs];
System.arraycopy(output, 0, expectedOut, outOfs, output.length);
int plen = input.length / ops.size(); // partial input length
int theoreticallen;// expected output length
int dataoffset = 0; // offset of unconsumed data in pt
int index = 0; // index of which op we are on
int rlen; // result length
int pbuflen = 0; // plen remaining in the GCM internal buffers
Cipher cipher = Cipher.getInstance(algo);
cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE),
d.key, new GCMParameterSpec(d.tag.length * 8, d.iv));
cipher.updateAAD(d.aad);
ByteArrayOutputStream ba = new ByteArrayOutputStream();
ba.write(new byte[outOfs], 0, outOfs);
for (dtype v : ops) {
if (index < ops.size() - 1) {
if (sizes != null && input.length > 0) {
if (sizes[index] == -1) {
plen = input.length - dataoffset;
} else {
if (sizes[index] > input.length) {
plen = input.length;
} else {
plen = sizes[index];
}
}
}
int olen = cipher.getOutputSize(plen) + outOfs;
/*
* The theoretical limit is the length of the data sent to
* update() + any data might be setting in CipherCore or GCM
* internal buffers % the block size.
*/
theoreticallen = (plen + pbuflen) - ((plen + pbuflen) % AESBLOCK);
// Update operations
switch (v) {
case BYTE -> {
byte[] out = new byte[olen];
rlen = cipher.update(pt, dataoffset + inOfs, plen, out,
outOfs);
ba.write(out, outOfs, rlen);
}
case HEAP -> {
ByteBuffer b = ByteBuffer.allocate(plen + outOfs);
b.position(outOfs);
b.put(pt, dataoffset + inOfs, plen);
b.flip();
b.position(outOfs);
ByteBuffer out = ByteBuffer.allocate(olen);
out.position(outOfs);
rlen = cipher.update(b, out);
ba.write(out.array(), outOfs, rlen);
}
case DIRECT -> {
ByteBuffer b = ByteBuffer.allocateDirect(plen + outOfs);
b.position(outOfs);
b.put(pt, dataoffset + inOfs, plen);
b.flip();
b.position(outOfs);
ByteBuffer out = ByteBuffer.allocateDirect(olen);
out.position(outOfs);
rlen = cipher.update(b, out);
byte[] o = new byte[rlen];
out.flip();
out.position(outOfs);
out.get(o, 0, rlen);
ba.write(o);
}
default -> throw new Exception("Unknown op: " + v.name());
}
if (theoreticalCheck) {
pbuflen += plen - rlen;
if (encrypt && rlen != theoreticallen) {
throw new Exception("Wrong update return len (" +
v.name() + "): " + "rlen=" + rlen +
", expected output len=" + theoreticallen);
}
}
dataoffset += plen;
index++;
} else {
// doFinal operation
plen = input.length - dataoffset;
int olen = cipher.getOutputSize(plen) + outOfs;
switch (v) {
case BYTE -> {
byte[] out = new byte[olen];
rlen = cipher.doFinal(pt, dataoffset + inOfs,
plen, out, outOfs);
ba.write(out, outOfs, rlen);
}
case HEAP -> {
ByteBuffer b = ByteBuffer.allocate(plen + inOfs);
b.limit(b.capacity());
b.position(inOfs);
b.put(pt, dataoffset + inOfs, plen);
b.flip();
b.position(inOfs);
ByteBuffer out = ByteBuffer.allocate(olen);
out.limit(out.capacity());
out.position(outOfs);
rlen = cipher.doFinal(b, out);
ba.write(out.array(), outOfs, rlen);
}
case DIRECT -> {
ByteBuffer b = ByteBuffer.allocateDirect(plen + inOfs);
b.limit(b.capacity());
b.position(inOfs);
b.put(pt, dataoffset + inOfs, plen);
b.flip();
b.position(inOfs);
ByteBuffer out = ByteBuffer.allocateDirect(olen);
out.limit(out.capacity());
out.position(outOfs);
rlen = cipher.doFinal(b, out);
byte[] o = new byte[rlen];
out.flip();
out.position(outOfs);
out.get(o, 0, rlen);
ba.write(o);
}
default -> throw new Exception("Unknown op: " + v.name());
}
if (theoreticalCheck && rlen != olen - outOfs) {
throw new Exception("Wrong doFinal return len (" +
v.name() + "): " + "rlen=" + rlen +
", expected output len=" + (olen - outOfs));
}
// Verify results
byte[] ctresult = ba.toByteArray();
if (ctresult.length != expectedOut.length ||
Arrays.compare(ctresult, expectedOut) != 0) {
String s = "Ciphertext mismatch (" + v.name() +
"):\nresult (len=" + ctresult.length + "):" +
String.format("%0" + (ctresult.length << 1) + "x",
new BigInteger(1, ctresult)) +
"\nexpected (len=" + output.length + "):" +
String.format("%0" + (output.length << 1) + "x",
new BigInteger(1, output));
System.err.println(s);
throw new Exception(s);
}
}
}
}
/**
* Perform cipher operation using in-place buffers. This method does not
* allow mixing of data types (byte, heap, direct).
*
* Mixing data types makes no sense for in-place operations and would
* greatly complicate the test code.
*/
void cryptoSameBuffer(boolean encrypt, Data d, byte[] input, byte[] output) throws Exception {
byte[] data, out;
if (encrypt) {
data = new byte[output.length + Math.max(inOfs, outOfs)];
} else {
data = new byte[input.length + Math.max(inOfs, outOfs)];
}
ByteBuffer bbin = null, bbout = null;
System.arraycopy(input, 0, data, inOfs, input.length);
byte[] expectedOut = new byte[output.length + outOfs];
System.arraycopy(output, 0, expectedOut, outOfs, output.length);
int plen = input.length / ops.size(); // partial input length
int theorticallen = plen - (plen % AESBLOCK); // output length
int dataoffset = 0;
int index = 0;
int rlen = 0; // result length
int len = 0;
Cipher cipher = Cipher.getInstance(algo);
cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE),
d.key, new GCMParameterSpec(d.tag.length * 8, d.iv));
cipher.updateAAD(d.aad);
// Prepare data
switch (ops.get(0)) {
case HEAP -> {
bbin = ByteBuffer.wrap(data);
bbin.limit(input.length + inOfs);
bbout = bbin.duplicate();
}
case DIRECT -> {
bbin = ByteBuffer.allocateDirect(data.length);
bbout = bbin.duplicate();
bbin.put(data, 0, input.length + inOfs);
bbin.flip();
}
}
// Set data limits for bytebuffers
if (bbin != null) {
bbin.position(inOfs);
bbout.limit(output.length + outOfs);
bbout.position(outOfs);
}
// Iterate through each operation
for (dtype v : ops) {
if (index < ops.size() - 1) {
switch (v) {
case BYTE -> {
rlen = cipher.update(data, dataoffset + inOfs, plen,
data, len + outOfs);
}
case HEAP, DIRECT -> {
theorticallen = bbin.remaining() -
(bbin.remaining() % AESBLOCK);
rlen = cipher.update(bbin, bbout);
}
default -> throw new Exception("Unknown op: " + v.name());
}
// Check that the theoretical return value matches the actual.
if (theoreticalCheck && encrypt && rlen != theorticallen) {
throw new Exception("Wrong update return len (" +
v.name() + "): " + "rlen=" + rlen +
", expected output len=" + theorticallen);
}
dataoffset += plen;
len += rlen;
index++;
} else {
// Run doFinal op
plen = input.length - dataoffset;
switch (v) {
case BYTE -> {
rlen = cipher.doFinal(data, dataoffset + inOfs,
plen, data, len + outOfs);
out = Arrays.copyOfRange(data, 0,len + rlen + outOfs);
}
case HEAP, DIRECT -> {
rlen = cipher.doFinal(bbin, bbout);
bbout.flip();
out = new byte[bbout.remaining()];
bbout.get(out);
}
default -> throw new Exception("Unknown op: " + v.name());
}
len += rlen;
// Verify results
if (len != output.length ||
Arrays.compare(out, 0, len, expectedOut, 0,
output.length) != 0) {
String s = "Ciphertext mismatch (" + v.name() +
"):\nresult (len=" + len + "):\n" +
byteToHex(out) +
"\nexpected (len=" + output.length + "):\n" +
String.format("%0" + (output.length << 1) + "x",
new BigInteger(1, output));
System.err.println(s);
throw new Exception(s);
}
}
}
}
static void offsetTests(GCMBufferTest t) throws Exception {
t.clone().offset(2).test();
t.clone().inOfs(2).test();
// Test not designed for overlap situations
t.clone().outOfs(2).differentBufferOnly().test();
}
public static void main(String args[]) throws Exception {
initTest();
// Test single byte array
new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)));
// Test update-doFinal with byte arrays
new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE)));
// Test update-update-doFinal with byte arrays
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)));
// Test single heap bytebuffer
new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP)));
// Test update-doFinal with heap bytebuffer
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.HEAP, dtype.HEAP)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP, dtype.HEAP)));
// Test update-update-doFinal with heap bytebuffer
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.HEAP, dtype.HEAP, dtype.HEAP)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP, dtype.HEAP, dtype.HEAP)));
// Test single direct bytebuffer
new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.DIRECT)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.DIRECT)));
// Test update-doFinal with direct bytebuffer
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT)));
// Test update-update-doFinal with direct bytebuffer
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)));
// Test update-update-doFinal with byte arrays and preset data sizes
GCMBufferTest t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).dataSegments(
new int[] { 1, 1, GCMBufferTest.REMAINDER});
t.clone().test();
offsetTests(t.clone());
// Test update-doFinal with a byte array and a direct bytebuffer
t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.DIRECT)).differentBufferOnly();
t.clone().test();
offsetTests(t.clone());
// Test update-doFinal with a byte array and heap and direct bytebuffer
t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.HEAP, dtype.DIRECT)).differentBufferOnly();
t.clone().test();
offsetTests(t.clone());
// Test update-doFinal with a direct bytebuffer and a byte array.
t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly();
t.clone().test();
offsetTests(t.clone());
// Test update-doFinal with a direct bytebuffer and a byte array with
// preset data sizes.
t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly().
dataSegments(new int[] { 20, GCMBufferTest.REMAINDER });
t.clone().test();
offsetTests(t.clone());
// Test update-update-doFinal with a direct and heap bytebuffer and a
// byte array with preset data sizes.
t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.BYTE, dtype.HEAP)).
differentBufferOnly().dataSet(5).
dataSegments(new int[] { 5000, 1000, GCMBufferTest.REMAINDER });
t.clone().test();
offsetTests(t.clone());
// Test update-update-doFinal with byte arrays, incrementing through
// every data size combination for the Data set 0
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).incrementalSegments().
dataSet(0).test();
// Test update-update-doFinal with direct bytebuffers, incrementing through
// every data size combination for the Data set 0
new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
incrementalSegments().dataSet(0).test();
}
private static byte[] HexToBytes(String hexVal) {
if (hexVal == null) {
return new byte[0];
}
byte[] result = new byte[hexVal.length()/2];
for (int i = 0; i < result.length; i++) {
String byteVal = hexVal.substring(2*i, 2*i +2);
result[i] = Integer.valueOf(byteVal, 16).byteValue();
}
return result;
}
private static String byteToHex(byte[] barray) {
StringBuilder s = new StringBuilder();
for (byte b : barray) {
s.append(String.format("%02x", b));
}
return s.toString();
}
// Test data
static void initTest() {
datamap.put("AES/GCM/NoPadding", List.of(
// GCM KAT
new Data("AES", 0,
"141f1ce91989b07e7eb6ae1dbd81ea5e",
"49451da24bd6074509d3cebc2c0394c972e6934b45a1d91f3ce1d3ca69e19" +
"4aa1958a7c21b6f21d530ce6d2cc5256a3f846b6f9d2f38df0102c4791e5" +
"7df038f6e69085646007df999751e248e06c47245f4cd3b8004585a7470d" +
"ee1690e9d2d63169a58d243c0b57b3e5b4a481a3e4e8c60007094ef3adea" +
"2e8f05dd3a1396f",
"d384305af2388699aa302f510913fed0f2cb63ba42efa8c5c9de2922a2ec" +
"2fe87719dadf1eb0aef212b51e74c9c5b934104a43",
"630cf18a91cc5a6481ac9eefd65c24b1a3c93396bd7294d6b8ba3239517" +
"27666c947a21894a079ef061ee159c05beeb4",
"f4c34e5fbe74c0297313268296cd561d59ccc95bbfcdfcdc71b0097dbd83" +
"240446b28dc088abd42b0fc687f208190ff24c0548",
"dbb93bbb56d0439cd09f620a57687f5d"),
// GCM KAT
new Data("AES", 1, "11754cd72aec309bf52f7687212e8957",
"3c819d9a9bed087615030b65",
(String)null, null, null,
"250327c674aaf477aef2675748cf6971"),
// GCM KAT
new Data("AES", 2, "272f16edb81a7abbea887357a58c1917",
"794ec588176c703d3d2a7a07",
(String)null, null, null,
"b6e6f197168f5049aeda32dafbdaeb"),
// zero'd test data
new Data("AES", 3, "272f16edb81a7abbea887357a58c1917",
"794ec588176c703d3d2a7a07",
new byte[256], null,
"15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" +
"e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" +
"07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" +
"7483d041f2c3612ad5033155984470bdfc64d18df73c2745d92f28461bb09832" +
"33524811321ba87d213692825815dd13f528dba601a3c319cac6be9b48686c23" +
"a0ce23d5062916ea8827bbb243f585e446131489e951354c8ab24661f625c02e" +
"15536c5bb602244e98993ff745f3e523399b2059f0e062d8933fad2366e7e147" +
"510a931282bb0e3f635efe7bf05b1dd715f95f5858261b00735224256b6b3e80",
"08b3593840d4ed005f5234ae062a5c"),
// Random test data
new Data("AES", 4, "272f16edb81a7abbea887357a58c1917",
"794ec588176c703d3d2a7a07",
new byte[2075], null,
"15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" +
"e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" +
"07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" +
"7483d041f2c3612ad5033155984470bdfc64d18df73c2745d92f28461bb09832" +
"33524811321ba87d213692825815dd13f528dba601a3c319cac6be9b48686c23" +
"a0ce23d5062916ea8827bbb243f585e446131489e951354c8ab24661f625c02e" +
"15536c5bb602244e98993ff745f3e523399b2059f0e062d8933fad2366e7e147" +
"510a931282bb0e3f635efe7bf05b1dd715f95f5858261b00735224256b6b3e80" +
"7364cb53ff6d4e88f928cf67ac70da127718a8a35542efbae9dd7567c818a074" +
"9a0c74bd69014639f59768bc55056d1166ea5523e8c66f9d78d980beb8f0d83b" +
"a9e2c5544b94dc3a1a4b6f0f95f897b010150e89ebcacf0daee3c2793d6501a0" +
"b58b411de273dee987e8e8cf8bb29ef2e7f655b46b55fabf64c6a4295e0d080b" +
"6a570ace90eb0fe0f5b5d878bdd90eddaa1150e4d5a6505b350aac814fe99615" +
"317ecd0516a464c7904011ef5922409c0d65b1e43b69d7c3293a8f7d3e9fbee9" +
"eb91ec0007a7d6f72e64deb675d459c5ba07dcfd58d08e6820b100465e6e04f0" +
"663e310584a00d36d23699c1bffc6afa094c75184fc7cde7ad35909c0f49f2f3" +
"fe1e6d745ab628d74ea56b757047de57ce18b4b3c71e8af31a6fac16189cb0a3" +
"a97a1bea447042ce382fcf726560476d759c24d5c735525ea26a332c2094408e" +
"671c7deb81d5505bbfd178f866a6f3a011b3cfdbe089b4957a790688028dfdf7" +
"9a096b3853f9d0d6d3feef230c7f5f46ffbf7486ebdaca5804dc5bf9d202415e" +
"e0d67b365c2f92a17ea740807e4f0b198b42b54f15faa9dff2c7c35d2cf8d72e" +
"b8f8b18875a2e7b5c43d1e0aa5139c461e8153c7f632895aa46ffe2b134e6a0d" +
"dfbf6a336e709adfe951bd52c4dfc7b07a15fb3888fc35b7e758922f87a104c4" +
"563c5c7839cfe5a7edbdb97264a7c4ebc90367b10cbe09dbf2390767ad7afaa8" +
"8fb46b39d3f55f216d2104e5cf040bf3d39b758bea28e2dbce576c808d17a8eb" +
"e2fd183ef42a774e39119dff1f539efeb6ad15d889dfcb0d54d0d4d4cc03c8d9" +
"aa6c9ebd157f5e7170183298d6a30ada8792dcf793d931e2a1eafccbc63c11c0" +
"c5c5ed60837f30017d693ccb294df392a8066a0594a56954aea7b78a16e9a11f" +
"4a8bc2104070a7319f5fab0d2c4ccad8ec5cd8f47c839179bfd54a7bf225d502" +
"cd0a318752fe763e8c09eb88fa57fc5399ad1f797d0595c7b8afdd23f13603e9" +
"6802192bb51433b7723f4e512bd4f799feb94b458e7f9792f5f9bd6733828f70" +
"a6b7ffbbc0bb7575021f081ec2a0d37fecd7cda2daec9a3a9d9dfe1c8034cead" +
"e4b56b581cc82bd5b74b2b30817967d9da33850336f171a4c68e2438e03f4b11" +
"96da92f01b3b7aeab795180ccf40a4b090b1175a1fc0b67c95f93105c3aef00e" +
"13d76cc402539192274fee703730cd0d1c5635257719cc96cacdbad00c6255e2" +
"bd40c775b43ad09599e84f2c3205d75a6661ca3f151183be284b354ce21457d1" +
"3ba65b9b2cdb81874bd14469c2008b3ddec78f7225ecc710cc70de7912ca6a6d" +
"348168322ab59fdafcf5c833bfa0ad4046f4b6da90e9f263db7079af592eda07" +
"5bf16c6b1a8346da9c292a48bf660860a4fc89eaef40bc132779938eca294569" +
"787c740af2b5a8de7f5e10ac750d1e3d0ef3ed168ba408a676e10b8a20bd4be8" +
"3e8336b45e54481726d73e1bd19f165a98e242aca0d8387f2dd22d02d74e23db" +
"4cef9a523587413e0a44d7e3260019a34d3a6b38426ae9fa4655be338d721970" +
"cb9fe76c073f26f9303093a033022cd2c62b2790bce633ba9026a1c93b6535f1" +
"1882bf5880e511b9e1b0b7d8f23a993aae5fd275faac3a5b4ccaf7c06b0b266a" +
"ee970a1e3a4cd7a41094f516960630534e692545b25a347c30e3f328bba4825f" +
"ed754e5525d846131ecba7ca120a6aeabc7bab9f59c890c80b7e31f9bc741591" +
"55d292433ce9558e104102f2cc63ee267c1c8333e841522707ea6d595cb802b9" +
"61697da77bbc4cb404ea62570ab335ebffa2023730732ac5ddba1c3dbb5be408" +
"3c50aea462c1ffa166d7cc3db4b742b747e81b452db2363e91374dee8c6b40f0" +
"e7fbf50e60eaf5cc5649f6bb553aae772c185026ceb052af088c545330a1ffbf" +
"50615b8c7247c6cd386afd7440654f4e15bcfae0c45442ec814fe88433a9d616" +
"ee6cc3f163f0d3d325526d05f25d3b37ad5eeb3ca77248ad86c9042b16c65554" +
"aebb6ad3e17b981492b13f42c5a5dc088e991da303e5a273fdbb8601aece4267" +
"47b01f6cb972e6da1743a0d7866cf206e95f23c6f8e337c901b9cd34a9a1fbbe" +
"1694f2c26b00dfa4d02c0d54540163e798fbdc9c25f30d6406f5b4c13f7ed619" +
"34e350f4059c13aa5e973307a9e3058917cda96fdd082e9c629ccfb2a9f98d12" +
"5c6e4703a7b0f348f5cdeb63cef2133d1c6c1a087591e0a2bca29d09c6565e66" +
"e91042f83b0e74e60a5d57562c23e2fbcd6599c29d7c19e47cf625c2ce24bb8a" +
"13f8e54041498437eec2cedd1e3d8e57a051baa962c0a62d70264d99c5ee716d" +
"5c8b9078db08c8b2c5613f464198a7aff43f76c5b4612b46a4f1cd2a494386c5" +
"7fd28f3d199f0ba8d8e39116cc7db16ce6188205ee49a9dce3d4fa32ea394919" +
"f6e91ef58b84d00b99596b4306c2d9f432d917bb4ac73384c42ae12adb4920d8" +
"c33a816febcb299dcddf3ec7a8eb6e04cdc90891c6e145bd9fc5f41dc4061a46" +
"9feba38545b64ec8203f386ceef52785619e991d274ae80af7e54af535e0b011" +
"5effdf847472992875e09398457604d04e0bb965db692c0cdcf11a",
"687cc09c89298491deb51061d709af"),
// Randomly generated data at the time of execution.
new Data("AES", 5, "11754cd72aec309bf52f7687212e8957", 12345)
)
);
}
}

@ -0,0 +1,43 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/
import java.util.List;
/*
* @test
* @summary Uses GCMBufferTest to run a long test with incrementing through
* each byte in each byte array
* @run main/manual GCMIncrementByte4
*/
public class GCMIncrementByte4 {
public static void main(String args[]) throws Exception {
GCMBufferTest.initTest();
new GCMBufferTest("AES/GCM/NoPadding",
List.of(GCMBufferTest.dtype.BYTE, GCMBufferTest.dtype.BYTE,
GCMBufferTest.dtype.BYTE)).incrementalSegments().dataSet(4).
test();
}
}

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/
import java.util.List;
/*
* @test
* @summary Uses GCMBufferTest to run a long test with incrementing through
* each byte in each direct bytebuffer
* @run main/manual GCMIncrementDirect4
*/
public class GCMIncrementDirect4 {
public static void main(String args[]) throws Exception {
new GCMBufferTest("AES/GCM/NoPadding",
List.of(GCMBufferTest.dtype.DIRECT, GCMBufferTest.dtype.DIRECT,
GCMBufferTest.dtype.DIRECT)).incrementalSegments().dataSet(4).
test();
}
}

@ -0,0 +1,159 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
/*
* @test
* @summary This tests overlapping buffers using ByteBuffer.slice() with
* array-backed ByteBuffer, read only array-backed, ByteBuffer, and direct
* ByteBuffer.
*/
/*
* This tests overlapping buffers created with ByteBuffer.slice(). That is
* when the input and output ByteBuffers have shared memory (use the same
* underlying buffer space, commonly used for in-place crypto). The
* complication is the Cipher object specifies that it must be copy-safe. That
* means the output buffer will not overwrite any input data that has not been
* processed. If the output buffer's position or offset is greater than the
* input's overwriting will occur.
*/
public class OverlapByteBuffer {
public static void main(String[] args) throws Exception {
byte[] baseBuf = new byte[8192];
ByteBuffer output, input, in;
// Output offset from the baseBuf
int outOfs;
for (int i = 0; i < 3; i++) {
for (outOfs = -1; outOfs <= 1; outOfs++) {
SecretKeySpec key = new SecretKeySpec(new byte[16], "AES");
GCMParameterSpec params =
new GCMParameterSpec(128, new byte[12]);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, params);
// Offset on the particular ByteBuffer (aka position())
int inOfsInBuf = 1;
int outOfsInBuf = inOfsInBuf + outOfs;
int sliceLen = cipher.getOutputSize(baseBuf.length);
int bufferSize = sliceLen + Math.max(inOfsInBuf, outOfsInBuf);
byte[] buffer;
// Create overlapping input and output buffers
switch (i) {
case 0 -> {
buffer = new byte[bufferSize];
output = ByteBuffer.wrap(buffer, outOfsInBuf, sliceLen).
slice();
input = ByteBuffer.wrap(buffer, inOfsInBuf, sliceLen).
slice();
System.out.println("Using array-backed ByteBuffer");
in = input.duplicate();
}
case 1 -> {
buffer = new byte[bufferSize];
output = ByteBuffer.wrap(buffer, outOfsInBuf, sliceLen).
slice();
input = ByteBuffer.wrap(buffer, inOfsInBuf, sliceLen).
slice();
System.out.println("Using read-only array-backed " + "ByteBuffer");
in = input.asReadOnlyBuffer();
}
case 2 -> {
System.out.println("Using direct ByteBuffer");
ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize);
output = buf.duplicate();
output.position(outOfsInBuf);
output.limit(sliceLen + outOfsInBuf);
output = output.slice();
input = buf.duplicate();
input.position(inOfsInBuf);
input.limit(sliceLen + inOfsInBuf);
input = input.slice();
in = input.duplicate();
}
default -> {
throw new Exception("Unknown index " + i);
}
}
// Copy data into shared buffer
input.put(baseBuf);
input.flip();
in.limit(input.limit());
try {
int ctSize = cipher.doFinal(in, output);
// Get ready to decrypt
byte[] tmp = new byte[ctSize];
output.flip();
output.get(tmp);
output.clear();
input.clear();
input.put(tmp);
input.flip();
in.clear();
in.limit(input.limit());
cipher.init(Cipher.DECRYPT_MODE, key, params);
cipher.doFinal(in, output);
output.flip();
System.out.println("inOfsInBuf = " + inOfsInBuf);
System.out.println("outOfsInBuf = " + outOfsInBuf);
ByteBuffer b = ByteBuffer.wrap(baseBuf);
if (b.compareTo(output) != 0) {
System.err.println(
"\nresult (" + output + "):\n" +
byteToHex(output) +
"\nexpected (" + b + "):\n" +
byteToHex(b));
throw new Exception("Mismatch");
}
} catch (Exception e) {
throw new Exception("Error with base offset " + outOfs, e);
}
}
}
}
private static String byteToHex(ByteBuffer bb) {
StringBuilder s = new StringBuilder();
while (bb.remaining() > 0) {
s.append(String.format("%02x", bb.get()));
}
return s.toString();
}
}

@ -29,12 +29,14 @@ import javax.crypto.SecretKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
import jdk.test.lib.Convert;
/*
* @test
* @bug 8048596
* @summary Check if AEAD operations work correctly when buffers used
* for storing plain text and cipher text are overlapped or the same
* @library /test/lib
*/
public class SameBuffer {
@ -255,6 +257,13 @@ public class SameBuffer {
// check if two resutls are equal
if (!isEqual(text, myoff, outputText, 0, outputText.length)) {
System.err.println(
"\noutputText: len = " + outputText.length + " txtOffset = " + txtOffset + "\n" +
jdk.test.lib.Convert.byteArrayToHexString(outputText) + "\n" +
"text: len = " + text.length + " myoff = " + myoff + "\n" +
jdk.test.lib.Convert.byteArrayToHexString(text) + "\n" +
"lenght " + lenght);
System.err.println("tlen = " + params.getParameterSpec(GCMParameterSpec.class).getTLen() / 8);
throw new RuntimeException("Two results not equal, mode:" + mode);
}
}
@ -387,6 +396,8 @@ public class SameBuffer {
int setB = i + offsetB;
if (setA > A.length - 1 || setB > B.length - 1
|| A[setA] != B[setB]) {
System.err.println("i = " + i + " A[setA] = " + A[setA] +
" B[setB] = " + B[setB]);
return false;
}
}

@ -33,6 +33,7 @@
*/
import java.nio.ByteBuffer;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
@ -55,6 +56,7 @@ public class TestKATForGCM {
}
private static class TestVector {
int id;
SecretKey key;
byte[] plainText;
byte[] aad;
@ -63,23 +65,41 @@ public class TestKATForGCM {
GCMParameterSpec spec;
String info;
TestVector(String key, String iv, String pt, String aad,
TestVector(int id, String key, String iv, String pt, String aad,
String ct, String tag) {
this.id = id;
this.key = new SecretKeySpec(HexToBytes(key), "AES");
this.plainText = HexToBytes(pt);
this.aad = HexToBytes(aad);
this.cipherText = HexToBytes(ct);
this.tag = HexToBytes(tag);
this.spec = new GCMParameterSpec(this.tag.length * 8, HexToBytes(iv));
this.info = "key=" + key + ", iv=" + iv + ", pt=" + pt +
this.info = "id = " + id + ", key=" + key + ", iv=" + iv + ", pt=" + pt +
",aad=" + aad + ", ct=" + ct + ", tag=" + tag;
}
TestVector() {};
TestVector duplicate() {
TestVector t = new TestVector();
t.id = id;
t.key = key;
t.plainText = plainText;
t.cipherText = cipherText;
t.aad = aad;
t.tag = tag;
t.spec = spec;
t.info = info;
return t;
}
public String toString() {
return info;
}
}
static boolean testFailed = false;
// These test vectors are found off NIST's CAVP page
// http://csrc.nist.gov/groups/STM/cavp/index.html
// inside the link named "GCM Test Vectors", i.e.
@ -88,53 +108,53 @@ public class TestKATForGCM {
private static TestVector[] testValues = {
// 96-bit iv w/ 128/120/112/104/96-bit tags
// no plain text, no aad
new TestVector("11754cd72aec309bf52f7687212e8957",
new TestVector(1, "11754cd72aec309bf52f7687212e8957",
"3c819d9a9bed087615030b65",
null, null, null,
"250327c674aaf477aef2675748cf6971"),
new TestVector("272f16edb81a7abbea887357a58c1917",
new TestVector(2, "272f16edb81a7abbea887357a58c1917",
"794ec588176c703d3d2a7a07",
null, null, null,
"b6e6f197168f5049aeda32dafbdaeb"),
new TestVector("81b6844aab6a568c4556a2eb7eae752f",
new TestVector(3, "81b6844aab6a568c4556a2eb7eae752f",
"ce600f59618315a6829bef4d",
null, null, null,
"89b43e9dbc1b4f597dbbc7655bb5"),
new TestVector("cde2f9a9b1a004165ef9dc981f18651b",
new TestVector(4, "cde2f9a9b1a004165ef9dc981f18651b",
"29512c29566c7322e1e33e8e",
null, null, null,
"2e58ce7dabd107c82759c66a75"),
new TestVector("b01e45cc3088aaba9fa43d81d481823f",
new TestVector(5, "b01e45cc3088aaba9fa43d81d481823f",
"5a2c4a66468713456a4bd5e1",
null, null, null,
"014280f944f53c681164b2ff"),
// 96-bit iv w/ 128/120/112/104/96-bit tags
// no plain text, 16-byte aad
new TestVector("77be63708971c4e240d1cb79e8d77feb",
new TestVector(6, "77be63708971c4e240d1cb79e8d77feb",
"e0e00f19fed7ba0136a797f3",
null,
"7a43ec1d9c0a5a78a0b16533a6213cab",
null,
"209fcc8d3675ed938e9c7166709dd946"),
new TestVector("da0b615656135194ba6d3c851099bc48",
new TestVector(7, "da0b615656135194ba6d3c851099bc48",
"d39d4b4d3cc927885090e6c3",
null,
"e7e5e6f8dac913036cb2ff29e8625e0e",
null,
"ab967711a5770461724460b07237e2"),
new TestVector("7e0986937a88eef894235aba4a2f43b2",
new TestVector(8, "7e0986937a88eef894235aba4a2f43b2",
"92c4a631695907166b422d60",
null,
"85c185f8518f9f2cd597a8f9208fc76b",
null,
"3bb916b728df94fe9d1916736be1"),
new TestVector("c3db570d7f0c21e86b028f11465d1dc9",
new TestVector(9, "c3db570d7f0c21e86b028f11465d1dc9",
"f86970f58ceef89fc7cb679e",
null,
"c095240708c0f57c288d86090ae34ee1",
null,
"e043c52160d652e82c7262fcf4"),
new TestVector("bea48ae4980d27f357611014d4486625",
new TestVector(10, "bea48ae4980d27f357611014d4486625",
"32bddb5c3aa998a08556454c",
null,
"8a50b0b8c7654bced884f7f3afda2ead",
@ -142,56 +162,56 @@ public class TestKATForGCM {
"8e0f6d8bf05ffebe6f500eb1"),
// 96-bit iv w/ 128/120/112/104/96-bit tags
// no plain text, 20-byte aad
new TestVector("2fb45e5b8f993a2bfebc4b15b533e0b4",
new TestVector(11, "2fb45e5b8f993a2bfebc4b15b533e0b4",
"5b05755f984d2b90f94b8027",
null,
"e85491b2202caf1d7dce03b97e09331c32473941",
null,
"c75b7832b2a2d9bd827412b6ef5769db"),
new TestVector("9bf406339fcef9675bbcf156aa1a0661",
new TestVector(12, "9bf406339fcef9675bbcf156aa1a0661",
"8be4a9543d40f542abacac95",
null,
"7167cbf56971793186333a6685bbd58d47d379b3",
null,
"5e7968d7bbd5ba58cfcc750e2ef8f1"),
new TestVector("a2e962fff70fd0f4d63be728b80556fc",
new TestVector(13, "a2e962fff70fd0f4d63be728b80556fc",
"1fa7103483de43d09bc23db4",
null,
"2a58edf1d53f46e4e7ee5e77ee7aeb60fc360658",
null,
"fa37f2dbbefab1451eae1d0d74ca"),
new TestVector("6bf4fdce82926dcdfc52616ed5f23695",
new TestVector(14, "6bf4fdce82926dcdfc52616ed5f23695",
"cc0f5899a10615567e1193ed",
null,
"3340655592374c1da2f05aac3ee111014986107f",
null,
"8ad3385cce3b5e7c985908192c"),
new TestVector("4df7a13e43c3d7b66b1a72fac5ba398e",
new TestVector(15, "4df7a13e43c3d7b66b1a72fac5ba398e",
"97179a3a2d417908dcf0fb28",
null,
"cbb7fc0010c255661e23b07dbd804b1e06ae70ac",
null,
"37791edae6c137ea946cfb40"),
// 96-bit iv w/ 128-bit tags, 13/16/32/51-byte plain text, no aad
new TestVector("fe9bb47deb3a61e423c2231841cfd1fb",
new TestVector(16, "fe9bb47deb3a61e423c2231841cfd1fb",
"4d328eb776f500a2f7fb47aa",
"f1cc3818e421876bb6b8bbd6c9",
null,
"b88c5c1977b35b517b0aeae967",
"43fd4727fe5cdb4b5b42818dea7ef8c9"),
new TestVector("7fddb57453c241d03efbed3ac44e371c",
new TestVector(17, "7fddb57453c241d03efbed3ac44e371c",
"ee283a3fc75575e33efd4887",
"d5de42b461646c255c87bd2962d3b9a2",
null,
"2ccda4a5415cb91e135c2a0f78c9b2fd",
"b36d1df9b9d5e596f83e8b7f52971cb3"),
new TestVector("9971071059abc009e4f2bd69869db338",
new TestVector(18, "9971071059abc009e4f2bd69869db338",
"07a9a95ea3821e9c13c63251",
"f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983",
null,
"0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01",
"7870d9117f54811a346970f1de090c41"),
new TestVector("594157ec4693202b030f33798b07176d",
new TestVector(19, "594157ec4693202b030f33798b07176d",
"49b12054082660803a1df3df",
"3feef98a976a1bd634f364ac428bb59cd51fb159ec1789946918dbd50ea6c9d594a3a31a5269b0da6936c29d063a5fa2cc8a1c",
@ -200,26 +220,26 @@ public class TestKATForGCM {
"c1b7a46a335f23d65b8db4008a49796906e225474f4fe7d39e55bf2efd97fd82d4167de082ae30fa01e465a601235d8d68bc69",
"ba92d3661ce8b04687e8788d55417dc2"),
// 96-bit iv w/ 128-bit tags, 16-byte plain text, 16/20/48/90-byte aad
new TestVector("c939cc13397c1d37de6ae0e1cb7c423c",
new TestVector(20, "c939cc13397c1d37de6ae0e1cb7c423c",
"b3d8cc017cbb89b39e0f67e2",
"c3b3c41f113a31b73d9a5cd432103069",
"24825602bd12a984e0092d3e448eda5f",
"93fe7d9e9bfd10348a5606e5cafa7354",
"0032a1dc85f1c9786925a2e71d8272dd"),
new TestVector("d4a22488f8dd1d5c6c19a7d6ca17964c",
new TestVector(21, "d4a22488f8dd1d5c6c19a7d6ca17964c",
"f3d5837f22ac1a0425e0d1d5",
"7b43016a16896497fb457be6d2a54122",
"f1c5d424b83f96c6ad8cb28ca0d20e475e023b5a",
"c2bd67eef5e95cac27e3b06e3031d0a8",
"f23eacf9d1cdf8737726c58648826e9c"),
new TestVector("89850dd398e1f1e28443a33d40162664",
new TestVector(22, "89850dd398e1f1e28443a33d40162664",
"e462c58482fe8264aeeb7231",
"2805cdefb3ef6cc35cd1f169f98da81a",
"d74e99d1bdaa712864eec422ac507bddbe2b0d4633cd3dff29ce5059b49fe868526c59a2a3a604457bc2afea866e7606",
"ba80e244b7fc9025cd031d0f63677e06",
"d84a8c3eac57d1bb0e890a8f461d1065"),
new TestVector("bd7c5c63b7542b56a00ebe71336a1588",
new TestVector(23, "bd7c5c63b7542b56a00ebe71336a1588",
"87721f23ba9c3c8ea5571abc",
"de15ddbb1e202161e8a79af6a55ac6f3",
@ -227,17 +247,17 @@ public class TestKATForGCM {
"41eb28c0fee4d762de972361c863bc80",
"9cb567220d0b252eb97bff46e4b00ff8"),
// 8/1024-bit iv w/ 128-bit tag, no plain text, no aad
new TestVector("1672c3537afa82004c6b8a46f6f0d026",
new TestVector(24, "1672c3537afa82004c6b8a46f6f0d026",
"05",
null, null, null,
"8e2ad721f9455f74d8b53d3141f27e8e"),
new TestVector("d0f1f4defa1e8c08b4b26d576392027c",
new TestVector(25, "d0f1f4defa1e8c08b4b26d576392027c",
"42b4f01eb9f5a1ea5b1eb73b0fb0baed54f387ecaa0393c7d7dffc6af50146ecc021abf7eb9038d4303d91f8d741a11743166c0860208bcc02c6258fd9511a2fa626f96d60b72fcff773af4e88e7a923506e4916ecbd814651e9f445adef4ad6a6b6c7290cc13b956130eef5b837c939fcac0cbbcc9656cd75b13823ee5acdac",
null, null, null,
"7ab49b57ddf5f62c427950111c5c4f0d"),
// 8-bit iv w/ 128-bit tag, 13-byte plain text, 90-byte aad
new TestVector("9f79239f0904eace50784b863e723f6b",
new TestVector(26, "9f79239f0904eace50784b863e723f6b",
"d9",
"bdb0bb10c87965acd34d146171",
@ -245,7 +265,7 @@ public class TestKATForGCM {
"7e5a7c8dadb3f0c7335b4d9d8d",
"6b6ef1f53723a89f3bb7c6d043840717"),
// 1024-bit iv w/ 128-bit tag, 51-byte plain text, 48-byte aad
new TestVector("141f1ce91989b07e7eb6ae1dbd81ea5e",
new TestVector(27, "141f1ce91989b07e7eb6ae1dbd81ea5e",
"49451da24bd6074509d3cebc2c0394c972e6934b45a1d91f3ce1d3ca69e194aa1958a7c21b6f21d530ce6d2cc5256a3f846b6f9d2f38df0102c4791e57df038f6e69085646007df999751e248e06c47245f4cd3b8004585a7470dee1690e9d2d63169a58d243c0b57b3e5b4a481a3e4e8c60007094ef3adea2e8f05dd3a1396f",
@ -257,56 +277,139 @@ public class TestKATForGCM {
"dbb93bbb56d0439cd09f620a57687f5d"),
};
public boolean execute(TestVector[] testValues) throws Exception {
boolean testFailed = false;
void executeArray(TestVector tv) throws Exception {
Cipher c = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
for (int i = 0; i < testValues.length; i++) {
try {
c.init(Cipher.ENCRYPT_MODE, testValues[i].key, testValues[i].spec);
c.updateAAD(testValues[i].aad);
byte[] ctPlusTag = c.doFinal(testValues[i].plainText);
try {
System.out.println("Test #" + tv.id + ": byte[].");
c.init(Cipher.DECRYPT_MODE, testValues[i].key, testValues[i].spec);
c.updateAAD(testValues[i].aad);
byte[] pt = c.doFinal(ctPlusTag); // should fail if tag mismatched
c.init(Cipher.ENCRYPT_MODE, tv.key, tv.spec);
c.updateAAD(tv.aad);
byte[] ctPlusTag = c.doFinal(tv.plainText);
// check encryption/decryption results just to be sure
if (!Arrays.equals(testValues[i].plainText, pt)) {
System.out.println("PlainText diff failed for test# " + i);
testFailed = true;
}
int ctLen = testValues[i].cipherText.length;
if (!Arrays.equals(testValues[i].cipherText,
Arrays.copyOf(ctPlusTag, ctLen))) {
System.out.println("CipherText diff failed for test# " + i);
testFailed = true;
}
int tagLen = testValues[i].tag.length;
if (!Arrays.equals
(testValues[i].tag,
Arrays.copyOfRange(ctPlusTag, ctLen, ctLen+tagLen))) {
System.out.println("Tag diff failed for test# " + i);
testFailed = true;
}
} catch (Exception ex) {
// continue testing other test vectors
System.out.println("Failed Test Vector: " + testValues[i]);
ex.printStackTrace();
c.init(Cipher.DECRYPT_MODE, tv.key, tv.spec);
c.updateAAD(tv.aad);
byte[] pt = c.doFinal(ctPlusTag); // should fail if tag mismatched
// check encryption/decryption results just to be sure
if (!Arrays.equals(tv.plainText, pt)) {
System.out.println("PlainText diff failed for test# " + tv.id);
testFailed = true;
continue;
}
int ctLen = tv.cipherText.length;
if (!Arrays.equals(tv.cipherText,
Arrays.copyOf(ctPlusTag, ctLen))) {
System.out.println("CipherText diff failed for test# " + tv.id);
testFailed = true;
}
int tagLen = tv.tag.length;
if (!Arrays.equals
(tv.tag,
Arrays.copyOfRange(ctPlusTag, ctLen, ctLen+tagLen))) {
System.out.println("Tag diff failed for test# " + tv.id);
testFailed = true;
}
} catch (Exception ex) {
// continue testing other test vectors
System.out.println("Failed Test Vector: " + tv);
ex.printStackTrace();
testFailed = true;
}
if (testFailed) {
throw new Exception("Test Failed");
}
// passed all tests...hooray!
return true;
}
void executeByteBuffer(TestVector tv, boolean direct, int offset) throws Exception {
Cipher c = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
ByteBuffer src;
ByteBuffer ctdst;
ByteBuffer ptdst;
if (direct) {
System.out.print("Test #" + tv.id + ": ByteBuffer Direct.");
src = ByteBuffer.allocateDirect(tv.plainText.length + offset);
ctdst = ByteBuffer.allocateDirect(tv.cipherText.length + tv.tag.length + offset);
ptdst = ByteBuffer.allocateDirect(tv.plainText.length + offset);
} else {
System.out.print("Test #" + tv.id + ": ByteBuffer Heap.");
src = ByteBuffer.allocate(tv.plainText.length + offset);
ctdst = ByteBuffer.allocate(tv.cipherText.length + tv.tag.length + offset);
ptdst = ByteBuffer.allocate(tv.plainText.length + offset);
}
byte[] plainText;
if (offset > 0) {
System.out.println(" offset = " + offset);
plainText = new byte[tv.plainText.length + offset];
System.arraycopy(tv.plainText, 0, plainText, offset,
tv.plainText.length);
} else {
System.out.println();
plainText = tv.plainText;
}
src.put(plainText);
src.position(offset);
ctdst.position(offset);
ctdst.mark();
ptdst.position(offset);
ptdst.mark();
try {
c.init(Cipher.ENCRYPT_MODE, tv.key, tv.spec);
c.updateAAD(tv.aad);
c.doFinal(src, ctdst);
ctdst.reset();
ByteBuffer tag = ctdst.duplicate();
tag.position(tag.limit() - tv.tag.length);
c.init(Cipher.DECRYPT_MODE, tv.key, tv.spec);
c.updateAAD(tv.aad);
c.doFinal(ctdst, ptdst); // should fail if tag mismatched
ptdst.reset();
// check encryption/decryption results just to be sure
if (ptdst.compareTo(ByteBuffer.wrap(tv.plainText)) != 0) {
System.out.println("\t PlainText diff failed for test# " + tv.id);
testFailed = true;
}
ctdst.reset();
ctdst.limit(ctdst.limit() - tv.tag.length);
if (ctdst.compareTo(ByteBuffer.wrap(tv.cipherText)) != 0) {
System.out.println("\t CipherText diff failed for test# " + tv.id);
testFailed = true;
}
int mismatch = 0;
for (int i = 0; i < tv.tag.length; i++) {
mismatch |= tag.get() ^ tv.tag[i];
}
if (mismatch != 0) {
System.out.println("\t Tag diff failed for test# " + tv.id);
testFailed = true;
}
} catch (Exception ex) {
// continue testing other test vectors
System.out.println("\t Failed Test Vector ( #" + tv.id + ") : " + tv);
ex.printStackTrace();
}
}
public static void main (String[] args) throws Exception {
TestKATForGCM test = new TestKATForGCM();
if (test.execute(testValues)) {
System.out.println("Test Passed!");
for (TestVector tv : testValues) {
test.executeArray(tv);
test.executeByteBuffer(tv, false, 0);
test.executeByteBuffer(tv, true, 0);
test.executeByteBuffer(tv, false, 2);
test.executeByteBuffer(tv, true, 2);
}
if (!testFailed) {
System.out.println("Tests passed");
}
}
}

@ -0,0 +1,128 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.AlgorithmParameters;
import java.util.Arrays;
/*
* @test
* @summary Verify when decrypting over an existing buffer than padding does not
* overwrite past what the plaintext length is.
*
*/
public class SameBufferOverwrite {
private SecretKey skey;
private Cipher c;
private int start = 17, end = 17; // default
SameBufferOverwrite(String algo, String transformation)
throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(algo, "SunJCE");
skey = kg.generateKey();
c = Cipher.getInstance(transformation, "SunJCE");
}
/*
* Run the test
*/
void test() throws Exception {
byte[] in = new byte[end + (c.getBlockSize() - (end % c.getBlockSize()))];
Arrays.fill(in, (byte)8);
int len = start;
AlgorithmParameters params = null;
System.out.println("Testing transformation: " + c.getAlgorithm() +
", byte length from " + start + " to " + end);
while (end >= len) {
// encrypt
c.init(Cipher.ENCRYPT_MODE, skey, params);
byte[] out = c.doFinal(in, 0, len);
System.out.println(" enc = " + byteToHex(out));
System.out.println(" => enc " + len + " bytes, ret " +
(out == null ? "null" : (out.length + " byte")));
// decrypt
params = c.getParameters();
c.init(Cipher.DECRYPT_MODE, skey, params);
int rLen = c.doFinal(out, 0, out.length, in);
System.out.println(" dec = " + byteToHex(in));
System.out.println(" => dec " + out.length + " bytes, ret " +
rLen + " byte");
// check if more than rLen bytes are written into 'in'
for (int j = rLen; j < in.length; j++) {
if (in[j] != (byte) 8) {
throw new Exception("Value check failed at index " + j);
}
}
System.out.println(" Test Passed: len = " + len);
len++;
// Because GCM doesn't allow params reuse
if (c.getAlgorithm().contains("GCM")) {
params = null;
}
}
}
/**
* Builder method for the test to run data lengths from the start value to
* the end value. To do one length, have start and end equal that number.
* @param start starting data length
* @param end ending data length
*/
SameBufferOverwrite iterate(int start, int end) {
this.start = start;
this.end = end;
return this;
}
public static void main(String args[]) throws Exception {
new SameBufferOverwrite("AES", "AES/GCM/NoPadding").iterate(1, 25).
test();
new SameBufferOverwrite("AES", "AES/CTR/NoPadding").iterate(1, 25).
test();
new SameBufferOverwrite("AES", "AES/CBC/PKCS5Padding").iterate(1, 25).
test();
new SameBufferOverwrite("AES", "AES/ECB/PKCS5Padding").iterate(1, 25).
test();
new SameBufferOverwrite("DES", "DES/CBC/PKCS5Padding").iterate(1, 17).
test();
new SameBufferOverwrite("DESede", "DESede/CBC/PKCS5Padding").iterate(1, 17).
test();
}
private static String byteToHex(byte[] barray) {
StringBuilder s = new StringBuilder();
for (byte b : barray) {
s.append(String.format("%02x", b));
}
return s.toString();
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 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
@ -26,15 +26,21 @@
* @bug 8181386
* @summary CipherSpi ByteBuffer to byte array conversion fails for
* certain data overlap conditions
* @run main CipherByteBufferOverwriteTest 0 false
* @run main CipherByteBufferOverwriteTest 0 true
* @run main CipherByteBufferOverwriteTest 4 false
* @run main CipherByteBufferOverwriteTest 4 true
* @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 0 false
* @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 0 true
* @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 4 false
* @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 4 true
* @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 0 false
* @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 0 true
* @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 4 false
* @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 4 true
*/
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
@ -44,7 +50,7 @@ public class CipherByteBufferOverwriteTest {
private static final boolean DEBUG = false;
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static String transformation;
// must be larger than the temp array size, i.e. 4096, hardcoded in
// javax.crypto.CipherSpi class
@ -53,8 +59,7 @@ public class CipherByteBufferOverwriteTest {
private static final int CIPHERTEXT_BUFFER_SIZE = PLAINTEXT_SIZE + 32;
private static final SecretKey KEY = new SecretKeySpec(new byte[16], "AES");
private static final AlgorithmParameterSpec PARAMS =
new IvParameterSpec(new byte[16]);
private static AlgorithmParameterSpec params;
private static ByteBuffer inBuf;
private static ByteBuffer outBuf;
@ -65,9 +70,15 @@ public class CipherByteBufferOverwriteTest {
public static void main(String[] args) throws Exception {
int offset = Integer.parseInt(args[0]);
boolean useRO = Boolean.parseBoolean(args[1]);
transformation = args[0];
int offset = Integer.parseInt(args[1]);
boolean useRO = Boolean.parseBoolean(args[2]);
if (transformation.equalsIgnoreCase("AES/GCM/NoPadding")) {
params = new GCMParameterSpec(16 * 8, new byte[16]);
} else {
params = new IvParameterSpec(new byte[16]);
}
// an all-zeros plaintext is the easiest way to demonstrate the issue,
// but it fails with any plaintext, of course
byte[] expectedPT = new byte[PLAINTEXT_SIZE];
@ -75,8 +86,8 @@ public class CipherByteBufferOverwriteTest {
System.arraycopy(expectedPT, 0, buf, 0, PLAINTEXT_SIZE);
// generate expected cipher text using byte[] methods
Cipher c = Cipher.getInstance(TRANSFORMATION);
c.init(Cipher.ENCRYPT_MODE, KEY, PARAMS);
Cipher c = Cipher.getInstance(transformation);
c.init(Cipher.ENCRYPT_MODE, KEY, params);
byte[] expectedCT = c.doFinal(expectedPT);
// Test#1: against ByteBuffer generated with allocate(int) call
@ -89,9 +100,9 @@ public class CipherByteBufferOverwriteTest {
// Test#2: against direct ByteBuffer
prepareBuffers(BufferType.DIRECT, useRO, buf.length,
buf, 0, PLAINTEXT_SIZE, offset);
System.out.println("\tDIRECT: passed");
runTest(offset, expectedPT, expectedCT);
System.out.println("\tDIRECT: passed");
// Test#3: against ByteBuffer wrapping existing array
prepareBuffers(BufferType.WRAP, useRO, buf.length,
@ -150,8 +161,8 @@ public class CipherByteBufferOverwriteTest {
private static void runTest(int ofs, byte[] expectedPT, byte[] expectedCT)
throws Exception {
Cipher c = Cipher.getInstance(TRANSFORMATION);
c.init(Cipher.ENCRYPT_MODE, KEY, PARAMS);
Cipher c = Cipher.getInstance(transformation);
c.init(Cipher.ENCRYPT_MODE, KEY, params);
int ciphertextSize = c.doFinal(inBuf, outBuf);
// read out the encrypted result
@ -166,14 +177,20 @@ public class CipherByteBufferOverwriteTest {
outBuf.get(finalCT);
if (!Arrays.equals(finalCT, expectedCT)) {
System.err.println("Ciphertext mismatch:" +
"\nresult (len=" + finalCT.length + "):\n" +
String.format("%0" + (finalCT.length << 1) + "x",
new BigInteger(1, finalCT)) +
"\nexpected (len=" + expectedCT.length + "):\n" +
String.format("%0" + (expectedCT.length << 1) + "x",
new BigInteger(1, expectedCT)));
throw new Exception("ERROR: Ciphertext does not match");
}
// now do decryption
outBuf.position(ofs);
outBuf.limit(ofs + ciphertextSize);
c.init(Cipher.DECRYPT_MODE, KEY, PARAMS);
c.init(Cipher.DECRYPT_MODE, KEY, params);
ByteBuffer finalPTBuf = ByteBuffer.allocate(
c.getOutputSize(outBuf.remaining()));
c.doFinal(outBuf, finalPTBuf);
@ -184,6 +201,13 @@ public class CipherByteBufferOverwriteTest {
finalPTBuf.get(finalPT);
if (!Arrays.equals(finalPT, expectedPT)) {
System.err.println("Ciphertext mismatch " +
"):\nresult (len=" + finalCT.length + "):\n" +
String.format("%0" + (finalCT.length << 1) + "x",
new BigInteger(1, finalCT)) +
"\nexpected (len=" + expectedCT.length + "):\n" +
String.format("%0" + (expectedCT.length << 1) + "x",
new BigInteger(1, expectedCT)));
throw new Exception("ERROR: Plaintext does not match");
}
}

@ -31,11 +31,21 @@
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false CheckSessionContext
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext
* @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext
*
*/
import javax.net.ssl.SSLSession;
public class CheckSessionContext {
static void toHex(byte[] id) {
for (byte b : id) {
System.out.printf("%02X ", b);
}
System.out.println();
}
public static void main(String[] args) throws Exception {
TLSBase.Server server = new TLSBase.Server();
@ -46,6 +56,17 @@ public class CheckSessionContext {
} else {
System.out.println("Context was found");
}
SSLSession ss = server.getSession(client1);
System.out.println(ss);
byte[] id = ss.getId();
System.out.print("id = ");
toHex(id);
System.out.println("ss.getSessionContext().getSession(id) = " + ss.getSessionContext().getSession(id));
if (ss.getSessionContext().getSession(id) != null) {
id = ss.getSessionContext().getSession(id).getId();
System.out.print("id = ");
toHex(id);
}
server.close(client1);
client1.close();