8253821: Improve ByteBuffer performance with GCM
Reviewed-by: xuelei, valeriep
This commit is contained in:
parent
3da30e991a
commit
cc1915b3b3
src/java.base/share/classes/com/sun/crypto/provider
test/jdk
com/sun/crypto/provider/Cipher
AEAD
GCMBufferTest.javaGCMIncrementByte4.javaGCMIncrementDirect4.javaOverlapByteBuffer.javaSameBuffer.java
AES
TextLength
javax
@ -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;
|
||||
}
|
||||
}
|
||||
|
851
test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
Normal file
851
test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
Normal file
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user