8012900: CICO ignores AAD in GCM mode
Summary: Change GCM decryption to not return result until tag verification passed
Reviewed-by: xuelei
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -135,9 +135,10 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the result
* @param cipherOffset the offset in <code>cipher</code>
+ * @return the length of the encrypted data
*/
- void encrypt(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encrypt(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
{
int i;
int endIndex = plainOffset + plainLen;
@@ -150,6 +151,7 @@
embeddedCipher.encryptBlock(k, 0, cipher, cipherOffset);
System.arraycopy(cipher, cipherOffset, r, 0, blockSize);
}
+ return plainLen;
}
/**
@@ -174,13 +176,14 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the result
* @param plainOffset the offset in <code>plain</code>
+ * @return the length of the decrypted data
*
* @exception IllegalBlockSizeException if input data whose length does
* not correspond to the embedded cipher's block size is passed to the
* embedded cipher
*/
- void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset)
+ int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset)
{
int i;
byte[] cipherOrig=null;
@@ -195,7 +198,6 @@
// the plaintext result.
cipherOrig = cipher.clone();
}
-
for (; cipherOffset < endIndex;
cipherOffset += blockSize, plainOffset += blockSize) {
embeddedCipher.decryptBlock(cipher, cipherOffset, k, 0);
@@ -208,5 +210,6 @@
System.arraycopy(cipherOrig, cipherOffset, r, 0, blockSize);
}
}
+ return cipherLen;
}
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java Tue Oct 08 11:17:53 2013 -0700
@@ -310,49 +310,20 @@
* @return the required output buffer size (in bytes)
*/
int getOutputSize(int inputLen) {
- int totalLen = buffered + inputLen;
-
- // GCM: this call may be for either update() or doFinal(), so have to
- // return the larger value of both
- // Encryption: based on doFinal value: inputLen + tag
- // Decryption: based on update value: inputLen
- if (!decrypting && (cipherMode == GCM_MODE)) {
- return (totalLen + ((GaloisCounterMode) cipher).getTagLen());
- }
-
- if (padding == null) {
- return totalLen;
- }
-
- if (decrypting) {
- return totalLen;
- }
-
- if (unitBytes != blockSize) {
- if (totalLen < diffBlocksize) {
- return diffBlocksize;
- } else {
- return (totalLen + blockSize -
- ((totalLen - diffBlocksize) % blockSize));
- }
- } else {
- return totalLen + padding.padLength(totalLen);
- }
+ // estimate based on the maximum
+ return getOutputSizeByOperation(inputLen, true);
}
private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
- int totalLen = 0;
+ int totalLen = buffered + inputLen + cipher.getBufferedLength();
switch (cipherMode) {
case GCM_MODE:
- totalLen = buffered + inputLen;
if (isDoFinal) {
int tagLen = ((GaloisCounterMode) cipher).getTagLen();
- if (decrypting) {
- // need to get the actual value from cipher??
- // deduct tagLen
+ if (!decrypting) {
+ totalLen += tagLen;
+ } else {
totalLen -= tagLen;
- } else {
- totalLen += tagLen;
}
}
if (totalLen < 0) {
@@ -360,8 +331,19 @@
}
break;
default:
- totalLen = getOutputSize(inputLen);
- break;
+ if (padding != null && !decrypting) {
+ if (unitBytes != blockSize) {
+ if (totalLen < diffBlocksize) {
+ totalLen = diffBlocksize;
+ } else {
+ int residue = (totalLen - diffBlocksize) % blockSize;
+ totalLen += (blockSize - residue);
+ }
+ } else {
+ totalLen += padding.padLength(totalLen);
+ }
+ }
+ break;
}
return totalLen;
}
@@ -729,36 +711,52 @@
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");
}
- if (len != 0) {
- // there is some work to do
- byte[] in = new byte[len];
-
- int inputConsumed = len - buffered;
- int bufferedConsumed = buffered;
- if (inputConsumed < 0) {
- inputConsumed = 0;
- bufferedConsumed = len;
+ int outLen = 0;
+ if (len != 0) { // there is some work to do
+ if (len <= buffered) {
+ // all to-be-processed data are from 'buffer'
+ if (decrypting) {
+ outLen = cipher.decrypt(buffer, 0, len, output, outputOffset);
+ } else {
+ outLen = cipher.encrypt(buffer, 0, len, output, outputOffset);
+ }
+ buffered -= len;
+ if (buffered != 0) {
+ System.arraycopy(buffer, len, buffer, 0, buffered);
+ }
+ } else { // len > buffered
+ if (buffered == 0) {
+ // all to-be-processed data are from 'input'
+ if (decrypting) {
+ outLen = cipher.decrypt(input, inputOffset, len, output, outputOffset);
+ } else {
+ outLen = cipher.encrypt(input, inputOffset, len, output, outputOffset);
+ }
+ inputOffset += len;
+ inputLen -= len;
+ } else {
+ // assemble the data using both 'buffer' and 'input'
+ byte[] in = new byte[len];
+ System.arraycopy(buffer, 0, in, 0, buffered);
+ int inConsumed = len - buffered;
+ System.arraycopy(input, inputOffset, in, buffered, inConsumed);
+ buffered = 0;
+ inputOffset += inConsumed;
+ inputLen -= inConsumed;
+ if (decrypting) {
+ outLen = cipher.decrypt(in, 0, len, output, outputOffset);
+ } else {
+ outLen = cipher.encrypt(in, 0, len, output, outputOffset);
+ }
+ }
}
-
- if (buffered != 0) {
- System.arraycopy(buffer, 0, in, 0, bufferedConsumed);
- }
- if (inputConsumed > 0) {
- System.arraycopy(input, inputOffset, in,
- bufferedConsumed, inputConsumed);
- }
- if (decrypting) {
- cipher.decrypt(in, 0, len, output, outputOffset);
- } else {
- cipher.encrypt(in, 0, len, output, outputOffset);
- }
-
// Let's keep track of how many bytes are needed to make
// the total input length a multiple of blocksize when
// padding is applied
@@ -770,23 +768,14 @@
((len - diffBlocksize) % blockSize);
}
}
-
- inputLen -= inputConsumed;
- inputOffset += inputConsumed;
- outputOffset += len;
- buffered -= bufferedConsumed;
- if (buffered > 0) {
- System.arraycopy(buffer, bufferedConsumed, buffer, 0,
- buffered);
- }
}
- // left over again
+ // Store remaining input into 'buffer' again
if (inputLen > 0) {
System.arraycopy(input, inputOffset, buffer, buffered,
inputLen);
+ buffered += inputLen;
}
- buffered += inputLen;
- return len;
+ return outLen;
}
/**
@@ -881,11 +870,24 @@
("Must use either different key or iv for GCM encryption");
}
- // calculate the total input length
- int totalLen = buffered + inputLen;
- int paddedLen = totalLen;
+ int estOutSize = getOutputSizeByOperation(inputLen, true);
+ // check output buffer capacity.
+ // if we are decrypting with padding applied, we can perform this
+ // check only after we have determined how many padding bytes there
+ // are.
+ int outputCapacity = output.length - outputOffset;
+ int minOutSize = (decrypting? (estOutSize - blockSize):estOutSize);
+ if ((output == null) || (outputCapacity < minOutSize)) {
+ throw new ShortBufferException("Output buffer must be "
+ + "(at least) " + minOutSize + " bytes long");
+ }
+
+ // calculate total input length
+ int len = buffered + inputLen;
+
+ // calculate padding length
+ int totalLen = len + cipher.getBufferedLength();
int paddingLen = 0;
-
// will the total input length be a multiple of blockSize?
if (unitBytes != blockSize) {
if (totalLen < diffBlocksize) {
@@ -898,40 +900,23 @@
paddingLen = padding.padLength(totalLen);
}
- if ((paddingLen > 0) && (paddingLen != blockSize) &&
- (padding != null) && decrypting) {
+ if (decrypting && (padding != null) &&
+ (paddingLen > 0) && (paddingLen != blockSize)) {
throw new IllegalBlockSizeException
("Input length must be multiple of " + blockSize +
" when decrypting with padded cipher");
}
- // if encrypting and padding not null, add padding
- if (!decrypting && padding != null) {
- paddedLen += paddingLen;
- }
-
- // check output buffer capacity.
- // if we are decrypting with padding applied, we can perform this
- // check only after we have determined how many padding bytes there
- // are.
- if (output == null) {
- throw new ShortBufferException("Output buffer is null");
- }
- int outputCapacity = output.length - outputOffset;
-
- if (((!decrypting) && (outputCapacity < paddedLen)) ||
- (decrypting && (outputCapacity < (paddedLen - blockSize)))) {
- throw new ShortBufferException("Output buffer too short: "
- + outputCapacity + " bytes given, "
- + paddedLen + " bytes needed");
- }
-
// prepare the final input avoiding copying if possible
byte[] finalBuf = input;
int finalOffset = inputOffset;
+ int finalBufLen = inputLen;
if ((buffered != 0) || (!decrypting && padding != null)) {
+ if (decrypting || padding == null) {
+ paddingLen = 0;
+ }
+ finalBuf = new byte[len + paddingLen];
finalOffset = 0;
- finalBuf = new byte[paddedLen];
if (buffered != 0) {
System.arraycopy(buffer, 0, finalBuf, 0, buffered);
}
@@ -939,50 +924,50 @@
System.arraycopy(input, inputOffset, finalBuf,
buffered, inputLen);
}
- if (!decrypting && padding != null) {
- padding.padWithLen(finalBuf, totalLen, paddingLen);
+ if (paddingLen != 0) {
+ padding.padWithLen(finalBuf, (buffered+inputLen), paddingLen);
}
+ finalBufLen = finalBuf.length;
}
-
+ int outLen = 0;
if (decrypting) {
// if the size of specified output buffer is less than
// the length of the cipher text, then the current
// content of cipher has to be preserved in order for
// users to retry the call with a larger buffer in the
// case of ShortBufferException.
- if (outputCapacity < paddedLen) {
+ if (outputCapacity < estOutSize) {
cipher.save();
}
// create temporary output buffer so that only "real"
// data bytes are passed to user's output buffer.
- byte[] outWithPadding = new byte[totalLen];
- totalLen = finalNoPadding(finalBuf, finalOffset, outWithPadding,
- 0, totalLen);
+ byte[] outWithPadding = new byte[estOutSize];
+ outLen = finalNoPadding(finalBuf, finalOffset, outWithPadding,
+ 0, finalBufLen);
if (padding != null) {
- int padStart = padding.unpad(outWithPadding, 0, totalLen);
+ int padStart = padding.unpad(outWithPadding, 0, outLen);
if (padStart < 0) {
throw new BadPaddingException("Given final block not "
+ "properly padded");
}
- totalLen = padStart;
+ outLen = padStart;
}
- if ((output.length - outputOffset) < totalLen) {
+ if (outputCapacity < outLen) {
// restore so users can retry with a larger buffer
cipher.restore();
throw new ShortBufferException("Output buffer too short: "
- + (output.length-outputOffset)
- + " bytes given, " + totalLen
+ + (outputCapacity)
+ + " bytes given, " + outLen
+ " bytes needed");
}
- for (int i = 0; i < totalLen; i++) {
- output[outputOffset + i] = outWithPadding[i];
- }
+ // copy the result into user-supplied output buffer
+ System.arraycopy(outWithPadding, 0, output, outputOffset, outLen);
} else { // encrypting
try {
- totalLen = finalNoPadding(finalBuf, finalOffset, output,
- outputOffset, paddedLen);
+ outLen = finalNoPadding(finalBuf, finalOffset, output,
+ outputOffset, finalBufLen);
} finally {
// reset after doFinal() for GCM encryption
requireReinit = (cipherMode == GCM_MODE);
@@ -994,12 +979,13 @@
if (cipherMode != ECB_MODE) {
cipher.reset();
}
- return totalLen;
+ return outLen;
}
private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
int len)
- throws IllegalBlockSizeException, AEADBadTagException {
+ throws IllegalBlockSizeException, AEADBadTagException,
+ ShortBufferException {
if ((cipherMode != GCM_MODE) && (in == null || len == 0)) {
return 0;
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -150,9 +150,10 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the result
* @param cipherOffset the offset in <code>cipher</code>
+ * @return the length of the encrypted data
*/
- void encrypt(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encrypt(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
{
int i, len;
len = blockSize - numBytes;
@@ -194,6 +195,7 @@
}
}
}
+ return plainLen;
}
/**
@@ -218,9 +220,10 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the result
* @param plainOffset the offset in <code>plain</code>
+ * @return the length of the decrypted data
*/
- void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset)
+ int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset)
{
int i, len;
len = blockSize - numBytes;
@@ -268,5 +271,6 @@
}
}
}
+ return cipherLen;
}
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 201313, 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
@@ -149,9 +149,10 @@
* @param len the length of the input data
* @param out the buffer for the result
* @param outOff the offset in <code>cipher</code>
+ * @return the length of the encrypted data
*/
- void encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- crypt(in, inOff, len, out, outOff);
+ int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ return crypt(in, inOff, len, out, outOff);
}
/**
@@ -176,9 +177,10 @@
* @param len the length of the input data
* @param out the buffer for the result
* @param outOff the offset in <code>plain</code>
+ * @return the length of the decrypted data
*/
- void decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- crypt(in, inOff, len, out, outOff);
+ int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ return crypt(in, inOff, len, out, outOff);
}
/**
@@ -197,7 +199,8 @@
* keystream generated by encrypting the counter values. Counter values
* are encrypted on demand.
*/
- private void crypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ private int crypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ int result = len;
while (len-- > 0) {
if (used >= blockSize) {
embeddedCipher.encryptBlock(counter, 0, encryptedCounter, 0);
@@ -206,5 +209,6 @@
}
out[outOff++] = (byte)(in[inOff++] ^ encryptedCounter[used++]);
}
+ return result;
}
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -115,14 +115,15 @@
* @param len the length of the input data
* @param out the buffer for the result
* @param outOff the offset in <code>cipher</code>
+ * @return the length of the encrypted data
*/
- void encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- while (len >= blockSize) {
+ int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ for (int i = len; i >= blockSize; i -= blockSize) {
embeddedCipher.encryptBlock(in, inOff, out, outOff);
- len -= blockSize;
inOff += blockSize;
outOff += blockSize;
}
+ return len;
}
/**
@@ -147,14 +148,14 @@
* @param len the length of the input data
* @param out the buffer for the result
* @param outOff the offset in <code>plain</code>
+ * @return the length of the decrypted data
*/
- void decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- while (len >= blockSize) {
+ int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+ for (int i = len; i >= blockSize; i -= blockSize) {
embeddedCipher.decryptBlock(in, inOff, out, outOff);
- len -= blockSize;
inOff += blockSize;
outOff += blockSize;
}
+ return len;
}
-
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java Tue Oct 08 11:17:53 2013 -0700
@@ -133,9 +133,10 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the encryption result
* @param cipherOffset the offset in <code>cipher</code>
+ * @return the number of bytes placed into <code>cipher</code>
*/
- abstract void encrypt(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset);
+ abstract int encrypt(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset);
/**
* Performs encryption operation for the last time.
*
@@ -154,10 +155,9 @@
*/
int encryptFinal(byte[] plain, int plainOffset, int plainLen,
byte[] cipher, int cipherOffset)
- throws IllegalBlockSizeException {
- encrypt(plain, plainOffset, plainLen, cipher, cipherOffset);
- return plainLen;
- }
+ throws IllegalBlockSizeException, ShortBufferException {
+ return encrypt(plain, plainOffset, plainLen, cipher, cipherOffset);
+ }
/**
* Performs decryption operation.
*
@@ -174,9 +174,10 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the decryption result
* @param plainOffset the offset in <code>plain</code>
+ * @return the number of bytes placed into <code>plain</code>
*/
- abstract void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset);
+ abstract int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset);
/**
* Performs decryption operation for the last time.
@@ -196,9 +197,9 @@
*/
int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
byte[] plain, int plainOffset)
- throws IllegalBlockSizeException, AEADBadTagException {
- decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
- return cipherLen;
+ throws IllegalBlockSizeException, AEADBadTagException,
+ ShortBufferException {
+ return decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
}
/**
@@ -228,4 +229,15 @@
void updateAAD(byte[] src, int offset, int len) {
throw new IllegalStateException("No AAD accepted");
}
+
+ /**
+ * @return the number of bytes that are buffered internally inside
+ * this FeedbackCipher instance.
+ * @since 1.8
+ */
+ int getBufferedLength() {
+ // Currently only AEAD cipher impl, e.g. GCM, buffers data
+ // internally during decryption mode
+ return 0;
+ }
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java Tue Oct 08 11:17:53 2013 -0700
@@ -54,7 +54,7 @@
private byte[] counter;
// needed for save/restore calls
- private byte[] counterSave;
+ private byte[] counterSave = null;
// NOTE: cipher should already be initialized
GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
@@ -98,17 +98,16 @@
throw new IllegalBlockSizeException("Negative input size!");
} else if (inLen > 0) {
int lastBlockSize = inLen % AES_BLOCK_SIZE;
+ int completeBlkLen = inLen - lastBlockSize;
// process the complete blocks first
- update(in, inOfs, inLen - lastBlockSize, out, outOfs);
+ update(in, inOfs, completeBlkLen, out, outOfs);
if (lastBlockSize != 0) {
// do the last partial block
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
aes.encryptBlock(counter, 0, encryptedCntr, 0);
-
- int processed = inLen - lastBlockSize;
for (int n = 0; n < lastBlockSize; n++) {
- out[outOfs + processed + n] =
- (byte) ((in[inOfs + processed + n] ^
+ out[outOfs + completeBlkLen + n] =
+ (byte) ((in[inOfs + completeBlkLen + n] ^
encryptedCntr[n]));
}
}
@@ -120,12 +119,11 @@
}
/**
- * Resets the current counter to its initial value.
- * This is used after the doFinal() is called so this object can be
- * reused w/o explicit re-initialization.
+ * Resets the content of this object to when it's first constructed.
*/
void reset() {
System.arraycopy(icb, 0, counter, 0, icb.length);
+ counterSave = null;
}
/**
--- a/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java Tue Oct 08 11:17:53 2013 -0700
@@ -35,10 +35,12 @@
* This class represents ciphers in GaloisCounter (GCM) mode.
*
* <p>This mode currently should only be used w/ AES cipher.
- * Although no checking is done here, caller should only
- * pass AES Cipher to the constructor.
+ * Although no checking is done, caller should only pass AES
+ * Cipher to the constructor.
*
- * <p>NOTE: This class does not deal with buffering or padding.
+ * <p>NOTE: Unlike other modes, when used for decryption, this class
+ * will buffer all processed outputs internally and won't return them
+ * until the tag has been successfully verified.
*
* @since 1.8
*/
@@ -51,6 +53,9 @@
private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
private int sizeOfAAD = 0;
+ // buffer for storing input in decryption, not used for encryption
+ private ByteArrayOutputStream ibuffer = null;
+
// in bytes; need to convert to bits (default value 128) when needed
private int tagLenBytes = DEFAULT_TAG_LEN;
@@ -68,12 +73,14 @@
// additional variables for save/restore calls
private byte[] aadBufferSave = null;
private int sizeOfAADSave = 0;
+ private byte[] ibufferSave = null;
private int processedSave = 0;
// value must be 16-byte long; used by GCTR and GHASH as well
static void increment32(byte[] value) {
if (value.length != AES_BLOCK_SIZE) {
- throw new RuntimeException("Unexpected counter block length");
+ // should never happen
+ throw new ProviderException("Illegal counter block length");
}
// start from last byte and only go over 4 bytes, i.e. total 32 bits
int n = value.length - 1;
@@ -171,6 +178,9 @@
if (ghashAllToS != null) ghashAllToS.reset();
processed = 0;
sizeOfAAD = 0;
+ if (ibuffer != null) {
+ ibuffer.reset();
+ }
}
/**
@@ -184,6 +194,9 @@
null : aadBuffer.toByteArray());
if (gctrPAndC != null) gctrPAndC.save();
if (ghashAllToS != null) ghashAllToS.save();
+ if (ibuffer != null) {
+ ibufferSave = ibuffer.toByteArray();
+ }
}
/**
@@ -198,8 +211,12 @@
aadBuffer.write(aadBufferSave, 0, aadBufferSave.length);
}
}
- if (gctrPAndC != null) gctrPAndC.restore();
- if (ghashAllToS != null) ghashAllToS.restore();
+ if (gctrPAndC != null) gctrPAndC.restore();
+ if (ghashAllToS != null) ghashAllToS.restore();
+ if (ibuffer != null) {
+ ibuffer.reset();
+ ibuffer.write(ibufferSave, 0, ibufferSave.length);
+ }
}
/**
@@ -261,6 +278,9 @@
}
processed = 0;
sizeOfAAD = 0;
+ if (decrypting) {
+ ibuffer = new ByteArrayOutputStream();
+ }
}
/**
@@ -299,7 +319,7 @@
// Feed the AAD data to GHASH, pad if necessary
void processAAD() {
- if (aadBuffer != null) {
+ if (aadBuffer != null && aadBuffer.size() > 0) {
byte[] aad = aadBuffer.toByteArray();
sizeOfAAD = aad.length;
aadBuffer = null;
@@ -365,13 +385,14 @@
* @param out the buffer for the result
* @param outOfs the offset in <code>out</code>
*/
- void encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+ int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
processAAD();
if (len > 0) {
gctrPAndC.update(in, inOfs, len, out, outOfs);
processed += len;
ghashAllToS.update(out, outOfs, len);
}
+ return len;
}
/**
@@ -387,28 +408,28 @@
* @param outOfs the offset in <code>out</code>
* @return the number of bytes placed into the <code>out</code> buffer
*/
- int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
- throws IllegalBlockSizeException {
- if (out.length - outOfs < (len + tagLenBytes)) {
- throw new RuntimeException("Output buffer too small");
- }
+ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
+ throws IllegalBlockSizeException, ShortBufferException {
+ if (out.length - outOfs < (len + tagLenBytes)) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+
+ processAAD();
+ if (len > 0) {
+ doLastBlock(in, inOfs, len, out, outOfs, true);
+ }
- processAAD();
- if (len > 0) {
- //ByteUtil.dumpArray(Arrays.copyOfRange(in, inOfs, inOfs + len));
- doLastBlock(in, inOfs, len, out, outOfs, true);
- }
+ byte[] lengthBlock =
+ getLengthBlock(sizeOfAAD*8, processed*8);
+ ghashAllToS.update(lengthBlock);
+ byte[] s = ghashAllToS.digest();
+ byte[] sOut = new byte[s.length];
+ GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+ gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
- byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
- ghashAllToS.update(lengthBlock);
- byte[] s = ghashAllToS.digest();
- byte[] sOut = new byte[s.length];
- GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
- gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
-
- System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
- return (len + tagLenBytes);
- }
+ System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
+ return (len + tagLenBytes);
+ }
/**
* Performs decryption operation.
@@ -432,14 +453,16 @@
* @param out the buffer for the result
* @param outOfs the offset in <code>out</code>
*/
- void decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+ int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
processAAD();
- if (len > 0) { // must be at least AES_BLOCK_SIZE bytes long
- gctrPAndC.update(in, inOfs, len, out, outOfs);
- processed += len;
- ghashAllToS.update(in, inOfs, len);
+ if (len > 0) {
+ // store internally until decryptFinal is called because
+ // spec mentioned that only return recovered data after tag
+ // is successfully verified
+ ibuffer.write(in, inOfs, len);
}
+ return 0;
}
/**
@@ -458,44 +481,62 @@
* @param outOfs the offset in <code>plain</code>
* @return the number of bytes placed into the <code>out</code> buffer
*/
- int decryptFinal(byte[] in, int inOfs, int len,
- byte[] out, int outOfs)
- throws IllegalBlockSizeException, AEADBadTagException {
- if (len < tagLenBytes) {
- throw new RuntimeException("Input buffer too short - need tag");
- }
- if (out.length - outOfs < (len - tagLenBytes)) {
- throw new RuntimeException("Output buffer too small");
- }
- processAAD();
+ int decryptFinal(byte[] in, int inOfs, int len,
+ byte[] out, int outOfs)
+ throws IllegalBlockSizeException, AEADBadTagException,
+ ShortBufferException {
+ if (len < tagLenBytes) {
+ throw new AEADBadTagException("Input too short - need tag");
+ }
+ if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
+ throw new ShortBufferException("Output buffer too small");
+ }
+ processAAD();
+ if (len != 0) {
+ ibuffer.write(in, inOfs, len);
+ }
- int processedOld = processed;
- byte[] tag = new byte[tagLenBytes];
- // get the trailing tag bytes from 'in'
- System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
- len -= tagLenBytes;
+ // refresh 'in' to all buffered-up bytes
+ in = ibuffer.toByteArray();
+ inOfs = 0;
+ len = in.length;
+ ibuffer.reset();
- if (len > 0) {
- doLastBlock(in, inOfs, len, out, outOfs, false);
- }
+ byte[] tag = new byte[tagLenBytes];
+ // get the trailing tag bytes from 'in'
+ System.arraycopy(in, len - tagLenBytes, tag, 0, tagLenBytes);
+ len -= tagLenBytes;
- byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
- ghashAllToS.update(lengthBlock);
+ if (len > 0) {
+ doLastBlock(in, inOfs, len, out, outOfs, false);
+ }
- byte[] s = ghashAllToS.digest();
- byte[] sOut = new byte[s.length];
- GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
- gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
- for (int i = 0; i < tagLenBytes; i++) {
- if (tag[i] != sOut[i]) {
- throw new AEADBadTagException("Tag mismatch!");
- }
- }
- return len;
- }
+ byte[] lengthBlock =
+ getLengthBlock(sizeOfAAD*8, processed*8);
+ ghashAllToS.update(lengthBlock);
+
+ byte[] s = ghashAllToS.digest();
+ byte[] sOut = new byte[s.length];
+ GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+ gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
+ for (int i = 0; i < tagLenBytes; i++) {
+ if (tag[i] != sOut[i]) {
+ throw new AEADBadTagException("Tag mismatch!");
+ }
+ }
+ return len;
+ }
// return tag length in bytes
int getTagLen() {
return this.tagLenBytes;
}
+
+ int getBufferedLength() {
+ if (ibuffer == null) {
+ return 0;
+ } else {
+ return ibuffer.size();
+ }
+ }
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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
@@ -149,9 +149,10 @@
* @param plainLen the length of the input data
* @param cipher the buffer for the result
* @param cipherOffset the offset in <code>cipher</code>
+ * @return the length of the encrypted data
*/
- void encrypt(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encrypt(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
{
int i;
int len = blockSize - numBytes;
@@ -195,6 +196,7 @@
System.arraycopy(k, 0, register, len, numBytes);
}
}
+ return plainLen;
}
/**
@@ -219,11 +221,12 @@
* @param cipherLen the length of the input data
* @param plain the buffer for the result
* @param plainOffset the offset in <code>plain</code>
+ * @return the length of the decrypted data
*/
- void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+ int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
byte[] plain, int plainOffset)
{
// OFB encrypt and decrypt are identical
- encrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
+ return encrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
}
}
--- a/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java Tue Oct 08 11:17:53 2013 -0700
@@ -136,8 +136,8 @@
* @param cipher the buffer for the result
* @param cipherOffset the offset in <code>cipher</code>
*/
- void encrypt(byte[] plain, int plainOffset, int plainLen,
- byte[] cipher, int cipherOffset)
+ int encrypt(byte[] plain, int plainOffset, int plainLen,
+ byte[] cipher, int cipherOffset)
{
int i;
int endIndex = plainOffset + plainLen;
@@ -152,6 +152,7 @@
k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]);
}
}
+ return plainLen;
}
/**
@@ -177,8 +178,8 @@
* @param plain the buffer for the result
* @param plainOffset the offset in <code>plain</code>
*/
- void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
- byte[] plain, int plainOffset)
+ int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+ byte[] plain, int plainOffset)
{
int i;
int endIndex = cipherOffset + cipherLen;
@@ -194,5 +195,6 @@
k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]);
}
}
+ return cipherLen;
}
}
--- a/jdk/src/share/classes/javax/crypto/CipherSpi.java Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/javax/crypto/CipherSpi.java Tue Oct 08 11:17:53 2013 -0700
@@ -786,7 +786,9 @@
int total = 0;
do {
int chunk = Math.min(inLen, inArray.length);
- input.get(inArray, 0, chunk);
+ if (chunk > 0) {
+ input.get(inArray, 0, chunk);
+ }
int n;
if (isUpdate || (inLen != chunk)) {
n = engineUpdate(inArray, 0, chunk, outArray, outOfs);
@@ -814,8 +816,9 @@
int total = 0;
boolean resized = false;
do {
- int chunk = Math.min(inLen, outSize);
- if ((a1 == false) && (resized == false)) {
+ int chunk =
+ Math.min(inLen, (outSize == 0? inArray.length : outSize));
+ if (!a1 && !resized && chunk > 0) {
input.get(inArray, 0, chunk);
inOfs = 0;
}
@@ -829,8 +832,10 @@
resized = false;
inOfs += chunk;
inLen -= chunk;
- output.put(outArray, 0, n);
- total += n;
+ if (n > 0) {
+ output.put(outArray, 0, n);
+ total += n;
+ }
} catch (ShortBufferException e) {
if (resized) {
// we just resized the output buffer, but it still
@@ -840,11 +845,13 @@
}
// output buffer is too small, realloc and try again
resized = true;
- int newOut = engineGetOutputSize(chunk);
- outArray = new byte[newOut];
+ outSize = engineGetOutputSize(chunk);
+ outArray = new byte[outSize];
}
} while (inLen > 0);
- input.position(inLimit);
+ if (a1) {
+ input.position(inLimit);
+ }
return total;
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java Tue Oct 08 11:17:53 2013 -0700
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2013, 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
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8012900
+ * @library ../UTIL
+ * @build TestUtil
+ * @run main TestCICOWithGCMAndAAD
+ * @summary Test CipherInputStream/OutputStream with AES GCM mode with AAD.
+ * @author Valerie Peng
+ */
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import javax.crypto.*;
+
+public class TestCICOWithGCMAndAAD {
+ public static void main(String[] args) throws Exception {
+ //init Secret Key
+ KeyGenerator kg = KeyGenerator.getInstance("AES", "SunJCE");
+ kg.init(128);
+ SecretKey key = kg.generateKey();
+
+ //Do initialization of the plainText
+ byte[] plainText = new byte[700];
+ Random rdm = new Random();
+ rdm.nextBytes(plainText);
+
+ byte[] aad = new byte[128];
+ rdm.nextBytes(aad);
+ byte[] aad2 = aad.clone();
+ aad2[50]++;
+
+ Cipher encCipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
+ encCipher.init(Cipher.ENCRYPT_MODE, key);
+ encCipher.updateAAD(aad);
+ Cipher decCipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
+ decCipher.init(Cipher.DECRYPT_MODE, key, encCipher.getParameters());
+ decCipher.updateAAD(aad);
+
+ byte[] recovered = test(encCipher, decCipher, plainText);
+ if (!Arrays.equals(plainText, recovered)) {
+ throw new Exception("sameAAD: diff check failed!");
+ } else System.out.println("sameAAD: passed");
+
+ encCipher.init(Cipher.ENCRYPT_MODE, key);
+ encCipher.updateAAD(aad2);
+ recovered = test(encCipher, decCipher, plainText);
+ if (recovered != null && recovered.length != 0) {
+ throw new Exception("diffAAD: no data should be returned!");
+ } else System.out.println("diffAAD: passed");
+ }
+
+ private static byte[] test(Cipher encCipher, Cipher decCipher, byte[] plainText)
+ throws Exception {
+ //init cipher streams
+ ByteArrayInputStream baInput = new ByteArrayInputStream(plainText);
+ CipherInputStream ciInput = new CipherInputStream(baInput, encCipher);
+ ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
+ CipherOutputStream ciOutput = new CipherOutputStream(baOutput, decCipher);
+
+ //do test
+ byte[] buffer = new byte[200];
+ int len = ciInput.read(buffer);
+ System.out.println("read " + len + " bytes from input buffer");
+
+ while (len != -1) {
+ ciOutput.write(buffer, 0, len);
+ System.out.println("wite " + len + " bytes to output buffer");
+ len = ciInput.read(buffer);
+ if (len != -1) {
+ System.out.println("read " + len + " bytes from input buffer");
+ } else {
+ System.out.println("finished reading");
+ }
+ }
+
+ ciOutput.flush();
+ ciInput.close();
+ ciOutput.close();
+
+ return baOutput.toByteArray();
+ }
+}