diff --git a/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java b/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java index 3ed00e07257..c32aa7feae5 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java @@ -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 @@ class CipherBlockChaining extends FeedbackCipher { * @param plainLen the length of the input data * @param cipher the buffer for the result * @param cipherOffset the offset in cipher + * @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 @@ class CipherBlockChaining extends FeedbackCipher { embeddedCipher.encryptBlock(k, 0, cipher, cipherOffset); System.arraycopy(cipher, cipherOffset, r, 0, blockSize); } + return plainLen; } /** @@ -174,13 +176,14 @@ class CipherBlockChaining extends FeedbackCipher { * @param cipherLen the length of the input data * @param plain the buffer for the result * @param plainOffset the offset in plain + * @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 @@ class CipherBlockChaining extends FeedbackCipher { // the plaintext result. cipherOrig = cipher.clone(); } - for (; cipherOffset < endIndex; cipherOffset += blockSize, plainOffset += blockSize) { embeddedCipher.decryptBlock(cipher, cipherOffset, k, 0); @@ -208,5 +210,6 @@ class CipherBlockChaining extends FeedbackCipher { System.arraycopy(cipherOrig, cipherOffset, r, 0, blockSize); } } + return cipherLen; } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java b/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java index 7548612242a..dab1b4d6f57 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java @@ -310,49 +310,20 @@ final class CipherCore { * @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 - totalLen -= tagLen; - } else { + if (!decrypting) { totalLen += tagLen; + } else { + totalLen -= tagLen; } } if (totalLen < 0) { @@ -360,8 +331,19 @@ final class CipherCore { } 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 @@ 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"); } - 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 @@ final class CipherCore { ((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 @@ final class CipherCore { ("Must use either different key or iv for GCM encryption"); } - // calculate the total input length - int totalLen = buffered + inputLen; - int paddedLen = totalLen; - int paddingLen = 0; + 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 @@ final class CipherCore { 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 @@ final class CipherCore { 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 @@ final class CipherCore { 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; diff --git a/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java b/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java index a5f74f68266..f8e6115c98f 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java @@ -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 @@ final class CipherFeedback extends FeedbackCipher { * @param plainLen the length of the input data * @param cipher the buffer for the result * @param cipherOffset the offset in cipher + * @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 @@ final class CipherFeedback extends FeedbackCipher { } } } + return plainLen; } /** @@ -218,9 +220,10 @@ final class CipherFeedback extends FeedbackCipher { * @param cipherLen the length of the input data * @param plain the buffer for the result * @param plainOffset the offset in plain + * @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 @@ final class CipherFeedback extends FeedbackCipher { } } } + return cipherLen; } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java b/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java index d9e936c3bd0..438998e01ce 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java @@ -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 @@ final class CounterMode extends FeedbackCipher { * @param len the length of the input data * @param out the buffer for the result * @param outOff the offset in cipher + * @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 @@ final class CounterMode extends FeedbackCipher { * @param len the length of the input data * @param out the buffer for the result * @param outOff the offset in plain + * @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 @@ final class CounterMode extends FeedbackCipher { * 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 @@ final class CounterMode extends FeedbackCipher { } out[outOff++] = (byte)(in[inOff++] ^ encryptedCounter[used++]); } + return result; } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java b/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java index abbddd771de..a407b6a50c0 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java @@ -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 @@ final class ElectronicCodeBook extends FeedbackCipher { * @param len the length of the input data * @param out the buffer for the result * @param outOff the offset in cipher + * @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 @@ final class ElectronicCodeBook extends FeedbackCipher { * @param len the length of the input data * @param out the buffer for the result * @param outOff the offset in plain + * @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; } - } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java b/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java index c27332457f3..8d03913e284 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java @@ -133,9 +133,10 @@ abstract class FeedbackCipher { * @param plainLen the length of the input data * @param cipher the buffer for the encryption result * @param cipherOffset the offset in cipher + * @return the number of bytes placed into cipher */ - 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 @@ abstract class FeedbackCipher { */ 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 @@ abstract class FeedbackCipher { * @param cipherLen the length of the input data * @param plain the buffer for the decryption result * @param plainOffset the offset in plain + * @return the number of bytes placed into plain */ - 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 @@ abstract class FeedbackCipher { */ 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 @@ abstract class FeedbackCipher { 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; + } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java b/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java index 74d3555cc58..d4cd740af7b 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java @@ -54,7 +54,7 @@ final class GCTR { 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 @@ final class GCTR { 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 @@ final class GCTR { } /** - * 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; } /** diff --git a/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index 2f8b584e2a9..37bf6f41433 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -35,10 +35,12 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; * This class represents ciphers in GaloisCounter (GCM) mode. * *

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. * - *

NOTE: This class does not deal with buffering or padding. + *

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 @@ final class GaloisCounterMode extends FeedbackCipher { 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 @@ final class GaloisCounterMode extends FeedbackCipher { // 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 @@ final class GaloisCounterMode extends FeedbackCipher { if (ghashAllToS != null) ghashAllToS.reset(); processed = 0; sizeOfAAD = 0; + if (ibuffer != null) { + ibuffer.reset(); + } } /** @@ -184,6 +194,9 @@ final class GaloisCounterMode extends FeedbackCipher { null : aadBuffer.toByteArray()); if (gctrPAndC != null) gctrPAndC.save(); if (ghashAllToS != null) ghashAllToS.save(); + if (ibuffer != null) { + ibufferSave = ibuffer.toByteArray(); + } } /** @@ -198,8 +211,12 @@ final class GaloisCounterMode extends FeedbackCipher { 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 @@ final class GaloisCounterMode extends FeedbackCipher { } processed = 0; sizeOfAAD = 0; + if (decrypting) { + ibuffer = new ByteArrayOutputStream(); + } } /** @@ -299,7 +319,7 @@ final class GaloisCounterMode extends FeedbackCipher { // 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 @@ final class GaloisCounterMode extends FeedbackCipher { * @param out the buffer for the result * @param outOfs the offset in out */ - 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 @@ final class GaloisCounterMode extends FeedbackCipher { * @param outOfs the offset in out * @return the number of bytes placed into the out 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) { - //ByteUtil.dumpArray(Arrays.copyOfRange(in, inOfs, inOfs + len)); - doLastBlock(in, inOfs, len, out, outOfs, true); - } + processAAD(); + if (len > 0) { + 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 @@ final class GaloisCounterMode extends FeedbackCipher { * @param out the buffer for the result * @param outOfs the offset in out */ - 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 @@ final class GaloisCounterMode extends FeedbackCipher { * @param outOfs the offset in plain * @return the number of bytes placed into the out 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(); + } + } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java b/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java index f8a34602ac1..ea5890db2e5 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java @@ -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 @@ final class OutputFeedback extends FeedbackCipher { * @param plainLen the length of the input data * @param cipher the buffer for the result * @param cipherOffset the offset in cipher + * @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 @@ final class OutputFeedback extends FeedbackCipher { System.arraycopy(k, 0, register, len, numBytes); } } + return plainLen; } /** @@ -219,11 +221,12 @@ final class OutputFeedback extends FeedbackCipher { * @param cipherLen the length of the input data * @param plain the buffer for the result * @param plainOffset the offset in plain + * @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); } } diff --git a/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java b/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java index aa4758593af..360e1833afb 100644 --- a/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java +++ b/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java @@ -136,8 +136,8 @@ final class PCBC extends FeedbackCipher { * @param cipher the buffer for the result * @param cipherOffset the offset in cipher */ - 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 @@ final class PCBC extends FeedbackCipher { k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]); } } + return plainLen; } /** @@ -177,8 +178,8 @@ final class PCBC extends FeedbackCipher { * @param plain the buffer for the result * @param plainOffset the offset in plain */ - 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 @@ final class PCBC extends FeedbackCipher { k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]); } } + return cipherLen; } } diff --git a/jdk/src/share/classes/javax/crypto/CipherSpi.java b/jdk/src/share/classes/javax/crypto/CipherSpi.java index d839be7ce80..aae33318d1c 100644 --- a/jdk/src/share/classes/javax/crypto/CipherSpi.java +++ b/jdk/src/share/classes/javax/crypto/CipherSpi.java @@ -786,7 +786,9 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { 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 @@ public abstract class CipherSpi { } // 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; } } diff --git a/jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java b/jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java new file mode 100644 index 00000000000..816f764f9ba --- /dev/null +++ b/jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java @@ -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(); + } +}