diff --git a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java index dac33aa6211..c082dde20b8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java @@ -138,21 +138,6 @@ abstract class AESCipher extends CipherSpi { super(32, "CFB", "NOPADDING"); } } - public static final class AES128_GCM_NoPadding extends OidImpl { - public AES128_GCM_NoPadding() { - super(16, "GCM", "NOPADDING"); - } - } - public static final class AES192_GCM_NoPadding extends OidImpl { - public AES192_GCM_NoPadding() { - super(24, "GCM", "NOPADDING"); - } - } - public static final class AES256_GCM_NoPadding extends OidImpl { - public AES256_GCM_NoPadding() { - super(32, "GCM", "NOPADDING"); - } - } // utility method used by AESCipher and AESWrapCipher static final void checkKeySize(Key key, int fixedKeySize) @@ -185,10 +170,6 @@ abstract class AESCipher extends CipherSpi { */ private final int fixedKeySize; // in bytes, -1 if no restriction - /* - * needed to enforce ISE thrown when updateAAD is called after update for GCM mode. - */ - private boolean updateCalled; /** * Creates an instance of AES cipher with default ECB mode and @@ -322,7 +303,6 @@ abstract class AESCipher extends CipherSpi { protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException { checkKeySize(key, fixedKeySize); - updateCalled = false; core.init(opmode, key, random); } @@ -355,7 +335,6 @@ abstract class AESCipher extends CipherSpi { SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { checkKeySize(key, fixedKeySize); - updateCalled = false; core.init(opmode, key, params, random); } @@ -364,7 +343,6 @@ abstract class AESCipher extends CipherSpi { SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { checkKeySize(key, fixedKeySize); - updateCalled = false; core.init(opmode, key, params, random); } @@ -389,7 +367,6 @@ abstract class AESCipher extends CipherSpi { */ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { - updateCalled = true; return core.update(input, inputOffset, inputLen); } @@ -419,7 +396,6 @@ abstract class AESCipher extends CipherSpi { protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException { - updateCalled = true; return core.update(input, inputOffset, inputLen, output, outputOffset); } @@ -458,7 +434,6 @@ abstract class AESCipher extends CipherSpi { protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { byte[] out = core.doFinal(input, inputOffset, inputLen); - updateCalled = false; return out; } @@ -504,7 +479,6 @@ abstract class AESCipher extends CipherSpi { BadPaddingException { int outLen = core.doFinal(input, inputOffset, inputLen, output, outputOffset); - updateCalled = false; return outLen; } @@ -577,86 +551,6 @@ abstract class AESCipher extends CipherSpi { wrappedKeyType); } - /** - * Continues a multi-part update of the Additional Authentication - * Data (AAD), using a subset of the provided buffer. - *
- * Calls to this method provide AAD to the cipher when operating in - * modes such as AEAD (GCM/CCM). If this cipher is operating in - * either GCM or CCM mode, all AAD must be supplied before beginning - * operations on the ciphertext (via the {@code update} and {@code - * doFinal} methods). - * - * @param src the buffer containing the AAD - * @param offset the offset in {@code src} where the AAD input starts - * @param len the number of AAD bytes - * - * @throws IllegalStateException if this cipher is in a wrong state - * (e.g., has not been initialized), does not accept AAD, or if - * operating in either GCM or CCM mode and one of the {@code update} - * methods has already been called for the active - * encryption/decryption operation - * @throws UnsupportedOperationException if this method - * has not been overridden by an implementation - * - * @since 1.8 - */ - @Override - protected void engineUpdateAAD(byte[] src, int offset, int len) { - if (core.getMode() == CipherCore.GCM_MODE && updateCalled) { - throw new IllegalStateException("AAD must be supplied before encryption/decryption starts"); - } - core.updateAAD(src, offset, len); - } - - /** - * Continues a multi-part update of the Additional Authentication - * Data (AAD). - *
- * Calls to this method provide AAD to the cipher when operating in - * modes such as AEAD (GCM/CCM). If this cipher is operating in - * either GCM or CCM mode, all AAD must be supplied before beginning - * operations on the ciphertext (via the {@code update} and {@code - * doFinal} methods). - *
- * All {@code src.remaining()} bytes starting at
- * {@code src.position()} are processed.
- * Upon return, the input buffer's position will be equal
- * to its limit; its limit will not have changed.
- *
- * @param src the buffer containing the AAD
- *
- * @throws IllegalStateException if this cipher is in a wrong state
- * (e.g., has not been initialized), does not accept AAD, or if
- * operating in either GCM or CCM mode and one of the {@code update}
- * methods has already been called for the active
- * encryption/decryption operation
- * @throws UnsupportedOperationException if this method
- * has not been overridden by an implementation
- *
- * @since 1.8
- */
- @Override
- protected void engineUpdateAAD(ByteBuffer src) {
- if (core.getMode() == CipherCore.GCM_MODE && updateCalled) {
- throw new IllegalStateException("AAD must be supplied before encryption/decryption starts");
- }
- if (src != null) {
- int aadLen = src.limit() - src.position();
- if (aadLen > 0) {
- if (src.hasArray()) {
- int aadOfs = Math.addExact(src.arrayOffset(), src.position());
- core.updateAAD(src.array(), aadOfs, aadLen);
- src.position(src.limit());
- } else {
- byte[] aad = new byte[aadLen];
- src.get(aad);
- core.updateAAD(aad, 0, aadLen);
- }
- }
- }
- }
-
/**
* Finalize crypto operation with ByteBuffers
*
@@ -672,10 +566,6 @@ abstract class AESCipher extends CipherSpi {
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);
- }
+ return super.engineDoFinal(input, output);
}
}
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
index d8ad366ebd8..acef059d20a 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
@@ -25,7 +25,6 @@
package com.sun.crypto.provider;
-import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
@@ -83,7 +82,6 @@ final class CipherCore {
* currently, only the following cases have non-zero values:
* 1) CTS mode - due to its special handling on the last two blocks
* (the last one may be incomplete).
- * 2) GCM mode + decryption - due to its trailing tag bytes
*/
private int minBytes = 0;
@@ -125,24 +123,6 @@ final class CipherCore {
private static final int PCBC_MODE = 4;
private static final int CTR_MODE = 5;
private static final int CTS_MODE = 6;
- static final int GCM_MODE = 7;
-
- /*
- * variables used for performing the GCM (key+iv) uniqueness check.
- * To use GCM mode safely, the cipher object must be re-initialized
- * with a different combination of key + iv values for each
- * encryption operation. However, checking all past key + iv values
- * isn't feasible. Thus, we only do a per-instance check of the
- * key + iv values used in previous encryption.
- * For decryption operations, no checking is necessary.
- * NOTE: this key+iv check have to be done inside CipherCore class
- * since CipherCore class buffers potential tag bytes in GCM mode
- * and may not call GaloisCounterMode when there isn't sufficient
- * input to process.
- */
- private boolean requireReinit = false;
- private byte[] lastEncKey = null;
- private byte[] lastEncIv = null;
/**
* Creates an instance of CipherCore with default ECB mode and
@@ -197,15 +177,6 @@ final class CipherCore {
cipher = new CounterMode(rawImpl);
unitBytes = 1;
padding = null;
- } else if (modeUpperCase.equals("GCM")) {
- // can only be used for block ciphers w/ 128-bit block size
- if (blockSize != 16) {
- throw new NoSuchAlgorithmException
- ("GCM mode can only be used for AES cipher");
- }
- cipherMode = GCM_MODE;
- cipher = new GaloisCounterMode(rawImpl);
- padding = null;
} else if (modeUpperCase.startsWith("CFB")) {
cipherMode = CFB_MODE;
unitBytes = getNumOfUnit(mode, "CFB".length(), blockSize);
@@ -224,15 +195,6 @@ final class CipherCore {
}
}
- /**
- * Returns the mode of this cipher.
- *
- * @return the parsed cipher mode
- */
- int getMode() {
- return cipherMode;
- }
-
private static int getNumOfUnit(String mode, int offset, int blockSize)
throws NoSuchAlgorithmException {
int result = blockSize; // use blockSize as default value
@@ -279,17 +241,13 @@ final class CipherCore {
+ " not implemented");
}
if ((padding != null) &&
- ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE)
- || (cipherMode == GCM_MODE))) {
+ ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE))) {
padding = null;
String modeStr = null;
switch (cipherMode) {
case CTR_MODE:
modeStr = "CTR";
break;
- case GCM_MODE:
- modeStr = "GCM";
- break;
case CTS_MODE:
modeStr = "CTS";
break;
@@ -310,7 +268,7 @@ final class CipherCore {
* inputLen
(in bytes).
*
*
This call takes into account any unprocessed (buffered) data from a
- * previous update
call, padding, and AEAD tagging.
+ * previous update
call, and padding.
*
*
The actual output length of the next update
or
* doFinal
call may be smaller than the length returned by
@@ -326,36 +284,19 @@ final class CipherCore {
}
private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
- int totalLen = Math.addExact(buffered, cipher.getBufferedLength());
+ int totalLen = buffered;
totalLen = Math.addExact(totalLen, inputLen);
- switch (cipherMode) {
- case GCM_MODE:
- if (isDoFinal) {
- int tagLen = ((GaloisCounterMode) cipher).getTagLen();
- if (!decrypting) {
- totalLen = Math.addExact(totalLen, tagLen);
+ if (padding != null && !decrypting) {
+ if (unitBytes != blockSize) {
+ if (totalLen < diffBlocksize) {
+ totalLen = diffBlocksize;
} else {
- totalLen -= tagLen;
+ int residue = (totalLen - diffBlocksize) % blockSize;
+ totalLen = Math.addExact(totalLen, (blockSize - residue));
}
+ } else {
+ totalLen = Math.addExact(totalLen, padding.padLength(totalLen));
}
- if (totalLen < 0) {
- totalLen = 0;
- }
- break;
- default:
- if (padding != null && !decrypting) {
- if (unitBytes != blockSize) {
- if (totalLen < diffBlocksize) {
- totalLen = diffBlocksize;
- } else {
- int residue = (totalLen - diffBlocksize) % blockSize;
- totalLen = Math.addExact(totalLen, (blockSize - residue));
- }
- } else {
- totalLen = Math.addExact(totalLen, padding.padLength(totalLen));
- }
- }
- break;
}
return totalLen;
}
@@ -398,26 +339,15 @@ final class CipherCore {
AlgorithmParameterSpec spec;
byte[] iv = getIV();
if (iv == null) {
- // generate spec using default value
- if (cipherMode == GCM_MODE) {
- iv = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
- } else {
- iv = new byte[blockSize];
- }
+ iv = new byte[blockSize];
SunJCE.getRandom().nextBytes(iv);
}
- if (cipherMode == GCM_MODE) {
- algName = "GCM";
- spec = new GCMParameterSpec
- (((GaloisCounterMode) cipher).getTagLen()*8, iv);
+ if (algName.equals("RC2")) {
+ RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
+ spec = new RC2ParameterSpec
+ (rawImpl.getEffectiveKeyBits(), iv);
} else {
- if (algName.equals("RC2")) {
- RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
- spec = new RC2ParameterSpec
- (rawImpl.getEffectiveKeyBits(), iv);
- } else {
- spec = new IvParameterSpec(iv);
- }
+ spec = new IvParameterSpec(iv);
}
try {
params = AlgorithmParameters.getInstance(algName,
@@ -504,106 +434,51 @@ final class CipherCore {
|| (opmode == Cipher.UNWRAP_MODE);
byte[] keyBytes = getKeyBytes(key);
- try {
- int tagLen = -1;
- byte[] ivBytes = null;
- if (params != null) {
- if (cipherMode == GCM_MODE) {
- if (params instanceof GCMParameterSpec) {
- tagLen = ((GCMParameterSpec) params).getTLen();
- if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) {
- throw new InvalidAlgorithmParameterException
- ("Unsupported TLen value; must be one of " +
- "{128, 120, 112, 104, 96}");
- }
- tagLen = tagLen >> 3;
- ivBytes = ((GCMParameterSpec) params).getIV();
- } else {
- throw new InvalidAlgorithmParameterException
- ("Unsupported parameter: " + params);
- }
- } else {
- if (params instanceof IvParameterSpec) {
- ivBytes = ((IvParameterSpec) params).getIV();
- if ((ivBytes == null) || (ivBytes.length != blockSize)) {
- throw new InvalidAlgorithmParameterException
- ("Wrong IV length: must be " + blockSize +
- " bytes long");
- }
- } else if (params instanceof RC2ParameterSpec) {
- ivBytes = ((RC2ParameterSpec) params).getIV();
- if ((ivBytes != null) && (ivBytes.length != blockSize)) {
- throw new InvalidAlgorithmParameterException
- ("Wrong IV length: must be " + blockSize +
- " bytes long");
- }
- } else {
- throw new InvalidAlgorithmParameterException
- ("Unsupported parameter: " + params);
- }
- }
- }
- if (cipherMode == ECB_MODE) {
- if (ivBytes != null) {
+ byte[] ivBytes = null;
+ if (params != null) {
+ if (params instanceof IvParameterSpec) {
+ ivBytes = ((IvParameterSpec) params).getIV();
+ if ((ivBytes == null) || (ivBytes.length != blockSize)) {
throw new InvalidAlgorithmParameterException
- ("ECB mode cannot use IV");
+ ("Wrong IV length: must be " + blockSize +
+ " bytes long");
}
- } else if (ivBytes == null) {
- if (decrypting) {
- throw new InvalidAlgorithmParameterException("Parameters "
- + "missing");
+ } else if (params instanceof RC2ParameterSpec) {
+ ivBytes = ((RC2ParameterSpec) params).getIV();
+ if ((ivBytes != null) && (ivBytes.length != blockSize)) {
+ throw new InvalidAlgorithmParameterException
+ ("Wrong IV length: must be " + blockSize +
+ " bytes long");
}
-
- if (random == null) {
- random = SunJCE.getRandom();
- }
- if (cipherMode == GCM_MODE) {
- ivBytes = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
- } else {
- ivBytes = new byte[blockSize];
- }
- random.nextBytes(ivBytes);
- }
-
- buffered = 0;
- diffBlocksize = blockSize;
-
- String algorithm = key.getAlgorithm();
-
- // GCM mode needs additional handling
- if (cipherMode == GCM_MODE) {
- if (tagLen == -1) {
- tagLen = GaloisCounterMode.DEFAULT_TAG_LEN;
- }
- if (decrypting) {
- minBytes = tagLen;
- } else {
- // check key+iv for encryption in GCM mode
- requireReinit =
- Arrays.equals(ivBytes, lastEncIv) &&
- MessageDigest.isEqual(keyBytes, lastEncKey);
- if (requireReinit) {
- throw new InvalidAlgorithmParameterException
- ("Cannot reuse iv for GCM encryption");
- }
- lastEncIv = ivBytes;
- if (lastEncKey != null) {
- Arrays.fill(lastEncKey, (byte) 0);
- }
- lastEncKey = keyBytes;
- }
- ((GaloisCounterMode) cipher).init
- (decrypting, algorithm, keyBytes, ivBytes, tagLen);
} else {
- cipher.init(decrypting, algorithm, keyBytes, ivBytes);
- }
- // skip checking key+iv from now on until after doFinal()
- requireReinit = false;
- } finally {
- if (lastEncKey != keyBytes) {
- Arrays.fill(keyBytes, (byte) 0);
+ throw new InvalidAlgorithmParameterException
+ ("Unsupported parameter: " + params);
}
}
+ if (cipherMode == ECB_MODE) {
+ if (ivBytes != null) {
+ throw new InvalidAlgorithmParameterException
+ ("ECB mode cannot use IV");
+ }
+ } else if (ivBytes == null) {
+ if (decrypting) {
+ throw new InvalidAlgorithmParameterException("Parameters "
+ + "missing");
+ }
+
+ if (random == null) {
+ random = SunJCE.getRandom();
+ }
+
+ ivBytes = new byte[blockSize];
+ random.nextBytes(ivBytes);
+ }
+
+ buffered = 0;
+ diffBlocksize = blockSize;
+
+ String algorithm = key.getAlgorithm();
+ cipher.init(decrypting, algorithm, keyBytes, ivBytes);
}
void init(int opmode, Key key, AlgorithmParameters params,
@@ -613,16 +488,11 @@ final class CipherCore {
String paramType = null;
if (params != null) {
try {
- if (cipherMode == GCM_MODE) {
- paramType = "GCM";
- spec = params.getParameterSpec(GCMParameterSpec.class);
- } else {
- // NOTE: RC2 parameters are always handled through
- // init(..., AlgorithmParameterSpec,...) method, so
- // we can assume IvParameterSpec type here.
- paramType = "IV";
- spec = params.getParameterSpec(IvParameterSpec.class);
- }
+ // NOTE: RC2 parameters are always handled through
+ // init(..., AlgorithmParameterSpec,...) method, so
+ // we can assume IvParameterSpec type here.
+ paramType = "IV";
+ spec = params.getParameterSpec(IvParameterSpec.class);
} catch (InvalidParameterSpecException ipse) {
throw new InvalidAlgorithmParameterException
("Wrong parameter type: " + paramType + " expected");
@@ -671,7 +541,6 @@ final class CipherCore {
* (e.g., has not been initialized)
*/
byte[] update(byte[] input, int inputOffset, int inputLen) {
- checkReinit();
byte[] output = null;
try {
@@ -719,7 +588,6 @@ final class CipherCore {
*/
int update(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
- checkReinit();
// figure out how much can be sent to crypto function
int len = Math.addExact(buffered, inputLen);
@@ -854,7 +722,6 @@ final class CipherCore {
byte[] doFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
try {
- checkReinit();
byte[] output = new byte[getOutputSizeByOperation(inputLen, true)];
byte[] finalBuf = prepareInputBuffer(input, inputOffset,
inputLen, output, 0);
@@ -868,7 +735,7 @@ final class CipherCore {
if (outLen < output.length) {
byte[] copy = Arrays.copyOf(output, outLen);
if (decrypting) {
- // Zero out internal (ouput) array
+ // Zero out internal (output) array
Arrays.fill(output, (byte) 0x00);
}
return copy;
@@ -921,7 +788,6 @@ final class CipherCore {
int outputOffset)
throws IllegalBlockSizeException, ShortBufferException,
BadPaddingException {
- checkReinit();
int estOutSize = getOutputSizeByOperation(inputLen, true);
int outputCapacity = checkOutputCapacity(output, outputOffset,
@@ -943,15 +809,13 @@ final class CipherCore {
if (outputCapacity < estOutSize) {
cipher.save();
}
- 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;
- }
+ // create temporary output buffer if the estimated size is larger
+ // than the user-provided buffer.
+ internalOutput = new byte[estOutSize];
+ offset = 0;
}
- byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
+ byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer,
offset, finalBufLen, input);
@@ -961,13 +825,13 @@ final class CipherCore {
// restore so users can retry with a larger buffer
cipher.restore();
throw new ShortBufferException("Output buffer too short: "
- + (outputCapacity)
- + " bytes given, " + outLen
- + " bytes needed");
+ + (outputCapacity) + " bytes given, " + outLen
+ + " bytes needed");
}
// copy the result into user-supplied output buffer
if (internalOutput != null) {
- System.arraycopy(internalOutput, 0, output, outputOffset, outLen);
+ System.arraycopy(internalOutput, 0, output, outputOffset,
+ outLen);
// decrypt mode. Zero out output data that's not required
Arrays.fill(internalOutput, (byte) 0x00);
}
@@ -1001,7 +865,7 @@ final class CipherCore {
// calculate total input length
int len = Math.addExact(buffered, inputLen);
// calculate padding length
- int totalLen = Math.addExact(len, cipher.getBufferedLength());
+ int totalLen = len;
int paddingLen = 0;
// will the total input length be a multiple of blockSize?
if (unitBytes != blockSize) {
@@ -1059,30 +923,27 @@ final class CipherCore {
}
private int fillOutputBuffer(byte[] finalBuf, int finalOffset,
- byte[] output, int outOfs, int finalBufLen,
- byte[] input)
- throws ShortBufferException, BadPaddingException,
- IllegalBlockSizeException {
+ byte[] output, int outOfs, int finalBufLen, byte[] input)
+ throws ShortBufferException, BadPaddingException,
+ IllegalBlockSizeException {
+
int len;
try {
len = finalNoPadding(finalBuf, finalOffset, output,
- outOfs, finalBufLen);
+ outOfs, finalBufLen);
if (decrypting && padding != null) {
len = unpad(len, outOfs, output);
}
return len;
} finally {
- if (!decrypting) {
- // reset after doFinal() for GCM encryption
- requireReinit = (cipherMode == GCM_MODE);
- if (finalBuf != input) {
- // done with internal finalBuf array. Copied to output
- Arrays.fill(finalBuf, (byte) 0x00);
- }
+ if (!decrypting && finalBuf != input) {
+ // done with internal finalBuf array. Copied to output
+ Arrays.fill(finalBuf, (byte) 0x00);
}
}
}
+
private int checkOutputCapacity(byte[] output, int outputOffset,
int estOutSize) throws ShortBufferException {
// check output buffer capacity.
@@ -1098,23 +959,14 @@ final class CipherCore {
return outputCapacity;
}
- private void checkReinit() {
- if (requireReinit) {
- throw new IllegalStateException
- ("Must use either different key or iv for GCM encryption");
- }
- }
-
private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
int len)
- throws IllegalBlockSizeException, AEADBadTagException,
- ShortBufferException {
+ throws IllegalBlockSizeException, ShortBufferException {
- if ((cipherMode != GCM_MODE) && (in == null || len == 0)) {
+ if (in == null || len == 0) {
return 0;
}
if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE) &&
- (cipherMode != GCM_MODE) &&
((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) {
if (padding != null) {
throw new IllegalBlockSizeException
@@ -1126,7 +978,7 @@ final class CipherCore {
+ " bytes");
}
}
- int outLen = 0;
+ int outLen;
if (decrypting) {
outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs);
} else {
@@ -1217,59 +1069,4 @@ final class CipherCore {
Arrays.fill(encodedKey, (byte)0);
}
}
-
- /**
- * Continues a multi-part update of the Additional Authentication
- * Data (AAD), using a subset of the provided buffer.
- *
- * Calls to this method provide AAD to the cipher when operating in - * modes such as AEAD (GCM/CCM). If this cipher is operating in - * either GCM or CCM mode, all AAD must be supplied before beginning - * operations on the ciphertext (via the {@code update} and {@code - * doFinal} methods). - * - * @param src the buffer containing the AAD - * @param offset the offset in {@code src} where the AAD input starts - * @param len the number of AAD bytes - * - * @throws IllegalStateException if this cipher is in a wrong state - * (e.g., has not been initialized), does not accept AAD, or if - * operating in either GCM or CCM mode and one of the {@code update} - * methods has already been called for the active - * encryption/decryption operation - * @throws UnsupportedOperationException if this method - * has not been overridden by an implementation - * - * @since 1.8 - */ - void updateAAD(byte[] src, int offset, int len) { - 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"); - } - - int len; - if (decrypting) { - if (buffered > 0) { - cipher.decrypt(buffer, 0, buffered, new byte[0], 0); - } - len = cipher.decryptFinal(src, dst); - } else { - if (buffered > 0) { - ((GaloisCounterMode)cipher).encrypt(buffer, 0, buffered); - } - len = cipher.encryptFinal(src, dst); - } - endDoFinal(); - return len; - } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java index c7bb57664a1..6c0e10eb6d0 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2021, 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 @@ -25,7 +25,6 @@ package com.sun.crypto.provider; -import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.InvalidAlgorithmParameterException; import javax.crypto.*; @@ -200,70 +199,7 @@ abstract class FeedbackCipher { */ int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen, byte[] plain, int plainOffset) - throws IllegalBlockSizeException, AEADBadTagException, - ShortBufferException { + throws IllegalBlockSizeException, ShortBufferException { return decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset); } - - /** - * Continues a multi-part update of the Additional Authentication - * Data (AAD), using a subset of the provided buffer. If this - * cipher is operating in either GCM or CCM mode, all AAD must be - * supplied before beginning operations on the ciphertext (via the - * {@code update} and {@code doFinal} methods). - *
- * NOTE: Given most modes do not accept AAD, default impl for this - * method throws IllegalStateException. - * - * @param src the buffer containing the AAD - * @param offset the offset in {@code src} where the AAD input starts - * @param len the number of AAD bytes - * - * @throws IllegalStateException if this cipher is in a wrong state - * (e.g., has not been initialized), does not accept AAD, or if - * operating in either GCM or CCM mode and one of the {@code update} - * methods has already been called for the active - * encryption/decryption operation - * @throws UnsupportedOperationException if this method - * has not been overridden by an implementation - * - * @since 1.8 - */ - 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; - } - - /* - * 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"); - } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GCM.java b/src/java.base/share/classes/com/sun/crypto/provider/GCM.java new file mode 100644 index 00000000000..3bb05a0c16a --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/GCM.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package com.sun.crypto.provider; + +import java.nio.ByteBuffer; + +/** + * This interface allows GHASH.java and GCTR.java to easily operate to + * better operate with GaloisCounterMode.java + */ + +public interface GCM { + int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs); + int update(byte[] in, int inOfs, int inLen, ByteBuffer dst); + int update(ByteBuffer src, ByteBuffer dst); + int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs); + int doFinal(ByteBuffer src, ByteBuffer dst); +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java index 1a09a617a4d..7aaec3d6c1a 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java @@ -31,8 +31,7 @@ package com.sun.crypto.provider; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import javax.crypto.IllegalBlockSizeException; -import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; +import java.util.Arrays; /** * This class represents the GCTR function defined in NIST 800-38D @@ -52,17 +51,18 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; * * @since 1.8 */ -final class GCTR extends CounterMode { +final class GCTR extends CounterMode implements GCM { // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy private static final int MAX_LEN = 1024; + private byte[] block; GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) { super(cipher); - if (initialCounterBlk.length != AES_BLOCK_SIZE) { + if (initialCounterBlk.length != blockSize) { throw new RuntimeException("length of initial counter block (" + - initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" + - AES_BLOCK_SIZE + ")"); + initialCounterBlk.length + ") not equal to blockSize (" + + blockSize + ")"); } iv = initialCounterBlk; @@ -83,30 +83,47 @@ final class GCTR extends CounterMode { return blocksLeft; } - // input must be multiples of 128-bit blocks when calling update - int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { + private void checkBlock() { + if (block == null) { + block = new byte[blockSize]; + } else { + Arrays.fill(block, (byte)0); + } + } + + /** + * Using the given inLen, this operates only on blockSize data, leaving + * the remainder in 'in'. + * The return value will be (inLen - (inLen % blockSize)) + */ + public int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { + if (inLen == 0) { + return 0; + } + if (inLen - inOfs > in.length) { throw new RuntimeException("input length out of bound"); } - if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) { + if (inLen < 0) { throw new RuntimeException("input length unsupported"); } - if (out.length - outOfs < inLen) { + if (out.length - outOfs < (inLen - (inLen % blockSize))) { throw new RuntimeException("output buffer too small"); } + inLen -= inLen % blockSize; long blocksLeft = blocksUntilRollover(); - int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE; + int numOfCompleteBlocks = inLen / blockSize; 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]; + checkBlock(); 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); + embeddedCipher.encryptBlock(counter, 0, block, 0); + for (int n = 0; n < blockSize; n++) { + int index = (i * blockSize + n); out[outOfs + index] = - (byte) ((in[inOfs + index] ^ encryptedCntr[n])); + (byte) ((in[inOfs + index] ^ block[n])); } GaloisCounterMode.increment32(counter); } @@ -116,34 +133,46 @@ 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) { + /** + * Operate on only blocksize data leaving the remainder in 'in' . + */ + public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) { + // If the bytebuffer is backed by arrays, use that instead of + // allocating and copying for direct bytebuffers + if (!dst.isDirect()) { + int len = update(in, inOfs, inLen, dst.array(), + dst.arrayOffset() + dst.position()); + dst.position(dst.position() + len); + return len; + } + + // Direct ByteBuffer operation if (inLen - inOfs > in.length) { throw new RuntimeException("input length out of bound"); } - if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) { + if (inLen < 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; + int numOfCompleteBlocks = inLen / blockSize; 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]; + checkBlock(); 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]))); + embeddedCipher.encryptBlock(counter, 0, block, 0); + for (int n = 0; n < blockSize; n++) { + int index = (i * blockSize + n); + dst.put((byte) ((in[inOfs + index] ^ block[n]))); } GaloisCounterMode.increment32(counter); } return inLen; } else { - int len = inLen - inLen % AES_BLOCK_SIZE; + int len = inLen - inLen % blockSize; int processed = len; byte[] out = new byte[Math.min(MAX_LEN, len)]; int offset = inOfs; @@ -162,26 +191,41 @@ final class GCTR extends CounterMode { } } - // 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) { + /** + * Operate on only blocksize data leaving the remainder in the src buffer. + */ + public int update(ByteBuffer src, ByteBuffer dst) { + int len; + + // If the bytebuffer is backed by arrays, use that instead of + // allocating and copying for direct bytebuffers + if (src.hasArray() && dst.hasArray()) { + len = update(src.array(), src.arrayOffset() + src.position(), + src.remaining() - (src.remaining() % blockSize), + dst.array(), dst.arrayOffset() + dst.position()); + src.position(src.position() + len); + dst.position(dst.position() + len); + return len; + } + + // Direct bytebuffer operation long blocksLeft = blocksUntilRollover(); - int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE; + int numOfCompleteBlocks = src.remaining() / blockSize; 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]; + checkBlock(); 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])); + embeddedCipher.encryptBlock(counter, 0, block, 0); + for (int n = 0; n < blockSize; n++) { + dst.put((byte) (src.get() ^ block[n])); } GaloisCounterMode.increment32(counter); } - return numOfCompleteBlocks * AES_BLOCK_SIZE; + return numOfCompleteBlocks * blockSize; } - int len = src.remaining() - (src.remaining() % AES_BLOCK_SIZE); + len = src.remaining() - (src.remaining() % blockSize); int processed = len; byte[] in = new byte[Math.min(MAX_LEN, len)]; while (processed > MAX_LEN) { @@ -196,50 +240,62 @@ final class GCTR extends CounterMode { return len; } - // input can be arbitrary size when calling doFinal - int doFinal(byte[] in, int inOfs, int inLen, byte[] out, - int outOfs) throws IllegalBlockSizeException { - try { - if (inLen < 0) { - 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, completeBlkLen, out, outOfs); - 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++) { - out[outOfs + completeBlkLen + n] = - (byte) ((in[inOfs + completeBlkLen + n] ^ - encryptedCntr[n])); - } - } + /** + * doFinal operation by using update() for any full block operations needed, + * then operating on the final bytes in the input buffer. + * + * This method will not write any block padding to the output buffer + */ + public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, + int outOfs) { + if (inLen == 0) { + return 0; + } + int lastBlockSize = inLen % blockSize; + int completeBlkLen = inLen - lastBlockSize; + // process the complete blocks first + update(in, inOfs, completeBlkLen, out, outOfs); + if (lastBlockSize != 0) { + // do the last partial block + checkBlock(); + embeddedCipher.encryptBlock(counter, 0, block, 0); + for (int n = 0; n < lastBlockSize; n++) { + out[outOfs + completeBlkLen + n] = + (byte) ((in[inOfs + completeBlkLen + n] ^ block[n])); } - } finally { - reset(); } return inLen; } - // src can be arbitrary size when calling doFinal - int doFinal(ByteBuffer src, ByteBuffer dst) { + /** + * doFinal operation by using update() for any full block operations needed, + * then operating on the final bytes in the input buffer. + * + * If src and dst are array-backed bytebuffers, call doFinal(byte[]...) for + * less memory usage. + */ + public int doFinal(ByteBuffer src, ByteBuffer dst) { + // If the bytebuffer is backed by arrays, use that instead of + // allocating and copying for direct bytebuffers + if (src.hasArray() && dst.hasArray()) { + int len = doFinal(src.array(), src.arrayOffset() + src.position(), + src.remaining(), dst.array(), + dst.arrayOffset() + dst.position()); + src.position(src.position() + len); + dst.position(dst.position() + len); + return len; + } + 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])); - } + int lastBlockSize = len % blockSize; + update(src, dst); + if (lastBlockSize != 0) { + checkBlock(); + // do the last partial block + embeddedCipher.encryptBlock(counter, 0, block, 0); + for (int n = 0; n < lastBlockSize; n++) { + dst.put((byte) (src.get() ^ block[n])); } - } finally { - reset(); } return len; } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java index e9ce33b6b58..c0815524dd7 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java @@ -1,6 +1,5 @@ /* - * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2015 Red Hat, Inc. + * Copyright (c) 2013, 2021, 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 @@ -25,11 +24,15 @@ */ /* * (C) Copyright IBM Corp. 2013 + * Copyright (c) 2015 Red Hat, Inc. */ package com.sun.crypto.provider; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.ProviderException; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -44,27 +47,19 @@ import jdk.internal.vm.annotation.IntrinsicCandidate; * * @since 1.8 */ -final class GHASH { - private static long getLong(byte[] buffer, int offset) { - long result = 0; - int end = offset + 8; - for (int i = offset; i < end; ++i) { - result = (result << 8) + (buffer[i] & 0xFF); - } - return result; - } - - private static void putLong(byte[] buffer, int offset, long value) { - int end = offset + 8; - for (int i = end - 1; i >= offset; --i) { - buffer[i] = (byte) value; - value >>= 8; - } - } +final class GHASH implements Cloneable, GCM { private static final int AES_BLOCK_SIZE = 16; + // Handle for converting byte[] <-> long + private static final VarHandle asLongView = + MethodHandles.byteArrayViewVarHandle(long[].class, + ByteOrder.BIG_ENDIAN); + + // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy + private static final int MAX_LEN = 1024; + // Multiplies state[0], state[1] by subkeyH[0], subkeyH[1]. private static void blockMult(long[] st, long[] subH) { long Z0 = 0; @@ -127,15 +122,13 @@ final class GHASH { /* subkeyHtbl and state are stored in long[] for GHASH intrinsic use */ - // hashtable subkeyHtbl; holds 2*9 powers of subkeyH computed using carry-less multiplication + // hashtable subkeyHtbl holds 2*9 powers of subkeyH computed using + // carry-less multiplication private long[] subkeyHtbl; // buffer for storing hash private final long[] state; - // variables for save/restore calls - private long stateSave0, stateSave1; - /** * Initializes the cipher in the specified mode with the given key * and iv. @@ -151,87 +144,106 @@ final class GHASH { } state = new long[2]; subkeyHtbl = new long[2*9]; - subkeyHtbl[0] = getLong(subkeyH, 0); - subkeyHtbl[1] = getLong(subkeyH, 8); + subkeyHtbl[0] = (long)asLongView.get(subkeyH, 0); + subkeyHtbl[1] = (long)asLongView.get(subkeyH, 8); } - /** - * Resets the GHASH object to its original state, i.e. blank w/ - * the same subkey H. Used after digest() is called and to re-use - * this object for different data w/ the same H. - */ - void reset() { - state[0] = 0; - state[1] = 0; + // Cloning constructor + private GHASH(GHASH g) { + state = g.state.clone(); + subkeyHtbl = g.subkeyHtbl.clone(); } - /** - * Save the current snapshot of this GHASH object. - */ - void save() { - stateSave0 = state[0]; - stateSave1 = state[1]; + @Override + public GHASH clone() { + return new GHASH(this); } - /** - * Restores this object using the saved snapshot. - */ - void restore() { - state[0] = stateSave0; - state[1] = stateSave1; - } - - private static void processBlock(byte[] data, int ofs, long[] st, long[] subH) { - st[0] ^= getLong(data, ofs); - st[1] ^= getLong(data, ofs + 8); + private static void processBlock(byte[] data, int ofs, long[] st, + long[] subH) { + st[0] ^= (long)asLongView.get(data, ofs); + st[1] ^= (long)asLongView.get(data, ofs + 8); blockMult(st, subH); } - void update(byte[] in) { - update(in, 0, in.length); + int update(byte[] in) { + return update(in, 0, in.length); } - void update(byte[] in, int inOfs, int inLen) { + int update(byte[] in, int inOfs, int inLen) { if (inLen == 0) { - return; + return 0; } - ghashRangeCheck(in, inOfs, inLen, state, subkeyHtbl); - processBlocks(in, inOfs, inLen/AES_BLOCK_SIZE, state, subkeyHtbl); + int len = inLen - (inLen % AES_BLOCK_SIZE); + ghashRangeCheck(in, inOfs, len, state, subkeyHtbl); + processBlocks(in, inOfs, len / AES_BLOCK_SIZE, state, subkeyHtbl); + return len; } - // 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) { + int update(ByteBuffer ct, 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; + // If ct is a direct bytebuffer, send it directly to the intrinsic + if (ct.isDirect()) { + int processed = inLen; + processBlocksDirect(ct, inLen); + return processed; + } else if (!ct.isReadOnly()) { + // If a non-read only heap bytebuffer, use the array update method + int processed = update(ct.array(), + ct.arrayOffset() + ct.position(), + inLen); + ct.position(ct.position() + processed); + return processed; } - src.get(in, 0, processed); - update(in, 0, processed); + + // Read only heap bytebuffers have to be copied and operated on + int to_process = inLen; + byte[] in = new byte[Math.min(MAX_LEN, inLen)]; + while (to_process > MAX_LEN ) { + ct.get(in, 0, MAX_LEN); + update(in, 0 , MAX_LEN); + to_process -= MAX_LEN; + } + ct.get(in, 0, to_process); + update(in, 0, to_process); return inLen; } - void doLastBlock(ByteBuffer src, int inLen) { - int processed = update(src, inLen); + int doFinal(ByteBuffer src, int inLen) { + int processed = 0; + + if (inLen >= AES_BLOCK_SIZE) { + processed = update(src, inLen); + } + if (inLen == processed) { - return; + return processed; } byte[] block = new byte[AES_BLOCK_SIZE]; src.get(block, 0, inLen - processed); update(block, 0, AES_BLOCK_SIZE); + return inLen; } - private static void ghashRangeCheck(byte[] in, int inOfs, int inLen, long[] st, long[] subH) { + int doFinal(byte[] in, int inOfs, int inLen) { + int remainder = inLen % AES_BLOCK_SIZE; + inOfs += update(in, inOfs, inLen - remainder); + if (remainder > 0) { + byte[] block = new byte[AES_BLOCK_SIZE]; + System.arraycopy(in, inOfs, block, 0, + remainder); + update(block, 0, AES_BLOCK_SIZE); + } + return inLen; + } + + private static void ghashRangeCheck(byte[] in, int inOfs, int inLen, + long[] st, long[] subH) { if (inLen < 0) { throw new RuntimeException("invalid input length: " + inLen); } @@ -263,7 +275,8 @@ final class GHASH { * throw exceptions or allocate arrays as it will breaking intrinsics */ @IntrinsicCandidate - private static void processBlocks(byte[] data, int inOfs, int blocks, long[] st, long[] subH) { + private static void processBlocks(byte[] data, int inOfs, int blocks, + long[] st, long[] subH) { int offset = inOfs; while (blocks > 0) { processBlock(data, offset, st, subH); @@ -272,11 +285,61 @@ final class GHASH { } } + // ProcessBlock for Direct ByteBuffers + private void processBlocksDirect(ByteBuffer ct, int inLen) { + byte[] data = new byte[Math.min(MAX_LEN, inLen)]; + while (inLen > MAX_LEN) { + ct.get(data, 0, MAX_LEN); + processBlocks(data, 0, MAX_LEN / AES_BLOCK_SIZE, state, + subkeyHtbl); + inLen -= MAX_LEN; + } + if (inLen >= AES_BLOCK_SIZE) { + int len = inLen - (inLen % AES_BLOCK_SIZE); + ct.get(data, 0, len); + processBlocks(data, 0, len / AES_BLOCK_SIZE, state, + subkeyHtbl); + } + } + byte[] digest() { byte[] result = new byte[AES_BLOCK_SIZE]; - putLong(result, 0, state[0]); - putLong(result, 8, state[1]); - reset(); + asLongView.set(result, 0, state[0]); + asLongView.set(result, 8, state[1]); + // Reset state + state[0] = 0; + state[1] = 0; return result; } + + + /** + * None of the out or dst values are necessary, they are to satisfy the + * GCM interface requirement + */ + @Override + public int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { + return update(in, inOfs, inLen); + } + + @Override + public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) { + return update(in, inOfs, inLen); + } + + @Override + public int update(ByteBuffer src, ByteBuffer dst) { + return update(src, src.remaining()); + } + + @Override + public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, + int outOfs) { + return doFinal(in, inOfs, inLen); + } + + @Override + public int doFinal(ByteBuffer src, ByteBuffer dst) { + return doFinal(src, src.remaining()); + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index ebfd0e531c0..f43160eb9be 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -26,20 +26,34 @@ package com.sun.crypto.provider; import sun.nio.ch.DirectBuffer; +import sun.security.jca.JCAUtil; import sun.security.util.ArrayUtil; import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.ProviderException; - -import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; - +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; /** * This class represents ciphers in GaloisCounter (GCM) mode. @@ -54,11 +68,9 @@ import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; * * @since 1.8 */ -final class GaloisCounterMode extends FeedbackCipher { - - static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE; +abstract class GaloisCounterMode extends CipherSpi { static int DEFAULT_IV_LEN = 12; // in bytes - + static int DEFAULT_TAG_LEN = 16; // in bytes // In NIST SP 800-38D, GCM input size is limited to be no longer // than (2^36 - 32) bytes. Otherwise, the counter will wrap // around and lead to a leak of plaintext. @@ -68,46 +80,424 @@ final class GaloisCounterMode extends FeedbackCipher { // java byte array, e.g. Integer.MAX_VALUE, since all data // can only be returned by the doFinal(...) call. private static final int MAX_BUF_SIZE = Integer.MAX_VALUE; - // data size when buffer is divided up to aid in intrinsics private static final int TRIGGERLEN = 65536; // 64k - // buffer for AAD data; if null, meaning update has been called - private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); - private int sizeOfAAD = 0; + static final byte[] EMPTY_BUF = new byte[0]; - // buffer data for crypto operation - private ByteArrayOutputStream ibuffer = null; + private boolean initialized = false; - // Original dst buffer if there was an overlap situation - private ByteBuffer originalDst = null; + SymmetricCipher blockCipher; + // Engine instance for encryption or decryption + private GCMEngine engine; + private boolean encryption = true; - // in bytes; need to convert to bits (default value 128) when needed - private int tagLenBytes = DEFAULT_TAG_LEN; + // Default value is 128bits, this is in bytes. + int tagLenBytes = DEFAULT_TAG_LEN; + // Key size if the value is passed, in bytes. + int keySize; + // Prevent reuse of iv or key + boolean reInit = false; + byte[] lastKey = EMPTY_BUF; + byte[] lastIv = EMPTY_BUF; + byte[] iv = null; + SecureRandom random = null; - // these following 2 fields can only be initialized after init() is - // called, e.g. after cipher key k is set, and STAY UNCHANGED - private byte[] subkeyH = null; - private byte[] preCounterBlock = null; + /** + * + * @param keySize length of key. + * @param embeddedCipher Cipher object, such as AESCrypt. + */ + GaloisCounterMode(int keySize, SymmetricCipher embeddedCipher) { + blockCipher = embeddedCipher; + this.keySize = keySize; + } - private GCTR gctrPAndC = null; - private GHASH ghashAllToS = null; + /** + * Initializes the cipher in the specified mode with the given key + * and iv. + */ + void init(int opmode, Key key, GCMParameterSpec spec) + throws InvalidKeyException, InvalidAlgorithmParameterException { + encryption = (opmode == Cipher.ENCRYPT_MODE) || + (opmode == Cipher.WRAP_MODE); - // length of total data, i.e. len(C) - private int processed = 0; + int tagLen = spec.getTLen(); + if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) { + throw new InvalidAlgorithmParameterException + ("Unsupported TLen value. Must be one of " + + "{128, 120, 112, 104, 96}"); + } + tagLenBytes = tagLen >> 3; - // additional variables for save/restore calls - private byte[] aadBufferSave = null; - private int sizeOfAADSave = 0; - private byte[] ibufferSave = null; - private int processedSave = 0; + // Check the Key object is valid and the right size + if (key == null) { + throw new InvalidKeyException("The key must not be null"); + } + byte[] keyValue = key.getEncoded(); + if (keyValue == null) { + throw new InvalidKeyException("Key encoding must not be null"); + } else if (keySize != -1 && keyValue.length != keySize) { + Arrays.fill(keyValue, (byte) 0); + throw new InvalidKeyException("The key must be " + + keySize + " bytes"); + } + + // Check for reuse + if (encryption) { + if (MessageDigest.isEqual(keyValue, lastKey) && + MessageDigest.isEqual(iv, lastIv)) { + Arrays.fill(keyValue, (byte) 0); + throw new InvalidAlgorithmParameterException( + "Cannot reuse iv for GCM encryption"); + } + + // Both values are already clones + if (lastKey != null) { + Arrays.fill(lastKey, (byte) 0); + } + lastKey = keyValue; + lastIv = iv; + } + + reInit = false; + + // always encrypt mode for embedded cipher + blockCipher.init(false, key.getAlgorithm(), keyValue); + } + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + if (!mode.equalsIgnoreCase("GCM")) { + throw new NoSuchAlgorithmException("Mode must be GCM"); + } + } + + @Override + protected void engineSetPadding(String padding) + throws NoSuchPaddingException { + if (!padding.equalsIgnoreCase("NoPadding")) { + throw new NoSuchPaddingException("Padding must be NoPadding"); + } + } + + @Override + protected int engineGetBlockSize() { + return blockCipher.getBlockSize(); + } + + @Override + protected int engineGetOutputSize(int inputLen) { + checkInit(); + return engine.getOutputSize(inputLen, true); + } + + @Override + protected int engineGetKeySize(Key key) throws InvalidKeyException { + byte[] encoded = key.getEncoded(); + Arrays.fill(encoded, (byte)0); + if (!AESCrypt.isKeySizeValid(encoded.length)) { + throw new InvalidKeyException("Invalid key length: " + + encoded.length + " bytes"); + } + return Math.multiplyExact(encoded.length, 8); + } + + @Override + protected byte[] engineGetIV() { + if (iv == null) { + return null; + } + return iv.clone(); + } + + /** + * Create a random 16-byte iv. + * + * @param rand a {@code SecureRandom} object. If {@code null} is + * provided a new {@code SecureRandom} object will be instantiated. + * + * @return a 16-byte array containing the random nonce. + */ + private static byte[] createIv(SecureRandom rand) { + byte[] iv = new byte[DEFAULT_IV_LEN]; + if (rand == null) { + rand = JCAUtil.getDefSecureRandom(); + } + rand.nextBytes(iv); + return iv; + } + + @Override + protected AlgorithmParameters engineGetParameters() { + GCMParameterSpec spec; + spec = new GCMParameterSpec(tagLenBytes * 8, + iv == null ? createIv(random) : iv.clone()); + try { + AlgorithmParameters params = + AlgorithmParameters.getInstance("GCM", + SunJCE.getInstance()); + params.init(spec); + return params; + } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + + engine = null; + if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) { + throw new InvalidKeyException("No GCMParameterSpec specified"); + } + try { + engineInit(opmode, key, (AlgorithmParameterSpec) null, random); + } catch (InvalidAlgorithmParameterException e) { + // never happen + } + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + + GCMParameterSpec spec; + this.random = random; + engine = null; + if (params == null) { + iv = createIv(random); + spec = new GCMParameterSpec(DEFAULT_TAG_LEN * 8, iv); + } else { + if (!(params instanceof GCMParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "AlgorithmParameterSpec not of GCMParameterSpec"); + } + spec = (GCMParameterSpec)params; + iv = spec.getIV(); + if (iv == null) { + throw new InvalidAlgorithmParameterException("IV is null"); + } + if (iv.length == 0) { + throw new InvalidAlgorithmParameterException("IV is empty"); + } + } + init(opmode, key, spec); + initialized = true; + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, + InvalidAlgorithmParameterException { + GCMParameterSpec spec = null; + engine = null; + if (params != null) { + try { + spec = params.getParameterSpec(GCMParameterSpec.class); + } catch (InvalidParameterSpecException e) { + throw new InvalidAlgorithmParameterException(e); + } + } + engineInit(opmode, key, spec, random); + } + + void checkInit() { + if (!initialized) { + throw new IllegalStateException("Operation not initialized."); + } + + if (engine == null) { + if (encryption) { + engine = new GCMEncrypt(blockCipher); + } else { + engine = new GCMDecrypt(blockCipher); + } + } + } + + void checkReInit() { + if (reInit) { + throw new IllegalStateException( + "Must use either different key or " + " iv for GCM encryption"); + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + checkInit(); + ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); + return engine.doUpdate(input, inputOffset, inputLen); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException { + checkInit(); + ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); + ArrayUtil.nullAndBoundsCheck(output, outputOffset, + output.length - outputOffset); + int len = engine.getOutputSize(inputLen, false); + if (len > output.length - outputOffset) { + throw new ShortBufferException("Output buffer too small, must be " + + "at least " + len + " bytes long"); + } + return engine.doUpdate(input, inputOffset, inputLen, output, + outputOffset); + } + + @Override + protected int engineUpdate(ByteBuffer src, ByteBuffer dst) + throws ShortBufferException { + checkInit(); + int len = engine.getOutputSize(src.remaining(), false); + if (len > dst.remaining()) { + throw new ShortBufferException( + "Output buffer must be at least " + len + " bytes long"); + } + return engine.doUpdate(src, dst); + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + checkInit(); + engine.updateAAD(src, offset, len); + } + + @Override + protected void engineUpdateAAD(ByteBuffer src) { + checkInit(); + if (src.hasArray()) { + int pos = src.position(); + int len = src.remaining(); + engine.updateAAD(src.array(), src.arrayOffset() + pos, len); + src.position(pos + len); + } else { + byte[] aad = new byte[src.remaining()]; + src.get(aad); + engine.updateAAD(aad, 0, aad.length); + } + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, + int inputLen) throws IllegalBlockSizeException, BadPaddingException { + if (input == null) { + input = EMPTY_BUF; + } + try { + ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalBlockSizeException("input array invalid"); + } + + checkInit(); + byte[] output = new byte[engine.getOutputSize(inputLen, true)]; + + try { + engine.doFinal(input, inputOffset, inputLen, output, 0); + } catch (ShortBufferException e) { + throw new ProviderException(e); + } finally { + // Release crypto engine + engine = null; + } + return output; + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + + if (input == null) { + input = EMPTY_BUF; + } + try { + ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); + } catch (ArrayIndexOutOfBoundsException e) { + // Release crypto engine + engine = null; + throw new IllegalBlockSizeException("input array invalid"); + } + checkInit(); + int len = engine.doFinal(input, inputOffset, inputLen, output, + outputOffset); + + // Release crypto engine + engine = null; + + return len; + } + + @Override + protected int engineDoFinal(ByteBuffer src, ByteBuffer dst) + throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + checkInit(); + + int len = engine.doFinal(src, dst); + + // Release crypto engine + engine = null; + + return len; + } + + @Override + protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, + InvalidKeyException { + byte[] encodedKey = null; + + checkInit(); + try { + encodedKey = key.getEncoded(); + if ((encodedKey == null) || (encodedKey.length == 0)) { + throw new InvalidKeyException( + "Cannot get an encoding of the key to be wrapped"); + } + return engineDoFinal(encodedKey, 0, encodedKey.length); + } catch (BadPaddingException e) { + // should never happen + } finally { + // Release crypto engine + engine = null; + if (encodedKey != null) { + Arrays.fill(encodedKey, (byte) 0); + } + } + return null; + } + + @Override + protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, + int wrappedKeyType) throws InvalidKeyException, + NoSuchAlgorithmException { + checkInit(); + + byte[] encodedKey; + try { + encodedKey = engineDoFinal(wrappedKey, 0, + wrappedKey.length); + } catch (BadPaddingException ePadding) { + throw new InvalidKeyException( + "The wrapped key is not padded correctly"); + } catch (IllegalBlockSizeException eBlockSize) { + throw new InvalidKeyException( + "The wrapped key does not have the correct length"); + } + try { + return ConstructKeys.constructKey(encodedKey, wrappedKeyAlgorithm, + wrappedKeyType); + } finally { + Arrays.fill(encodedKey, (byte)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) { - // 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; while ((n >= value.length - 4) && (++value[n] == 0)) { @@ -115,74 +505,55 @@ final class GaloisCounterMode extends FeedbackCipher { } } + private static final VarHandle wrapToByteArray = + MethodHandles.byteArrayViewVarHandle(long[].class, + ByteOrder.BIG_ENDIAN); + private static byte[] getLengthBlock(int ivLenInBytes) { - long ivLen = ((long)ivLenInBytes) << 3; - byte[] out = new byte[AES_BLOCK_SIZE]; - out[8] = (byte)(ivLen >>> 56); - out[9] = (byte)(ivLen >>> 48); - out[10] = (byte)(ivLen >>> 40); - out[11] = (byte)(ivLen >>> 32); - out[12] = (byte)(ivLen >>> 24); - out[13] = (byte)(ivLen >>> 16); - out[14] = (byte)(ivLen >>> 8); - out[15] = (byte)ivLen; + byte[] out = new byte[16]; + wrapToByteArray.set(out, 8, ((long)ivLenInBytes & 0xFFFFFFFFL) << 3); return out; } private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) { - long aLen = ((long)aLenInBytes) << 3; - long cLen = ((long)cLenInBytes) << 3; - byte[] out = new byte[AES_BLOCK_SIZE]; - out[0] = (byte)(aLen >>> 56); - out[1] = (byte)(aLen >>> 48); - out[2] = (byte)(aLen >>> 40); - out[3] = (byte)(aLen >>> 32); - out[4] = (byte)(aLen >>> 24); - out[5] = (byte)(aLen >>> 16); - out[6] = (byte)(aLen >>> 8); - out[7] = (byte)aLen; - out[8] = (byte)(cLen >>> 56); - out[9] = (byte)(cLen >>> 48); - out[10] = (byte)(cLen >>> 40); - out[11] = (byte)(cLen >>> 32); - out[12] = (byte)(cLen >>> 24); - out[13] = (byte)(cLen >>> 16); - out[14] = (byte)(cLen >>> 8); - out[15] = (byte)cLen; + byte[] out = new byte[16]; + wrapToByteArray.set(out, 0, ((long)aLenInBytes & 0xFFFFFFFFL) << 3); + wrapToByteArray.set(out, 8, ((long)cLenInBytes & 0xFFFFFFFFL) << 3); return out; } - private static byte[] expandToOneBlock(byte[] in, int inOfs, int len) { - if (len > AES_BLOCK_SIZE) { + private static byte[] expandToOneBlock(byte[] in, int inOfs, int len, + int blockSize) { + if (len > blockSize) { throw new ProviderException("input " + len + " too long"); } - if (len == AES_BLOCK_SIZE && inOfs == 0) { + if (len == blockSize && inOfs == 0) { return in; } else { - byte[] paddedIn = new byte[AES_BLOCK_SIZE]; + byte[] paddedIn = new byte[blockSize]; System.arraycopy(in, inOfs, paddedIn, 0, len); return paddedIn; } } - private static byte[] getJ0(byte[] iv, byte[] subkeyH) { + private static byte[] getJ0(byte[] iv, byte[] subkeyH, int blockSize) { byte[] j0; if (iv.length == 12) { // 96 bits - j0 = expandToOneBlock(iv, 0, iv.length); - j0[AES_BLOCK_SIZE - 1] = 1; + j0 = expandToOneBlock(iv, 0, iv.length, blockSize); + j0[blockSize - 1] = 1; } else { GHASH g = new GHASH(subkeyH); - int lastLen = iv.length % AES_BLOCK_SIZE; + int lastLen = iv.length % blockSize; if (lastLen != 0) { g.update(iv, 0, iv.length - lastLen); byte[] padded = - expandToOneBlock(iv, iv.length - lastLen, lastLen); + expandToOneBlock(iv, iv.length - lastLen, lastLen, + blockSize); g.update(padded); } else { g.update(iv); } - byte[] lengthBlock = getLengthBlock(iv.length); - g.update(lengthBlock); + g.update(getLengthBlock(iv.length)); j0 = g.digest(); } return j0; @@ -198,820 +569,1089 @@ final class GaloisCounterMode extends FeedbackCipher { for (int len : lengths) { max = Math.subtractExact(max, len); } - if (processed > max) { + if (engine.processed > max) { throw new ProviderException("SunJCE provider only supports " + "input size up to " + MAX_BUF_SIZE + " bytes"); } } - GaloisCounterMode(SymmetricCipher embeddedCipher) { - super(embeddedCipher); - aadBuffer = new ByteArrayOutputStream(); - } - /** - * Gets the name of the feedback mechanism - * - * @return the name of the feedback mechanism + * Abstract class for GCMEncrypt and GCMDecrypt internal context objects */ - String getFeedback() { - return "GCM"; - } + abstract class GCMEngine { + byte[] preCounterBlock; + GCTR gctrPAndC; + GHASH ghashAllToS; - /** - * Resets the cipher object to its original state. - * This is used when doFinal is called in the Cipher class, so that the - * cipher can be reused (with its original key and iv). - */ - void reset() { - if (aadBuffer == null) { - aadBuffer = new ByteArrayOutputStream(); - } else { - aadBuffer.reset(); + // Block size of the algorithm + final int blockSize; + + // length of total data, i.e. len(C) + int processed = 0; + + // buffer for AAD data; if null, meaning update has been called + ByteArrayOutputStream aadBuffer = null; + int sizeOfAAD = 0; + boolean aadProcessed = false; + + // buffer data for crypto operation + ByteArrayOutputStream ibuffer = null; + + // Original dst buffer if there was an overlap situation + ByteBuffer originalDst = null; + byte[] originalOut = null; + int originalOutOfs = 0; + + + GCMEngine(SymmetricCipher blockCipher) { + blockSize = blockCipher.getBlockSize(); + byte[] subkeyH = new byte[blockSize]; + blockCipher.encryptBlock(subkeyH, 0, subkeyH,0); + preCounterBlock = getJ0(iv, subkeyH, blockSize); + byte[] j0Plus1 = preCounterBlock.clone(); + increment32(j0Plus1); + gctrPAndC = new GCTR(blockCipher, j0Plus1); + ghashAllToS = new GHASH(subkeyH); } - if (gctrPAndC != null) gctrPAndC.reset(); - if (ghashAllToS != null) ghashAllToS.reset(); - processed = 0; - sizeOfAAD = 0; - if (ibuffer != null) { - ibuffer.reset(); - } - } - /** - * Save the current content of this cipher. - */ - void save() { - processedSave = processed; - sizeOfAADSave = sizeOfAAD; - aadBufferSave = - ((aadBuffer == null || aadBuffer.size() == 0)? - null : aadBuffer.toByteArray()); - if (gctrPAndC != null) gctrPAndC.save(); - if (ghashAllToS != null) ghashAllToS.save(); - if (ibuffer != null) { - ibufferSave = ibuffer.toByteArray(); - } - } + /** + * Get output buffer size + * @param inLen Contains the length of the input data and buffered data. + * @param isFinal true if this is a doFinal operation + * @return If it's an update operation, inLen must blockSize + * divisible. If it's a final operation, output will + * include the tag. + */ + abstract int getOutputSize(int inLen, boolean isFinal); - /** - * Restores the content of this cipher to the previous saved one. - */ - void restore() { - processed = processedSave; - sizeOfAAD = sizeOfAADSave; - if (aadBuffer != null) { - aadBuffer.reset(); - if (aadBufferSave != null) { - aadBuffer.write(aadBufferSave, 0, aadBufferSave.length); + // Update operations + abstract byte[] doUpdate(byte[] in, int inOff, int inLen); + abstract int doUpdate(byte[] in, int inOff, int inLen, byte[] out, + int outOff) throws ShortBufferException; + abstract int doUpdate(ByteBuffer src, ByteBuffer dst) + throws ShortBufferException; + + // Final operations + abstract int doFinal(byte[] in, int inOff, int inLen, byte[] out, + int outOff) throws IllegalBlockSizeException, AEADBadTagException, + ShortBufferException; + abstract int doFinal(ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException, AEADBadTagException, + ShortBufferException; + + // Initialize internal data buffer, if not already. + void initBuffer(int len) { + if (ibuffer == null) { + ibuffer = new ByteArrayOutputStream(len); } } - if (gctrPAndC != null) gctrPAndC.restore(); - if (ghashAllToS != null) ghashAllToS.restore(); - if (ibuffer != null) { - ibuffer.reset(); - ibuffer.write(ibufferSave, 0, ibufferSave.length); - } - } - /** - * Initializes the cipher in the specified mode with the given key - * and iv. - * - * @param decrypting flag indicating encryption or decryption - * @param algorithm the algorithm name - * @param key the key - * @param iv the iv - * @exception InvalidKeyException if the given key is inappropriate for - * initializing this cipher - */ - @Override - void init(boolean decrypting, String algorithm, byte[] key, byte[] iv) - throws InvalidKeyException, InvalidAlgorithmParameterException { - init(decrypting, algorithm, key, iv, DEFAULT_TAG_LEN); - } - - /** - * Initializes the cipher in the specified mode with the given key - * and iv. - * - * @param decrypting flag indicating encryption or decryption - * @param algorithm the algorithm name - * @param keyValue the key - * @param ivValue the iv - * @param tagLenBytes the length of tag in bytes - * - * @exception InvalidKeyException if the given key is inappropriate for - * initializing this cipher - */ - void init(boolean decrypting, String algorithm, byte[] keyValue, - byte[] ivValue, int tagLenBytes) - throws InvalidKeyException, InvalidAlgorithmParameterException { - if (keyValue == null) { - throw new InvalidKeyException("Internal error"); - } - if (ivValue == null) { - throw new InvalidAlgorithmParameterException("Internal error"); - } - if (ivValue.length == 0) { - throw new InvalidAlgorithmParameterException("IV is empty"); + // Helper method for getting ibuffer size + int getBufferedLength() { + return (ibuffer == null ? 0 : ibuffer.size()); } - // always encrypt mode for embedded cipher - this.embeddedCipher.init(false, algorithm, keyValue); - this.subkeyH = new byte[AES_BLOCK_SIZE]; - this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0, - this.subkeyH, 0); - - this.iv = ivValue.clone(); - preCounterBlock = getJ0(iv, subkeyH); - byte[] j0Plus1 = preCounterBlock.clone(); - increment32(j0Plus1); - gctrPAndC = new GCTR(embeddedCipher, j0Plus1); - ghashAllToS = new GHASH(subkeyH); - - this.tagLenBytes = tagLenBytes; - if (aadBuffer == null) { - aadBuffer = new ByteArrayOutputStream(); - } else { - aadBuffer.reset(); + /** + * The method takes two buffers to create one block of data. The + * difference with the other mergeBlock is this will calculate + * the bufLen from the existing 'buffer' length & offset + * + * This is only called when buffer length is less than a blockSize + * @return number of bytes used from 'in' + */ + int mergeBlock(byte[] buffer, int bufOfs, byte[] in, int inOfs, + int inLen, byte[] block) { + return mergeBlock(buffer, bufOfs, buffer.length - bufOfs, in, + inOfs, inLen, block); } - processed = 0; - sizeOfAAD = 0; - if (decrypting) { - ibuffer = new ByteArrayOutputStream(); + + /** + * The method takes two buffers to create one block of data + * + * This is only called when buffer length is less than a blockSize + * @return number of bytes used from 'in' + */ + int mergeBlock(byte[] buffer, int bufOfs, int bufLen, byte[] in, + int inOfs, int inLen, byte[] block) { + if (bufLen > blockSize) { + throw new RuntimeException("mergeBlock called on an ibuffer " + + "too big: " + bufLen + " bytes"); + } + + System.arraycopy(buffer, bufOfs, block, 0, bufLen); + int inUsed = Math.min(block.length - bufLen, inLen); + System.arraycopy(in, inOfs, block, bufLen, inUsed); + return inUsed; } - } - /** - * Continues a multi-part update of the Additional Authentication - * Data (AAD), using a subset of the provided buffer. If this - * cipher is operating in either GCM or CCM mode, all AAD must be - * supplied before beginning operations on the ciphertext (via the - * {@code update} and {@code doFinal} methods). - *
- * NOTE: Given most modes do not accept AAD, default impl for this - * method throws IllegalStateException. - * - * @param src the buffer containing the AAD - * @param offset the offset in {@code src} where the AAD input starts - * @param len the number of AAD bytes - * - * @throws IllegalStateException if this cipher is in a wrong state - * (e.g., has not been initialized), does not accept AAD, or if - * operating in either GCM or CCM mode and one of the {@code update} - * methods has already been called for the active - * encryption/decryption operation - * @throws UnsupportedOperationException if this method - * has not been overridden by an implementation - * - * @since 1.8 - */ - void updateAAD(byte[] src, int offset, int len) { - if (aadBuffer != null) { - aadBuffer.write(src, offset, len); - } else { - // update has already been called - throw new IllegalStateException - ("Update has been called; no more AAD data"); - } - } + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD), using a subset of the provided buffer. All AAD must be + * supplied before beginning operations on the ciphertext (via the + * {@code update} and {@code doFinal} methods). + * + * @param src the buffer containing the AAD + * @param offset the offset in {@code src} where the AAD input starts + * @param len the number of AAD bytes + * + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized) or does not accept AAD, and one of + * the {@code update} methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if this method + * has not been overridden by an implementation + */ + void updateAAD(byte[] src, int offset, int len) { + if (encryption) { + checkReInit(); + } - // Feed the AAD data to GHASH, pad if necessary - void processAAD() { - if (aadBuffer != null) { - if (aadBuffer.size() > 0) { - byte[] aad = aadBuffer.toByteArray(); - sizeOfAAD = aad.length; - - int lastLen = aad.length % AES_BLOCK_SIZE; - if (lastLen != 0) { - ghashAllToS.update(aad, 0, aad.length - lastLen); - byte[] padded = expandToOneBlock(aad, aad.length - lastLen, - lastLen); - ghashAllToS.update(padded); + if (aadBuffer == null) { + if (sizeOfAAD == 0 && !aadProcessed) { + aadBuffer = new ByteArrayOutputStream(len); } else { - ghashAllToS.update(aad); + // update has already been called + throw new IllegalStateException + ("Update has been called; no more AAD data"); } } - aadBuffer = null; - } - } - - // Utility to process the last block; used by encryptFinal and decryptFinal - void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs, - boolean isEncrypt) throws IllegalBlockSizeException { - byte[] ct; - int ctOfs; - int ilen = len; // internal length - - if (isEncrypt) { - ct = out; - ctOfs = outOfs; - } else { - ct = in; - ctOfs = inOfs; + aadBuffer.write(src, offset, len); } - // Divide up larger data sizes to trigger CTR & GHASH intrinsic quicker - if (len > TRIGGERLEN) { - int i = 0; - int tlen; // incremental lengths - final int plen = AES_BLOCK_SIZE * 6; - // arbitrary formula to aid intrinsic without reaching buffer end - final int count = len / 1024; + // Feed the AAD data to GHASH, pad if necessary + void processAAD() { + if (aadBuffer != null) { + if (aadBuffer.size() > 0) { + byte[] aad = aadBuffer.toByteArray(); + sizeOfAAD = aad.length; - while (count > i) { - tlen = gctrPAndC.update(in, inOfs, plen, out, outOfs); - ghashAllToS.update(ct, ctOfs, tlen); - inOfs += tlen; - outOfs += tlen; - ctOfs += tlen; - i++; - } - ilen -= count * plen; - processed += count * plen; - } - - gctrPAndC.doFinal(in, inOfs, ilen, out, outOfs); - processed += ilen; - - int lastLen = ilen % AES_BLOCK_SIZE; - if (lastLen != 0) { - ghashAllToS.update(ct, ctOfs, ilen - lastLen); - ghashAllToS.update( - expandToOneBlock(ct, (ctOfs + ilen - lastLen), lastLen)); - } else { - ghashAllToS.update(ct, ctOfs, ilen); - } - } - - // 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; + int lastLen = aad.length % blockSize; + if (lastLen != 0) { + ghashAllToS.update(aad, 0, aad.length - lastLen); + byte[] padded = expandToOneBlock(aad, + aad.length - lastLen, lastLen, blockSize); + ghashAllToS.update(padded); } 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; + ghashAllToS.update(aad); } } - gctrPAndC.update(block, 0, AES_BLOCK_SIZE, dst); - processed += len; + aadBuffer = null; } + aadProcessed = true; } - // en/decrypt whatever remains in src. - // If src has been consumed, this will be a no-op - processed += gctrPAndC.doFinal(src, dst); - } + /** + * 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. + */ + int doLastBlock(GCM op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) { + int resultLen = 0; - /* - * 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; - } + int bLen = (buffer != null ? buffer.remaining() : 0); + if (bLen > 0) { + // en/decrypt on how much buffer there is in AES_BLOCK_SIZE + if (bLen >= blockSize) { + resultLen += op.update(buffer, dst); + } - /** - * Performs encryption operation. - * - *
The input plain text in
, starting at inOfs
- * and ending at (inOfs + len - 1)
, is encrypted. The result
- * is stored in out
, starting at outOfs
.
- *
- * @param in the buffer with the input data to be encrypted
- * @param inOfs the offset in in
- * @param inLen the length of the input data
- * @param out the buffer for the result
- * @param outOfs the offset in out
- * @return the number of bytes placed into the out
buffer
- */
- 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 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;
-
- // 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.
- *
- * @param in the input buffer with the data to be encrypted
- * @param inOfs the offset in in
- * @param len the length of the input data
- * @param out the buffer for the encryption result
- * @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, ShortBufferException {
- checkDataLength(len, getBufferedLength(), tagLenBytes);
-
- try {
- ArrayUtil.nullAndBoundsCheck(out, outOfs,
- (len + tagLenBytes));
- } catch (ArrayIndexOutOfBoundsException aiobe) {
- throw new ShortBufferException("Output buffer too small");
- }
-
- processAAD();
- if (len > 0) {
- ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-
- doLastBlock(in, inOfs, len, out, outOfs, true);
- }
-
- 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);
-
- 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);
-
- return (len + tagLenBytes);
- }
-
- /**
- * Performs decryption operation.
- *
- *
The input cipher text in
, starting at
- * inOfs
and ending at (inOfs + len - 1)
,
- * is decrypted. The result is stored in out
, starting at
- * outOfs
.
- *
- * @param in the buffer with the input data to be decrypted
- * @param inOfs the offset in in
- * @param len the length of the input data
- * @param out the buffer for the result
- * @param outOfs the offset in out
- * @exception ProviderException if len
is not
- * a multiple of the block size
- * @return the number of bytes placed into the out
buffer
- */
- int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
- processAAD();
-
- if (len > 0) {
- // store internally until decryptFinal is called because
- // spec mentioned that only return recovered data after tag
- // is successfully verified
- ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
- ibuffer.write(in, inOfs, len);
- }
- 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.
- *
- *
NOTE: For cipher feedback modes which does not perform
- * special handling for the last few blocks, this is essentially
- * the same as encrypt(...)
. Given most modes do
- * not do special handling, the default impl for this method is
- * to simply call decrypt(...)
.
- *
- * @param in the input buffer with the data to be decrypted
- * @param inOfs the offset in cipher
- * @param len the length of the input data
- * @param out the buffer for the decryption result
- * @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,
- ShortBufferException {
- if (len < tagLenBytes) {
- throw new AEADBadTagException("Input too short - need tag");
- }
-
- // do this check here can also catch the potential integer overflow
- // scenario for the subsequent output buffer capacity check.
- checkDataLength(getBufferedLength(), (len - tagLenBytes));
-
- try {
- ArrayUtil.nullAndBoundsCheck(out, outOfs,
- (getBufferedLength() + len) - tagLenBytes);
- } catch (ArrayIndexOutOfBoundsException aiobe) {
- throw new ShortBufferException("Output buffer too small");
- }
-
- processAAD();
-
- ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
-
- // get the trailing tag bytes from 'in'
- byte[] tag = new byte[tagLenBytes];
- System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
- len -= tagLenBytes;
-
- // If decryption is in-place or there is buffered "ibuffer" data, copy
- // the "in" byte array into the ibuffer before proceeding.
- if (in == out || getBufferedLength() > 0) {
- if (len > 0) {
- ibuffer.write(in, inOfs, len);
- }
-
- // 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[] block = getLengthBlock(sizeOfAAD, processed);
- 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[i] ^ block[i];
- }
-
- if (mismatch != 0) {
- throw new AEADBadTagException("Tag mismatch!");
- }
-
- 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) {
+ // Process the remainder in the buffer
+ if (bLen - resultLen > 0) {
+ // Copy the buffer remainder into an extra block
+ byte[] block = new byte[blockSize];
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);
+ // If src has data, complete the block;
+ int slen = Math.min(src.remaining(), blockSize - over);
+ if (slen > 0) {
+ src.get(block, over, slen);
}
+ int len = slen + over;
+ if (len == blockSize) {
+ resultLen += op.update(block, 0, blockSize, dst);
+ } else {
+ resultLen += op.doFinal(block, 0, len, block,
+ 0);
+ if (dst != null) {
+ dst.put(block, 0, len);
+ }
+ processed += resultLen;
+ return resultLen;
+ }
+ }
+ }
+
+ // en/decrypt whatever remains in src.
+ // If src has been consumed, this will be a no-op
+ if (src.remaining() > TRIGGERLEN) {
+ resultLen += throttleData(op, src, dst);
+ }
+
+ resultLen += op.doFinal(src, dst);
+ processed += resultLen;
+ return resultLen;
+ }
+
+
+ /**
+ * This segments large data into smaller chunks so hotspot will start
+ * using GCTR and GHASH intrinsics sooner. This is a problem for app
+ * and perf tests that only use large input sizes.
+ */
+ int throttleData(GCM op, byte[] in, int inOfs, int inLen,
+ byte[] out, int outOfs) {
+
+ int segments = (inLen / 6);
+ segments -= segments % blockSize;
+ int len = 0;
+ int i = 0;
+ do {
+ len += op.update(in, inOfs + len, segments, out,outOfs + len);
+ } while (++i < 5);
+
+ len += op.update(in, inOfs + len, inLen - len, out, outOfs + len);
+ return len;
+ }
+
+
+ /**
+ * This segments large data into smaller chunks so hotspot will start
+ * using GCTR and GHASH intrinsics sooner. This is a problem for app
+ * and perf tests that only use large input sizes.
+ */
+ int throttleData(GCM op, ByteBuffer src, ByteBuffer dst) {
+ int inLen = src.limit();
+ int segments = (src.remaining() / 6);
+ segments -= segments % blockSize;
+ int i = 0, resultLen = 0;
+ do {
+ src.limit(src.position() + segments);
+ resultLen += op.update(src, dst);
+ } while (++i < 5);
+
+ src.limit(inLen);
+ // If there is still at least a blockSize left
+ if (src.remaining() > blockSize) {
+ resultLen += op.update(src, dst);
+ }
+
+ return resultLen;
+ }
+
+ /**
+ * 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 is read only, then we need a copy
+ 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 are not the same and can be used as-is
+ 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;
+ }
+
+ /**
+ * Overlap detection for data using byte array.
+ * If an intermediate array is needed, the original out array length is
+ * allocated because for code simplicity.
+ */
+ byte[] overlapDetection(byte[] in, int inOfs, byte[] out, int outOfs) {
+ if (in == out && inOfs < outOfs) {
+ originalOut = out;
+ originalOutOfs = outOfs;
+ return new byte[out.length];
+ }
+ return out;
+ }
+
+ /**
+ * If originalDst is not null, 'dst' is an internal buffer and it's
+ * data will be copied to the original dst buffer
+ */
+ void restoreDst(ByteBuffer dst) {
+ if (originalDst == null) {
+ return;
+ }
+
+ dst.flip();
+ originalDst.put(dst);
+ originalDst = null;
+ }
+
+ /**
+ * If originalOut is not null, the 'out' is an internal buffer and it's
+ * data will be copied into original out byte[];
+ */
+ void restoreOut(byte[] out, int len) {
+ if (originalOut == null) {
+ return;
+ }
+
+ System.arraycopy(out, originalOutOfs, originalOut, originalOutOfs,
+ len);
+ originalOut = null;
+ }
+ }
+
+ /**
+ * Encryption Engine object
+ */
+ class GCMEncrypt extends GCMEngine {
+ GCTRGHASH gctrghash;
+
+ GCMEncrypt(SymmetricCipher blockCipher) {
+ super(blockCipher);
+ gctrghash = new GCTRGHASH(gctrPAndC, ghashAllToS);
+ }
+
+ @Override
+ public int getOutputSize(int inLen, boolean isFinal) {
+ int len = getBufferedLength();
+ if (isFinal) {
+ return len + inLen + tagLenBytes;
+ } else {
+ len += inLen;
+ return len - (len % blockCipher.getBlockSize());
+ }
+ }
+
+ @Override
+ byte[] doUpdate(byte[] in, int inOff, int inLen) {
+ checkReInit();
+ byte[] output = new byte[getOutputSize(inLen, false)];
+ try {
+ doUpdate(in, inOff, inLen, output, 0);
+ } catch (ShortBufferException e) {
+ // This should never happen because we just allocated output
+ throw new ProviderException("output buffer creation failed", e);
+ }
+ return output;
+ }
+
+ /**
+ * Encrypt update operation. This uses both the ibuffer and 'in' to
+ * encrypt as many blocksize data as possible. Any remaining data is
+ * put into the ibuffer.
+ */
+ @Override
+ public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) throws ShortBufferException {
+
+ checkReInit();
+
+ // 'inLen' stores the length to use with buffer 'in'.
+ // 'len' stores the length returned by the method.
+ int len = 0;
+ int bLen = getBufferedLength();
+ checkDataLength(inLen, bLen);
+
+ processAAD();
+ out = overlapDetection(in, inOfs, out, outOfs);
+
+ // if there is enough data in the ibuffer and 'in', encrypt it.
+ if (bLen > 0) {
+ byte[] buffer = ibuffer.toByteArray();
+ // number of bytes not filling a block
+ int remainder = blockSize - bLen;
+
+ // Construct and encrypt a block if there is enough 'buffer' and
+ // 'in' to make one
+ if ((inLen + bLen) >= blockSize) {
+ byte[] block = new byte[blockSize];
+
+ System.arraycopy(buffer, 0, block, 0, bLen);
+ System.arraycopy(in, inOfs, block, bLen, remainder);
+
+ len = gctrghash.update(block, 0, blockSize, out, outOfs);
+ inOfs += remainder;
+ inLen -= remainder;
+ outOfs += blockSize;
+ ibuffer.reset();
+ }
+ }
+
+ // Encrypt the remaining blocks inside of 'in'
+ if (inLen >= blockSize) {
+ len += gctrghash.update(in, inOfs, inLen, out, outOfs);
+ }
+
+ // Write any remaining bytes less than a blockSize into ibuffer.
+ int remainder = inLen % blockSize;
+ if (remainder > 0) {
+ initBuffer(remainder);
+ inLen -= remainder;
+ // remainder offset is based on original buffer length
+ ibuffer.write(in, inOfs + inLen, remainder);
+ }
+
+ restoreOut(out, len);
+ processed += len;
+ return len;
+ }
+
+ /**
+ * Encrypt update operation. This uses both the ibuffer and 'src' to
+ * encrypt as many blocksize data as possible. Any remaining data is
+ * put into the ibuffer.
+ */
+ @Override
+ public int doUpdate(ByteBuffer src, ByteBuffer dst)
+ throws ShortBufferException {
+ checkReInit();
+ int bLen = getBufferedLength();
+ checkDataLength(src.remaining(), bLen);
+
+ // 'len' stores the length returned by the method.
+ int len = 0;
+
+ processAAD();
+
+ dst = overlapDetection(src, dst);
+ // if there is enough data in the ibuffer and 'in', encrypt it.
+ if (bLen > 0) {
+ // number of bytes not filling a block
+ int remainder = blockSize - bLen;
+ // Check if there is enough 'src' and 'buffer' to fill a block
+ if (src.remaining() >= remainder) {
+ byte[] block = new byte[blockSize];
+ ByteBuffer buffer = ByteBuffer.wrap(ibuffer.toByteArray());
+ buffer.get(block, 0, bLen);
+ src.get(block, bLen, remainder);
+ len += cryptBlocks(
+ ByteBuffer.wrap(block, 0, blockSize), dst);
+ ibuffer.reset();
+ }
+ }
+
+ // encrypt any blocksized data in 'src'
+ if (src.remaining() >= blockSize) {
+ len += cryptBlocks(src, dst);
+ }
+
+ // Write the remaining bytes into the 'ibuffer'
+ if (src.remaining() > 0) {
+ initBuffer(src.remaining());
+ byte[] b = new byte[src.remaining()];
+ src.get(b);
+ // remainder offset is based on original buffer length
+ try {
+ ibuffer.write(b);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ restoreDst(dst);
+ return len;
+ }
+
+ /**
+ * Return final encrypted data with auth tag using byte[]
+ */
+ @Override
+ public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) throws IllegalBlockSizeException, ShortBufferException {
+ checkReInit();
+ try {
+ ArrayUtil.nullAndBoundsCheck(out, outOfs, getOutputSize(inLen,
+ true));
+ } catch (ArrayIndexOutOfBoundsException aiobe) {
+ throw new ShortBufferException("Output buffer invalid");
+ }
+
+ int bLen = getBufferedLength();
+ checkDataLength(inLen, bLen, tagLenBytes);
+ processAAD();
+ out = overlapDetection(in, inOfs, out, outOfs);
+
+ int resultLen = 0;
+ byte[] block;
+
+ // process what is in the ibuffer
+ if (bLen > 0) {
+ byte[] buffer = ibuffer.toByteArray();
+
+ // Make a block if the remaining ibuffer and 'in' can make one.
+ if (bLen + inLen >= blockSize) {
+ int r, bufOfs = 0;
+ block = new byte[blockSize];
+ r = mergeBlock(buffer, bufOfs, in, inOfs, inLen, block);
+ inOfs += r;
+ inLen -= r;
+ r = gctrghash.update(block, 0, blockSize, out,
+ outOfs);
+ outOfs += r;
+ resultLen += r;
+ processed += r;
} else {
- // data is empty, so complete the ghash op with the
- // remaining buffer
- ghashAllToS.doLastBlock(buffer, buffer.remaining());
+ // Need to consume all the ibuffer here to prepare for doFinal()
+ block = new byte[bLen + inLen];
+ System.arraycopy(buffer, 0, block, 0, bLen);
+ System.arraycopy(in, inOfs, block, bLen, inLen);
+ inLen += bLen;
+ in = block;
+ inOfs = 0;
}
}
- // Prepare buffer for decryption
- buffer.flip();
+
+ // process what is left in the input buffer
+ if (inLen > TRIGGERLEN) {
+ int r = throttleData(gctrghash, in, inOfs, inLen, out, outOfs);
+ inOfs += r;
+ inLen -= r;
+ outOfs += r;
+ resultLen += r;
+ processed += r;
+ }
+
+ processed += gctrghash.doFinal(in, inOfs, inLen, out, outOfs);
+ outOfs += inLen;
+ resultLen += inLen;
+
+ block = getLengthBlock(sizeOfAAD, processed);
+ ghashAllToS.update(block);
+ block = ghashAllToS.digest();
+ new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+ tagLenBytes, block, 0);
+
+ // copy the tag to the end of the buffer
+ System.arraycopy(block, 0, out, outOfs, tagLenBytes);
+ int len = resultLen + tagLenBytes;
+ restoreOut(out, len);
+
+ reInit = true;
+ return len;
}
- if (ct.remaining() > 0) {
- ghashAllToS.doLastBlock(ct, ct.remaining());
- }
- // Prepare buffer for decryption if available
- ct.reset();
+ /**
+ * Return final encrypted data with auth tag using bytebuffers
+ */
+ @Override
+ public int doFinal(ByteBuffer src, ByteBuffer dst) throws
+ IllegalBlockSizeException, ShortBufferException {
+ checkReInit();
+ dst = overlapDetection(src, dst);
+ int len = src.remaining() + getBufferedLength();
- 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);
+ // 'len' includes ibuffer data
+ checkDataLength(len, tagLenBytes);
+ if (dst.remaining() < len + tagLenBytes) {
+ throw new ShortBufferException("Output buffer too small, must" +
+ "be at least " + (len + tagLenBytes) + " bytes long");
+ }
- // check entire authentication tag for time-consistency
- int mismatch = 0;
- for (int i = 0; i < tagLenBytes; i++) {
- mismatch |= tag.get() ^ block[i];
+ processAAD();
+ if (len > 0) {
+ processed += doLastBlock(gctrghash,
+ (ibuffer == null || ibuffer.size() == 0) ? null :
+ ByteBuffer.wrap(ibuffer.toByteArray()), src, dst);
+ }
+
+ // release buffer if needed
+ if (ibuffer != null) {
+ ibuffer.reset();
+ }
+
+ byte[] block = getLengthBlock(sizeOfAAD, processed);
+ ghashAllToS.update(block);
+ block = ghashAllToS.digest();
+ new GCTR(blockCipher, preCounterBlock).doFinal(block, 0,
+ tagLenBytes, block, 0);
+ dst.put(block, 0, tagLenBytes);
+ restoreDst(dst);
+
+ reInit = true;
+ return (len + tagLenBytes);
}
- if (mismatch != 0) {
- throw new AEADBadTagException("Tag mismatch!");
+ // Handler method for encrypting blocks
+ int cryptBlocks(ByteBuffer src, ByteBuffer dst) {
+ int len;
+ if (src.remaining() > TRIGGERLEN) {
+ len = throttleData(gctrghash, src, dst);
+ } else {
+ len = gctrghash.update(src, dst);
+ }
+ processed += len;
+ return len;
}
-
- // Decrypt the all the input data and put it into dst
- doLastBlock(buffer, ct, dst);
- restoreDst(dst);
- src.position(src.limit());
- // 'processed' from the gctr decryption operation, not ghash
- return processed;
}
- // return tag length in bytes
- int getTagLen() {
- return this.tagLenBytes;
- }
+ /**
+ * Decryption Engine object
+ */
+ class GCMDecrypt extends GCMEngine {
+ // byte array of tag
+ byte[] tag;
+ // offset for byte[] operations
+ int tagOfs = 0;
- int getBufferedLength() {
- if (ibuffer == null) {
+ GCMDecrypt(SymmetricCipher blockCipher) {
+ super(blockCipher);
+ }
+
+ @Override
+ public int getOutputSize(int inLen, boolean isFinal) {
+ if (!isFinal) {
+ return 0;
+ }
+ return Math.max(inLen + getBufferedLength() - tagLenBytes, 0);
+ }
+
+ /**
+ * Find the tag in a given input buffer
+ *
+ * If tagOfs > 0, the tag is inside 'in' along with some encrypted data
+ * If tagOfs = 0, 'in' contains only the tag
+ * If tagOfs < 0, that tag is split between ibuffer and 'in'
+ * If tagOfs = -tagLenBytes, the tag is in the ibuffer, 'in' is empty.
+ */
+ void findTag(byte[] in, int inOfs, int inLen) {
+ tag = new byte[tagLenBytes];
+ if (inLen >= tagLenBytes) {
+ tagOfs = inLen - tagLenBytes;
+ System.arraycopy(in, inOfs + tagOfs, tag, 0,
+ tagLenBytes);
+ } else {
+ // tagOfs will be negative
+ byte[] buffer = ibuffer.toByteArray();
+ tagOfs = mergeBlock(buffer,
+ buffer.length - (tagLenBytes - inLen), in, inOfs, inLen,
+ tag) - tagLenBytes;
+ }
+ }
+
+ // Put the input data into the ibuffer
+ @Override
+ byte[] doUpdate(byte[] in, int inOff, int inLen) {
+ try {
+ doUpdate(in, inOff, inLen, null, 0);
+ } catch (ShortBufferException e) {
+ // update decryption has no output
+ }
+ return new byte[0];
+ }
+
+ // Put the input data into the ibuffer
+ @Override
+ public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) throws ShortBufferException {
+
+ processAAD();
+ if (inLen > 0) {
+ // store internally until decryptFinal is called because
+ // spec mentioned that only return recovered data after tag
+ // is successfully verified
+ initBuffer(inLen);
+ ibuffer.write(in, inOfs, inLen);
+ }
return 0;
- } else {
- return ibuffer.size();
+ }
+
+
+ // Put the src data into the ibuffer
+ @Override
+ public int doUpdate(ByteBuffer src, ByteBuffer dst)
+ throws ShortBufferException {
+
+ processAAD();
+ if (src.remaining() > 0) {
+ // If there is an array, use that to avoid the extra copy to
+ // take the src data out of the bytebuffer.
+ if (src.hasArray()) {
+ doUpdate(src.array(), src.arrayOffset() + src.position(),
+ src.remaining(), null, 0);
+ src.position(src.limit());
+ } else {
+ byte[] b = new byte[src.remaining()];
+ src.get(b);
+ initBuffer(b.length);
+ try {
+ ibuffer.write(b);
+ } catch (IOException e) {
+ throw new ProviderException(
+ "Unable to add remaining input to the buffer", e);
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Use any data from ibuffer and 'in' to first verify the auth tag. If
+ * the tag is valid, decrypt the data.
+ */
+ @Override
+ public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) throws IllegalBlockSizeException, AEADBadTagException,
+ ShortBufferException {
+ GHASH save = null;
+
+ int len = inLen + getBufferedLength();
+ try {
+ ArrayUtil.nullAndBoundsCheck(out, outOfs, len - tagLenBytes);
+ } catch (ArrayIndexOutOfBoundsException aiobe) {
+ throw new ShortBufferException("Output buffer invalid");
+ }
+
+ if (len < tagLenBytes) {
+ throw new AEADBadTagException("Input too short - need tag");
+ }
+
+ if (len - tagLenBytes > out.length - outOfs) {
+ save = ghashAllToS.clone();
+ }
+
+ checkDataLength(len - tagLenBytes);
+ processAAD();
+
+ findTag(in, inOfs, inLen);
+ byte[] block = getLengthBlock(sizeOfAAD,
+ decryptBlocks(ghashAllToS, in, inOfs, inLen, null, 0));
+ ghashAllToS.update(block);
+ block = ghashAllToS.digest();
+ new GCTR(blockCipher, preCounterBlock).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] ^ block[i];
+ }
+
+ if (mismatch != 0) {
+ throw new AEADBadTagException("Tag mismatch!");
+ }
+
+ if (save != null) {
+ ghashAllToS = save;
+ throw new ShortBufferException("Output buffer too small, must" +
+ "be at least " + (len - tagLenBytes) + " bytes long");
+ }
+
+ out = overlapDetection(in, inOfs, out, outOfs);
+ len = decryptBlocks(gctrPAndC, in, inOfs, inLen, out, outOfs);
+ restoreOut(out, len);
+ return len;
+ }
+
+ /**
+ * Use any data from ibuffer and 'src' to first verify the auth tag. If
+ * the tag is valid, decrypt the data.
+ */
+ @Override
+ public int doFinal(ByteBuffer src, ByteBuffer dst)
+ throws IllegalBlockSizeException, AEADBadTagException,
+ ShortBufferException {
+ GHASH save = null;
+
+ ByteBuffer tag;
+ ByteBuffer ct = src.duplicate();
+ ByteBuffer buffer = null;
+
+ // The 'len' the total amount of ciphertext
+ int len = ct.remaining() - tagLenBytes;
+
+ // Check if ibuffer has data
+ if (getBufferedLength() != 0) {
+ buffer = ByteBuffer.wrap(ibuffer.toByteArray());
+ len += buffer.remaining();
+ }
+
+ checkDataLength(len);
+
+ // Save GHASH context to allow the tag to be checked even though
+ // the dst buffer is too short. Context will be restored so the
+ // method can be called again with the proper sized dst buffer.
+ if (len > dst.remaining()) {
+ save = ghashAllToS.clone();
+ }
+
+ // Create buffer 'tag' that contains only the auth tag
+ if (ct.remaining() >= tagLenBytes) {
+ tag = src.duplicate();
+ tag.position(ct.limit() - tagLenBytes);
+ ct.limit(ct.limit() - tagLenBytes);
+ } else if (buffer != null) {
+ // 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();
+ // Set the limit to where the ciphertext ends
+ buffer.limit(limit);
+ tag.put(ct);
+ tag.flip();
+ } else {
+ throw new AEADBadTagException("Input too short - need tag");
+ }
+
+ // 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();
+
+ processAAD();
+ // Perform GHASH check on data
+ doLastBlock(ghashAllToS, buffer, ct, null);
+
+ byte[] block = getLengthBlock(sizeOfAAD, len);
+ ghashAllToS.update(block);
+ block = ghashAllToS.digest();
+ new GCTR(blockCipher, preCounterBlock).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!");
+ }
+
+ if (save != null) {
+ ghashAllToS = save;
+ throw new ShortBufferException("Output buffer too small, must" +
+ " be at least " + len + " bytes long");
+ }
+
+ // Prepare for decryption
+ if (buffer != null) {
+ buffer.flip();
+ }
+ ct.reset();
+ processed = 0;
+ // Check for overlap in the bytebuffers
+ dst = overlapDetection(src, dst);
+
+ // Decrypt the all the input data and put it into dst
+ doLastBlock(gctrPAndC, buffer, ct, dst);
+ restoreDst(dst);
+ src.position(src.limit());
+ if (ibuffer != null) {
+ ibuffer.reset();
+ }
+ return processed;
+ }
+
+ /**
+ * This method organizes the data from the ibuffer and 'in' to
+ * blocksize operations for GHASH and GCTR decryption operations.
+ * When this method is used, all the data is either in the ibuffer
+ * or in 'in'.
+ */
+ int decryptBlocks(GCM op, byte[] in, int inOfs, int inLen,
+ byte[] out, int outOfs) {
+ byte[] buffer;
+ byte[] block;
+ int len = 0;
+
+ // Calculate the encrypted data length inside the ibuffer
+ // considering the tag location
+ int bLen = getBufferedLength();
+
+ // Change the inLen based of the tag location.
+ if (tagOfs < 0) {
+ inLen = 0;
+ bLen += tagOfs;
+ } else {
+ inLen -= tagLenBytes;
+ }
+
+ if (bLen > 0) {
+ buffer = ibuffer.toByteArray();
+
+ if (bLen >= blockSize) {
+ len += op.update(buffer, 0, bLen, out, outOfs);
+ outOfs += len; // noop for ghash
+ // Use len as it becomes the ibuffer offset, if
+ // needed, in the next op
+ }
+
+ // merge the remaining ibuffer with the 'in'
+ int bufRemainder = bLen - len;
+ if (bufRemainder > 0) {
+ block = new byte[blockSize];
+ int inUsed = mergeBlock(buffer, len, bufRemainder, in,
+ inOfs, inLen, block);
+ // update the input parameters for what was taken out of 'in'
+ inOfs += inUsed;
+ inLen -= inUsed;
+ // If is more than block between the merged data and 'in',
+ // update(), otherwise setup for final
+ if (inLen > 0) {
+ int resultLen = op.update(block, 0, blockSize,
+ out, outOfs);
+ outOfs += resultLen; // noop for ghash
+ len += resultLen;
+ } else {
+ in = block;
+ inOfs = 0;
+ inLen = inUsed + bufRemainder;
+ }
+ }
+ }
+
+ // Finish off the operation
+ if (inLen > TRIGGERLEN) {
+ int l = throttleData(op, in, inOfs, inLen, out, outOfs);
+ inOfs += l;
+ inLen -= l;
+ outOfs += l; // noop for ghash
+ len += l;
+ }
+ return len + op.doFinal(in, inOfs, inLen, out, outOfs);
+ }
+ }
+
+ public static final class AESGCM extends GaloisCounterMode {
+ public AESGCM() {
+ super(-1, new AESCrypt());
+ }
+ }
+
+ public static final class AES128 extends GaloisCounterMode {
+ public AES128() {
+ super(16, new AESCrypt());
+ }
+ }
+
+ public static final class AES192 extends GaloisCounterMode {
+ public AES192() {
+ super(24, new AESCrypt());
+ }
+ }
+
+ public static final class AES256 extends GaloisCounterMode {
+ public AES256() {
+ super(32, new AESCrypt());
}
}
/**
- * 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.
+ * This class is for encryption when both GCTR and GHASH
+ * can operation in parallel.
*/
- ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) {
- if (src.isDirect() && dst.isDirect()) {
- DirectBuffer dsrc = (DirectBuffer) src;
- DirectBuffer ddst = (DirectBuffer) dst;
+ static final class GCTRGHASH implements GCM {
+ GCTR gctr;
+ GHASH ghash;
- // 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() != 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;
+ GCTRGHASH(GCTR c, GHASH g) {
+ gctr = c;
+ ghash = g;
}
- // 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;
+ @Override
+ public int update(byte[] in, int inOfs, int inLen, byte[] out,
+ int outOfs) {
+ int len = gctr.update(in, inOfs, inLen, out, outOfs);
+ ghash.update(out, outOfs, len);
+ return len;
}
- dst.flip();
- originalDst.put(dst);
- originalDst = null;
+ @Override
+ public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
+ dst.mark();
+ int len = gctr.update(in, inOfs, inLen, dst);
+ dst.reset();
+ ghash.update(dst, len);
+ return len;
+ }
+
+ @Override
+ public int update(ByteBuffer src, ByteBuffer dst) {
+ dst.mark();
+ int len = gctr.update(src, dst);
+ dst.reset();
+ ghash.update(dst, len);
+ return len;
+ }
+
+ @Override
+ public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
+ int len = gctr.doFinal(in, inOfs, inLen, out, outOfs);
+ ghash.doFinal(out, outOfs, len);
+ return len;
+ }
+
+ @Override
+ public int doFinal(ByteBuffer src, ByteBuffer dst) {
+ dst.mark();
+ int l = gctr.doFinal(src, dst);
+ dst.reset();
+ ghash.doFinal(dst, l);
+ return l;
+ }
}
}
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
index 521afa4b696..a020e1c15d8 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java
@@ -165,7 +165,7 @@ public final class SunJCE extends Provider {
"|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64" +
"|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64";
final String BLOCK_MODES128 = BLOCK_MODES +
- "|GCM|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
+ "|CFB72|CFB80|CFB88|CFB96|CFB104|CFB112|CFB120|CFB128" +
"|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128";
final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
@@ -214,9 +214,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_128/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding",
attrs);
- psA("Cipher", "AES_128/GCM/NoPadding",
- "com.sun.crypto.provider.AESCipher$AES128_GCM_NoPadding",
- attrs);
psA("Cipher", "AES_128/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES128_KW_NoPadding",
attrs);
@@ -239,9 +236,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_192/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding",
attrs);
- psA("Cipher", "AES_192/GCM/NoPadding",
- "com.sun.crypto.provider.AESCipher$AES192_GCM_NoPadding",
- attrs);
psA("Cipher", "AES_192/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES192_KW_NoPadding",
attrs);
@@ -264,9 +258,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_256/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding",
attrs);
- psA("Cipher", "AES_256/GCM/NoPadding",
- "com.sun.crypto.provider.AESCipher$AES256_GCM_NoPadding",
- attrs);
psA("Cipher", "AES_256/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES256_KW_NoPadding",
attrs);
@@ -277,6 +268,23 @@ public final class SunJCE extends Provider {
"com.sun.crypto.provider.KeyWrapCipher$AES256_KWP_NoPadding",
attrs);
+ attrs.clear();
+ attrs.put("SupportedModes", "GCM");
+ attrs.put("SupportedKeyFormats", "RAW");
+
+ ps("Cipher", "AES/GCM/NoPadding",
+ "com.sun.crypto.provider.GaloisCounterMode$AESGCM", null,
+ attrs);
+ psA("Cipher", "AES_128/GCM/NoPadding",
+ "com.sun.crypto.provider.GaloisCounterMode$AES128",
+ attrs);
+ psA("Cipher", "AES_192/GCM/NoPadding",
+ "com.sun.crypto.provider.GaloisCounterMode$AES192",
+ attrs);
+ psA("Cipher", "AES_256/GCM/NoPadding",
+ "com.sun.crypto.provider.GaloisCounterMode$AES256",
+ attrs);
+
attrs.clear();
attrs.put("SupportedModes", "CBC");
attrs.put("SupportedPaddings", "NOPADDING");
diff --git a/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java b/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
index 6c65c19ba85..ed826dff24a 100644
--- a/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
+++ b/src/java.base/share/classes/sun/security/util/SecurityProviderConstants.java
@@ -217,8 +217,6 @@ public final class SecurityProviderConstants {
store("DiffieHellman", KnownOIDs.DiffieHellman);
- store("AES", KnownOIDs.AES, "Rijndael");
-
store("EC", KnownOIDs.EC, "EllipticCurve");
store("X.509", null, "X509");
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
index d96cb00a6f4..58267c2ffba 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/Encrypt.java
@@ -27,6 +27,7 @@ import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HexFormat;
import java.util.List;
import javax.crypto.SecretKey;
import javax.crypto.Cipher;
@@ -84,7 +85,7 @@ import javax.crypto.KeyGenerator;
*/
public class Encrypt {
- private static final String ALGORITHMS[] = { "AES", "Rijndael" };
+ private static final String ALGORITHMS[] = { "AES" };
private static final int KEY_STRENGTHS[] = { 128, 192, 256 };
private static final int TEXT_LENGTHS[] = { 0, 256, 1024 };
private static final int AAD_LENGTHS[] = { 0, 8, 128, 256, 1024 };
@@ -230,8 +231,12 @@ public class Encrypt {
combination_16(outputTexts, mode, AAD, inputText, params);
for (int k = 0; k < outputTexts.size(); k++) {
+ HexFormat hex = HexFormat.of().withUpperCase();
if (!Arrays.equals(output, outputTexts.get(k))) {
- throw new RuntimeException("Combination #" + k + " failed");
+ System.out.println("Combination #" + (k + 1) + "\nresult " +
+ hex.formatHex(outputTexts.get(k)) +
+ "\nexpected: " + hex.formatHex(output));
+ throw new RuntimeException("Combination #" + (k + 1) + " failed");
}
}
return output;
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
index 54d271d63ae..2c0dcae2807 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2021, 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
@@ -36,11 +36,11 @@ 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.HexFormat;
import java.util.List;
public class GCMBufferTest implements Cloneable {
@@ -65,6 +65,7 @@ public class GCMBufferTest implements Cloneable {
boolean theoreticalCheck;
List dataSet;
int inOfs = 0, outOfs = 0;
+ static HexFormat hex = HexFormat.of();
static class Data {
int id;
@@ -108,14 +109,21 @@ public class GCMBufferTest implements Cloneable {
new GCMParameterSpec(tag.length * 8, this.iv));
tct = c.doFinal(pt);
} catch (Exception e) {
- System.out.println("Error in generating data for length " +
- ptlen);
+ throw new RuntimeException("Error in generating data for length " +
+ ptlen, e);
}
ct = new byte[ptlen];
System.arraycopy(tct, 0, ct, 0, ct.length);
System.arraycopy(tct, ct.length, tag, 0, tag.length);
}
+ private static final byte[] HexToBytes(String hexVal) {
+ if (hexVal == null) {
+ return new byte[0];
+ }
+ return hex.parseHex(hexVal);
+ }
+
}
/**
@@ -176,7 +184,7 @@ public class GCMBufferTest implements Cloneable {
return this;
}
}
- throw new Exception("Unaeble to find dataSet id = " + id);
+ throw new Exception("Unable to find dataSet id = " + id);
}
/**
@@ -244,7 +252,7 @@ public class GCMBufferTest implements Cloneable {
void test() throws Exception {
int i = 1;
- System.out.println("Algo: " + algo + " \tOps: " + ops.toString());
+ System.err.println("Algo: " + algo + " \tOps: " + ops.toString());
for (Data data : dataSet) {
// If incrementalSegments is enabled, run through that test only
@@ -256,31 +264,31 @@ public class GCMBufferTest implements Cloneable {
sizes = new int[ops.size()];
while (incrementSizes(data.pt.length)) {
- System.out.print("Encrypt: Data Index: " + i + " \tSizes[ ");
+ System.err.print("Encrypt: Data Index: " + i + " \tSizes[ ");
for (int v : sizes) {
- System.out.print(v + " ");
+ System.err.print(v + " ");
}
- System.out.println("]");
+ System.err.println("]");
encrypt(data);
}
Arrays.fill(sizes, 0);
while (incrementSizes(data.ct.length + data.tag.length)) {
- System.out.print("Decrypt: Data Index: " + i + " \tSizes[ ");
+ System.err.print("Decrypt: Data Index: " + i + " \tSizes[ ");
for (int v : sizes) {
- System.out.print(v + " ");
+ System.err.print(v + " ");
}
- System.out.println("]");
+ System.err.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);
+ System.err.println("Encrypt: Data Index: " + i);
encrypt(data);
- System.out.println("Decrypt: Data Index: " + i);
+ System.err.println("Decrypt: Data Index: " + i);
decrypt(data);
}
i++;
@@ -298,13 +306,13 @@ public class GCMBufferTest implements Cloneable {
data.tag.length);
// Test different input/output buffers
- System.out.println("\tinput len: " + input.length + " inOfs " +
+ System.err.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 " +
+ System.err.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in/out buffer: in-place");
cryptoSameBuffer(true, data, input, output);
}
@@ -320,13 +328,13 @@ public class GCMBufferTest implements Cloneable {
output = data.pt;
// Test different input/output buffers
- System.out.println("\tinput len: " + input.length + " inOfs " +
- inOfs + " outOfs " + outOfs + " in-place: different");
+ System.err.println("\tinput len: " + input.length + " inOfs " +
+ inOfs + " outOfs " + outOfs + " in/out buffer: different");
crypto(false, data, input, output);
// Test with in-place buffers
if (same) {
- System.out.println("\tinput len: " + input.length + " inOfs " +
+ System.err.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in-place: same");
cryptoSameBuffer(false, data, input, output);
}
@@ -484,12 +492,10 @@ public class GCMBufferTest implements Cloneable {
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));
+ "):\nresult (len=" + ctresult.length + "): " +
+ hex.formatHex(ctresult) +
+ "\nexpected (len=" + output.length + "): " +
+ hex.formatHex(output);
System.err.println(s);
throw new Exception(s);
@@ -605,10 +611,9 @@ public class GCMBufferTest implements Cloneable {
output.length) != 0) {
String s = "Ciphertext mismatch (" + v.name() +
"):\nresult (len=" + len + "):\n" +
- byteToHex(out) +
+ hex.formatHex(out) +
"\nexpected (len=" + output.length + "):\n" +
- String.format("%0" + (output.length << 1) + "x",
- new BigInteger(1, output));
+ hex.formatHex(output);
System.err.println(s);
throw new Exception(s);
}
@@ -623,7 +628,10 @@ public class GCMBufferTest implements Cloneable {
}
public static void main(String args[]) throws Exception {
+ GCMBufferTest t;
+
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)));
@@ -662,7 +670,7 @@ public class GCMBufferTest implements Cloneable {
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",
+ t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).dataSegments(
new int[] { 1, 1, GCMBufferTest.REMAINDER});
t.clone().test();
@@ -678,6 +686,7 @@ public class GCMBufferTest implements Cloneable {
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();
@@ -710,26 +719,10 @@ public class GCMBufferTest implements Cloneable {
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();
+ new GCMBufferTest("AES/GCM/NoPadding",
+ List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
+ dataSegments(new int[] { 49, 0, 2 }).dataSet(0).test();
}
// Test data
@@ -762,8 +755,7 @@ public class GCMBufferTest implements Cloneable {
"b6e6f197168f5049aeda32dafbdaeb"),
// zero'd test data
new Data("AES", 3, "272f16edb81a7abbea887357a58c1917",
- "794ec588176c703d3d2a7a07",
- new byte[256], null,
+ "794ec588176c703d3d2a7a07", new byte[256], null,
"15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" +
"e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" +
"07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" +
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java
new file mode 100644
index 00000000000..3cfaed9bd9d
--- /dev/null
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMShortBuffer.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2021, 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.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HexFormat;
+
+/*
+ * @test
+ * @summary Call decrypt doFinal() with different output values to see if the
+ * the operation can complete after a ShortBufferException
+ */
+public class GCMShortBuffer {
+ static Cipher c;
+ static final GCMParameterSpec iv = new GCMParameterSpec(128, new byte[16]);
+ static final SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+ static byte cipherText[], plaintext[] = new byte[51];
+ boolean error = false;
+
+ GCMShortBuffer(byte[] out) throws Exception {
+ int len = cipherText.length - 1;
+
+ c.init(Cipher.DECRYPT_MODE, keySpec, iv);
+ byte[] pt = new byte[c.getOutputSize(cipherText.length)];
+ c.update(cipherText, 0, 1);
+ try {
+ c.doFinal(cipherText, 1, len, out, 0);
+ } catch (ShortBufferException e) {
+ System.out.println("ShortBuffer caught");
+ } catch (Exception e) {
+ throw e;
+ }
+ int r = c.doFinal(cipherText, 1, len, pt, 0);
+ if (r != pt.length) {
+ System.out.println(
+ "doFinal() return ( " + r + ") is not the same" +
+ "as getOutputSize returned" + pt.length);
+ error = true;
+ }
+ if (Arrays.compare(pt, plaintext) != 0) {
+ System.out.println("output : " + HexFormat.of().formatHex(pt));
+ System.out.println("expected: " +
+ HexFormat.of().formatHex(plaintext));
+ System.out.println("output and plaintext do not match");
+ error = true;
+ }
+ if (error) {
+ throw new Exception("An error has occurred");
+ }
+ }
+
+ GCMShortBuffer(ByteBuffer dst) throws Exception {
+ int len = cipherText.length - 1;
+ ByteBuffer out = ByteBuffer.allocate(plaintext.length);
+
+ c.init(Cipher.DECRYPT_MODE, keySpec, iv);
+ c.update(cipherText, 0, 1);
+ ByteBuffer ct = ByteBuffer.wrap(cipherText, 1, len);
+ try {
+ c.doFinal(ct , dst);
+ } catch (ShortBufferException e) {
+ System.out.println("ShortBuffer caught");
+ } catch (Exception e) {
+ throw e;
+ }
+ int r = c.doFinal(ByteBuffer.wrap(cipherText, 1, len), out);
+ out.flip();
+ if (r != out.capacity()) {
+ System.out.println(
+ "doFinal() return ( " + r + ") is not the same" +
+ " as getOutputSize returned" + out.capacity());
+ error = true;
+ }
+ if (out.compareTo(ByteBuffer.wrap(plaintext)) != 0) {
+ System.out.println("output and plaintext do not match");
+ System.out.println("output : " +
+ HexFormat.of().formatHex(out.array()));
+ System.out.println("expected: " +
+ HexFormat.of().formatHex(plaintext));
+ error = true;
+ }
+ if (error) {
+ throw new Exception("An error has occurred");
+ }
+ }
+
+ public static void main(String args[]) throws Exception {
+ c = Cipher.getInstance("AES/GCM/NoPadding");
+ c.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+ cipherText = c.doFinal(plaintext);
+
+ new GCMShortBuffer(new byte[13]);
+ new GCMShortBuffer(new byte[50]);
+ new GCMShortBuffer(ByteBuffer.allocate(13));
+ new GCMShortBuffer(ByteBuffer.allocate(50));
+ }
+
+
+}
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
index 9e84887c990..c8af2d2fbd5 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java
@@ -107,6 +107,9 @@ public class OverlapByteBuffer {
}
}
+ System.out.println("inOfsInBuf = " + inOfsInBuf);
+ System.out.println("outOfsInBuf = " + outOfsInBuf);
+
// Copy data into shared buffer
input.put(baseBuf);
input.flip();
@@ -132,8 +135,6 @@ public class OverlapByteBuffer {
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(
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
index 0ea8d01bc92..dd0d97101c1 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestAESCipher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2021, 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,16 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.AlgorithmParameterSpec;
-import java.util.Random;
+import java.util.HexFormat;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
/**
* @test
@@ -43,7 +44,6 @@ import javax.crypto.spec.IvParameterSpec;
* doesn't use IV).
* @author Liwen Wang
* @author Parag Salvi
- * @key randomness
*/
public class TestAESCipher {
@@ -56,7 +56,9 @@ public class TestAESCipher {
"OFB32", "OFB40", "OFB48", "OFB56", "OFB64", "OFB72", "OFB80",
"OFB88", "OFB96", "OFB104", "OFB112", "OFB120", "GCM" };
private static final String[] PADDING = { "NoPadding", "PKCS5Padding" };
- private static final int KEY_LENGTH = 128;
+ private static final int KEY_LENGTH = 16;
+ static byte[] plainText = new byte[128];
+ static byte[] key = new byte[KEY_LENGTH];
public static void main(String argv[]) throws Exception {
TestAESCipher test = new TestAESCipher();
@@ -73,32 +75,31 @@ public class TestAESCipher {
}
}
+
public void runTest(String algo, String mo, String pad) throws Exception {
- Cipher ci = null;
- byte[] iv = null;
- AlgorithmParameterSpec aps = null;
- SecretKey key = null;
+ Cipher ci;
+ System.out.println("Testing " + algo + "/" + mo + "/" + pad);
+
+ byte[] iv = new byte[16];
+ AlgorithmParameterSpec aps = new GCMParameterSpec(128, iv);
+ SecretKey key = new SecretKeySpec(this.key, 0, KEY_LENGTH,"AES");
+
try {
// Initialization
- Random rdm = new Random();
- byte[] plainText = new byte[128];
- rdm.nextBytes(plainText);
-
ci = Cipher.getInstance(algo + "/" + mo + "/" + pad, PROVIDER);
- KeyGenerator kg = KeyGenerator.getInstance(algo, PROVIDER);
- kg.init(KEY_LENGTH);
- key = kg.generateKey();
// encrypt
- if (!mo.equalsIgnoreCase("GCM")) {
+ if (mo.equalsIgnoreCase("GCM")) {
ci.init(Cipher.ENCRYPT_MODE, key, aps);
+ } else if (mo.equalsIgnoreCase("ECB")) {
+ ci.init(Cipher.ENCRYPT_MODE, key, (AlgorithmParameterSpec)null);
} else {
- ci.init(Cipher.ENCRYPT_MODE, key);
+ ci.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
}
byte[] cipherText = new byte[ci.getOutputSize(plainText.length)];
int offset = ci.update(plainText, 0, plainText.length, cipherText,
- 0);
+ 0);
ci.doFinal(cipherText, offset);
if (!mo.equalsIgnoreCase("ECB")) {
@@ -117,25 +118,24 @@ public class TestAESCipher {
byte[] recoveredText = new byte[ci.getOutputSize(cipherText.length)];
int len = ci.doFinal(cipherText, 0, cipherText.length,
recoveredText);
- byte[] tmp = new byte[len];
- System.arraycopy(recoveredText, 0, tmp, 0, len);
// Comparison
- if (!java.util.Arrays.equals(plainText, tmp)) {
+ if (!java.util.Arrays.equals(plainText, 0 , plainText.length,
+ recoveredText, 0, len)) {
System.out.println("Original: ");
- dumpBytes(plainText);
+ System.out.println(HexFormat.of().formatHex(plainText));
System.out.println("Recovered: ");
- dumpBytes(tmp);
- throw new RuntimeException(
- "Original text is not equal with recovered text, with mode:"
- + mo);
+ System.out.println(HexFormat.of().
+ formatHex(recoveredText, 0, len));
+ throw new RuntimeException("Original text is not equal with " +
+ "recovered text, with mode:" + mo);
}
} catch (NoSuchAlgorithmException e) {
//CFB7 and OFB150 are for negative testing
if (!mo.equalsIgnoreCase("CFB7") && !mo.equalsIgnoreCase("OFB150")) {
- System.out.println("Unexpected NoSuchAlgorithmException with mode: "
- + mo);
+ System.out.println("Unexpected NoSuchAlgorithmException with" +
+ " mode: " + mo);
throw new RuntimeException("Test failed!");
}
} catch ( NoSuchProviderException | NoSuchPaddingException
@@ -146,12 +146,4 @@ public class TestAESCipher {
throw e;
}
}
-
- private void dumpBytes(byte[] bytes) {
- for (byte b : bytes) {
- System.out.print(Integer.toHexString(b));
- }
-
- System.out.println();
- }
}
diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
index f4cc9276bbc..e158bc54652 100644
--- a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
+++ b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestSameBuffer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2021, 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
@@ -46,7 +46,7 @@ import javax.crypto.spec.IvParameterSpec;
*/
public class TestSameBuffer {
- private static final String ALGORITHM = "Rijndael";
+ private static final String ALGORITHM = "AES";
private static final String PROVIDER = "SunJCE";
private static final String[] MODES = { "ECb", "CbC", "OFB", "CFB150",
"cFB", "CFB7", " cFB8", "cFB16", "cFB24", "cFB32", "Cfb40",