8255557: Decouple GCM from CipherCore

Reviewed-by: valeriep
This commit is contained in:
Anthony Scarpino 2021-06-07 22:22:28 +00:00
parent e546ae27ff
commit c7c77fd32b
15 changed files with 2087 additions and 1545 deletions

@ -138,21 +138,6 @@ abstract class AESCipher extends CipherSpi {
super(32, "CFB", "NOPADDING"); 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 // utility method used by AESCipher and AESWrapCipher
static final void checkKeySize(Key key, int fixedKeySize) 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 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 * 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) protected void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException { throws InvalidKeyException {
checkKeySize(key, fixedKeySize); checkKeySize(key, fixedKeySize);
updateCalled = false;
core.init(opmode, key, random); core.init(opmode, key, random);
} }
@ -355,7 +335,6 @@ abstract class AESCipher extends CipherSpi {
SecureRandom random) SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException { throws InvalidKeyException, InvalidAlgorithmParameterException {
checkKeySize(key, fixedKeySize); checkKeySize(key, fixedKeySize);
updateCalled = false;
core.init(opmode, key, params, random); core.init(opmode, key, params, random);
} }
@ -364,7 +343,6 @@ abstract class AESCipher extends CipherSpi {
SecureRandom random) SecureRandom random)
throws InvalidKeyException, InvalidAlgorithmParameterException { throws InvalidKeyException, InvalidAlgorithmParameterException {
checkKeySize(key, fixedKeySize); checkKeySize(key, fixedKeySize);
updateCalled = false;
core.init(opmode, key, params, random); core.init(opmode, key, params, random);
} }
@ -389,7 +367,6 @@ abstract class AESCipher extends CipherSpi {
*/ */
protected byte[] engineUpdate(byte[] input, int inputOffset, protected byte[] engineUpdate(byte[] input, int inputOffset,
int inputLen) { int inputLen) {
updateCalled = true;
return core.update(input, inputOffset, inputLen); return core.update(input, inputOffset, inputLen);
} }
@ -419,7 +396,6 @@ abstract class AESCipher extends CipherSpi {
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) byte[] output, int outputOffset)
throws ShortBufferException { throws ShortBufferException {
updateCalled = true;
return core.update(input, inputOffset, inputLen, output, return core.update(input, inputOffset, inputLen, output,
outputOffset); outputOffset);
} }
@ -458,7 +434,6 @@ abstract class AESCipher extends CipherSpi {
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException { throws IllegalBlockSizeException, BadPaddingException {
byte[] out = core.doFinal(input, inputOffset, inputLen); byte[] out = core.doFinal(input, inputOffset, inputLen);
updateCalled = false;
return out; return out;
} }
@ -504,7 +479,6 @@ abstract class AESCipher extends CipherSpi {
BadPaddingException { BadPaddingException {
int outLen = core.doFinal(input, inputOffset, inputLen, output, int outLen = core.doFinal(input, inputOffset, inputLen, output,
outputOffset); outputOffset);
updateCalled = false;
return outLen; return outLen;
} }
@ -577,86 +551,6 @@ abstract class AESCipher extends CipherSpi {
wrappedKeyType); wrappedKeyType);
} }
/**
* Continues a multi-part update of the Additional Authentication
* Data (AAD), using a subset of the provided buffer.
* <p>
* 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).
* <p>
* 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).
* <p>
* 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 * Finalize crypto operation with ByteBuffers
* *
@ -672,10 +566,6 @@ abstract class AESCipher extends CipherSpi {
protected int engineDoFinal(ByteBuffer input, ByteBuffer output) protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException { BadPaddingException {
if (core.getMode() == CipherCore.GCM_MODE && !input.hasArray()) { return super.engineDoFinal(input, output);
return core.gcmDoFinal(input, output);
} else {
return super.engineDoFinal(input, output);
}
} }
} }

@ -25,7 +25,6 @@
package com.sun.crypto.provider; package com.sun.crypto.provider;
import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
@ -83,7 +82,6 @@ final class CipherCore {
* currently, only the following cases have non-zero values: * currently, only the following cases have non-zero values:
* 1) CTS mode - due to its special handling on the last two blocks * 1) CTS mode - due to its special handling on the last two blocks
* (the last one may be incomplete). * (the last one may be incomplete).
* 2) GCM mode + decryption - due to its trailing tag bytes
*/ */
private int minBytes = 0; private int minBytes = 0;
@ -125,24 +123,6 @@ final class CipherCore {
private static final int PCBC_MODE = 4; private static final int PCBC_MODE = 4;
private static final int CTR_MODE = 5; private static final int CTR_MODE = 5;
private static final int CTS_MODE = 6; 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 * Creates an instance of CipherCore with default ECB mode and
@ -197,15 +177,6 @@ final class CipherCore {
cipher = new CounterMode(rawImpl); cipher = new CounterMode(rawImpl);
unitBytes = 1; unitBytes = 1;
padding = null; 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")) { } else if (modeUpperCase.startsWith("CFB")) {
cipherMode = CFB_MODE; cipherMode = CFB_MODE;
unitBytes = getNumOfUnit(mode, "CFB".length(), blockSize); 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) private static int getNumOfUnit(String mode, int offset, int blockSize)
throws NoSuchAlgorithmException { throws NoSuchAlgorithmException {
int result = blockSize; // use blockSize as default value int result = blockSize; // use blockSize as default value
@ -279,17 +241,13 @@ final class CipherCore {
+ " not implemented"); + " not implemented");
} }
if ((padding != null) && if ((padding != null) &&
((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE) ((cipherMode == CTR_MODE) || (cipherMode == CTS_MODE))) {
|| (cipherMode == GCM_MODE))) {
padding = null; padding = null;
String modeStr = null; String modeStr = null;
switch (cipherMode) { switch (cipherMode) {
case CTR_MODE: case CTR_MODE:
modeStr = "CTR"; modeStr = "CTR";
break; break;
case GCM_MODE:
modeStr = "GCM";
break;
case CTS_MODE: case CTS_MODE:
modeStr = "CTS"; modeStr = "CTS";
break; break;
@ -310,7 +268,7 @@ final class CipherCore {
* <code>inputLen</code> (in bytes). * <code>inputLen</code> (in bytes).
* *
* <p>This call takes into account any unprocessed (buffered) data from a * <p>This call takes into account any unprocessed (buffered) data from a
* previous <code>update</code> call, padding, and AEAD tagging. * previous <code>update</code> call, and padding.
* *
* <p>The actual output length of the next <code>update</code> or * <p>The actual output length of the next <code>update</code> or
* <code>doFinal</code> call may be smaller than the length returned by * <code>doFinal</code> call may be smaller than the length returned by
@ -326,36 +284,19 @@ final class CipherCore {
} }
private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) { private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
int totalLen = Math.addExact(buffered, cipher.getBufferedLength()); int totalLen = buffered;
totalLen = Math.addExact(totalLen, inputLen); totalLen = Math.addExact(totalLen, inputLen);
switch (cipherMode) { if (padding != null && !decrypting) {
case GCM_MODE: if (unitBytes != blockSize) {
if (isDoFinal) { if (totalLen < diffBlocksize) {
int tagLen = ((GaloisCounterMode) cipher).getTagLen(); totalLen = diffBlocksize;
if (!decrypting) {
totalLen = Math.addExact(totalLen, tagLen);
} else { } 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; return totalLen;
} }
@ -398,26 +339,15 @@ final class CipherCore {
AlgorithmParameterSpec spec; AlgorithmParameterSpec spec;
byte[] iv = getIV(); byte[] iv = getIV();
if (iv == null) { if (iv == null) {
// generate spec using default value iv = new byte[blockSize];
if (cipherMode == GCM_MODE) {
iv = new byte[GaloisCounterMode.DEFAULT_IV_LEN];
} else {
iv = new byte[blockSize];
}
SunJCE.getRandom().nextBytes(iv); SunJCE.getRandom().nextBytes(iv);
} }
if (cipherMode == GCM_MODE) { if (algName.equals("RC2")) {
algName = "GCM"; RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
spec = new GCMParameterSpec spec = new RC2ParameterSpec
(((GaloisCounterMode) cipher).getTagLen()*8, iv); (rawImpl.getEffectiveKeyBits(), iv);
} else { } else {
if (algName.equals("RC2")) { spec = new IvParameterSpec(iv);
RC2Crypt rawImpl = (RC2Crypt) cipher.getEmbeddedCipher();
spec = new RC2ParameterSpec
(rawImpl.getEffectiveKeyBits(), iv);
} else {
spec = new IvParameterSpec(iv);
}
} }
try { try {
params = AlgorithmParameters.getInstance(algName, params = AlgorithmParameters.getInstance(algName,
@ -504,106 +434,51 @@ final class CipherCore {
|| (opmode == Cipher.UNWRAP_MODE); || (opmode == Cipher.UNWRAP_MODE);
byte[] keyBytes = getKeyBytes(key); byte[] keyBytes = getKeyBytes(key);
try { byte[] ivBytes = null;
int tagLen = -1; if (params != null) {
byte[] ivBytes = null; if (params instanceof IvParameterSpec) {
if (params != null) { ivBytes = ((IvParameterSpec) params).getIV();
if (cipherMode == GCM_MODE) { if ((ivBytes == null) || (ivBytes.length != blockSize)) {
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) {
throw new InvalidAlgorithmParameterException throw new InvalidAlgorithmParameterException
("ECB mode cannot use IV"); ("Wrong IV length: must be " + blockSize +
" bytes long");
} }
} else if (ivBytes == null) { } else if (params instanceof RC2ParameterSpec) {
if (decrypting) { ivBytes = ((RC2ParameterSpec) params).getIV();
throw new InvalidAlgorithmParameterException("Parameters " if ((ivBytes != null) && (ivBytes.length != blockSize)) {
+ "missing"); 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 { } else {
cipher.init(decrypting, algorithm, keyBytes, ivBytes); throw new InvalidAlgorithmParameterException
} ("Unsupported parameter: " + params);
// skip checking key+iv from now on until after doFinal()
requireReinit = false;
} finally {
if (lastEncKey != keyBytes) {
Arrays.fill(keyBytes, (byte) 0);
} }
} }
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, void init(int opmode, Key key, AlgorithmParameters params,
@ -613,16 +488,11 @@ final class CipherCore {
String paramType = null; String paramType = null;
if (params != null) { if (params != null) {
try { try {
if (cipherMode == GCM_MODE) { // NOTE: RC2 parameters are always handled through
paramType = "GCM"; // init(..., AlgorithmParameterSpec,...) method, so
spec = params.getParameterSpec(GCMParameterSpec.class); // we can assume IvParameterSpec type here.
} else { paramType = "IV";
// NOTE: RC2 parameters are always handled through spec = params.getParameterSpec(IvParameterSpec.class);
// init(..., AlgorithmParameterSpec,...) method, so
// we can assume IvParameterSpec type here.
paramType = "IV";
spec = params.getParameterSpec(IvParameterSpec.class);
}
} catch (InvalidParameterSpecException ipse) { } catch (InvalidParameterSpecException ipse) {
throw new InvalidAlgorithmParameterException throw new InvalidAlgorithmParameterException
("Wrong parameter type: " + paramType + " expected"); ("Wrong parameter type: " + paramType + " expected");
@ -671,7 +541,6 @@ final class CipherCore {
* (e.g., has not been initialized) * (e.g., has not been initialized)
*/ */
byte[] update(byte[] input, int inputOffset, int inputLen) { byte[] update(byte[] input, int inputOffset, int inputLen) {
checkReinit();
byte[] output = null; byte[] output = null;
try { try {
@ -719,7 +588,6 @@ final class CipherCore {
*/ */
int update(byte[] input, int inputOffset, int inputLen, byte[] output, int update(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException { int outputOffset) throws ShortBufferException {
checkReinit();
// figure out how much can be sent to crypto function // figure out how much can be sent to crypto function
int len = Math.addExact(buffered, inputLen); int len = Math.addExact(buffered, inputLen);
@ -854,7 +722,6 @@ final class CipherCore {
byte[] doFinal(byte[] input, int inputOffset, int inputLen) byte[] doFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException { throws IllegalBlockSizeException, BadPaddingException {
try { try {
checkReinit();
byte[] output = new byte[getOutputSizeByOperation(inputLen, true)]; byte[] output = new byte[getOutputSizeByOperation(inputLen, true)];
byte[] finalBuf = prepareInputBuffer(input, inputOffset, byte[] finalBuf = prepareInputBuffer(input, inputOffset,
inputLen, output, 0); inputLen, output, 0);
@ -868,7 +735,7 @@ final class CipherCore {
if (outLen < output.length) { if (outLen < output.length) {
byte[] copy = Arrays.copyOf(output, outLen); byte[] copy = Arrays.copyOf(output, outLen);
if (decrypting) { if (decrypting) {
// Zero out internal (ouput) array // Zero out internal (output) array
Arrays.fill(output, (byte) 0x00); Arrays.fill(output, (byte) 0x00);
} }
return copy; return copy;
@ -921,7 +788,6 @@ final class CipherCore {
int outputOffset) int outputOffset)
throws IllegalBlockSizeException, ShortBufferException, throws IllegalBlockSizeException, ShortBufferException,
BadPaddingException { BadPaddingException {
checkReinit();
int estOutSize = getOutputSizeByOperation(inputLen, true); int estOutSize = getOutputSizeByOperation(inputLen, true);
int outputCapacity = checkOutputCapacity(output, outputOffset, int outputCapacity = checkOutputCapacity(output, outputOffset,
@ -943,15 +809,13 @@ final class CipherCore {
if (outputCapacity < estOutSize) { if (outputCapacity < estOutSize) {
cipher.save(); cipher.save();
} }
if (getMode() != GCM_MODE || outputCapacity < estOutSize) { // create temporary output buffer if the estimated size is larger
// create temporary output buffer if the estimated size is larger // than the user-provided buffer.
// than the user-provided buffer. internalOutput = new byte[estOutSize];
internalOutput = new byte[estOutSize]; offset = 0;
offset = 0;
}
} }
byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
byte[] outBuffer = (internalOutput != null) ? internalOutput : output;
int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer, int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer,
offset, finalBufLen, input); offset, finalBufLen, input);
@ -961,13 +825,13 @@ final class CipherCore {
// restore so users can retry with a larger buffer // restore so users can retry with a larger buffer
cipher.restore(); cipher.restore();
throw new ShortBufferException("Output buffer too short: " throw new ShortBufferException("Output buffer too short: "
+ (outputCapacity) + (outputCapacity) + " bytes given, " + outLen
+ " bytes given, " + outLen + " bytes needed");
+ " bytes needed");
} }
// copy the result into user-supplied output buffer // copy the result into user-supplied output buffer
if (internalOutput != null) { 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 // decrypt mode. Zero out output data that's not required
Arrays.fill(internalOutput, (byte) 0x00); Arrays.fill(internalOutput, (byte) 0x00);
} }
@ -1001,7 +865,7 @@ final class CipherCore {
// calculate total input length // calculate total input length
int len = Math.addExact(buffered, inputLen); int len = Math.addExact(buffered, inputLen);
// calculate padding length // calculate padding length
int totalLen = Math.addExact(len, cipher.getBufferedLength()); int totalLen = len;
int paddingLen = 0; int paddingLen = 0;
// will the total input length be a multiple of blockSize? // will the total input length be a multiple of blockSize?
if (unitBytes != blockSize) { if (unitBytes != blockSize) {
@ -1059,30 +923,27 @@ final class CipherCore {
} }
private int fillOutputBuffer(byte[] finalBuf, int finalOffset, private int fillOutputBuffer(byte[] finalBuf, int finalOffset,
byte[] output, int outOfs, int finalBufLen, byte[] output, int outOfs, int finalBufLen, byte[] input)
byte[] input) throws ShortBufferException, BadPaddingException,
throws ShortBufferException, BadPaddingException, IllegalBlockSizeException {
IllegalBlockSizeException {
int len; int len;
try { try {
len = finalNoPadding(finalBuf, finalOffset, output, len = finalNoPadding(finalBuf, finalOffset, output,
outOfs, finalBufLen); outOfs, finalBufLen);
if (decrypting && padding != null) { if (decrypting && padding != null) {
len = unpad(len, outOfs, output); len = unpad(len, outOfs, output);
} }
return len; return len;
} finally { } finally {
if (!decrypting) { if (!decrypting && finalBuf != input) {
// reset after doFinal() for GCM encryption // done with internal finalBuf array. Copied to output
requireReinit = (cipherMode == GCM_MODE); Arrays.fill(finalBuf, (byte) 0x00);
if (finalBuf != input) {
// done with internal finalBuf array. Copied to output
Arrays.fill(finalBuf, (byte) 0x00);
}
} }
} }
} }
private int checkOutputCapacity(byte[] output, int outputOffset, private int checkOutputCapacity(byte[] output, int outputOffset,
int estOutSize) throws ShortBufferException { int estOutSize) throws ShortBufferException {
// check output buffer capacity. // check output buffer capacity.
@ -1098,23 +959,14 @@ final class CipherCore {
return outputCapacity; 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, private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
int len) int len)
throws IllegalBlockSizeException, AEADBadTagException, throws IllegalBlockSizeException, ShortBufferException {
ShortBufferException {
if ((cipherMode != GCM_MODE) && (in == null || len == 0)) { if (in == null || len == 0) {
return 0; return 0;
} }
if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE) && if ((cipherMode != CFB_MODE) && (cipherMode != OFB_MODE) &&
(cipherMode != GCM_MODE) &&
((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) { ((len % unitBytes) != 0) && (cipherMode != CTS_MODE)) {
if (padding != null) { if (padding != null) {
throw new IllegalBlockSizeException throw new IllegalBlockSizeException
@ -1126,7 +978,7 @@ final class CipherCore {
+ " bytes"); + " bytes");
} }
} }
int outLen = 0; int outLen;
if (decrypting) { if (decrypting) {
outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs); outLen = cipher.decryptFinal(in, inOfs, len, out, outOfs);
} else { } else {
@ -1217,59 +1069,4 @@ final class CipherCore {
Arrays.fill(encodedKey, (byte)0); Arrays.fill(encodedKey, (byte)0);
} }
} }
/**
* Continues a multi-part update of the Additional Authentication
* Data (AAD), using a subset of the provided buffer.
* <p>
* 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;
}
} }

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,6 @@
package com.sun.crypto.provider; package com.sun.crypto.provider;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import javax.crypto.*; import javax.crypto.*;
@ -200,70 +199,7 @@ abstract class FeedbackCipher {
*/ */
int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen, int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
byte[] plain, int plainOffset) byte[] plain, int plainOffset)
throws IllegalBlockSizeException, AEADBadTagException, throws IllegalBlockSizeException, ShortBufferException {
ShortBufferException {
return decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset); 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).
* <p>
* 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");
}
} }

@ -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);
}

@ -31,8 +31,7 @@ package com.sun.crypto.provider;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import javax.crypto.IllegalBlockSizeException; import java.util.Arrays;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
/** /**
* This class represents the GCTR function defined in NIST 800-38D * 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 * @since 1.8
*/ */
final class GCTR extends CounterMode { final class GCTR extends CounterMode implements GCM {
// Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
private static final int MAX_LEN = 1024; private static final int MAX_LEN = 1024;
private byte[] block;
GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) { GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
super(cipher); super(cipher);
if (initialCounterBlk.length != AES_BLOCK_SIZE) { if (initialCounterBlk.length != blockSize) {
throw new RuntimeException("length of initial counter block (" + throw new RuntimeException("length of initial counter block (" +
initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" + initialCounterBlk.length + ") not equal to blockSize (" +
AES_BLOCK_SIZE + ")"); blockSize + ")");
} }
iv = initialCounterBlk; iv = initialCounterBlk;
@ -83,30 +83,47 @@ final class GCTR extends CounterMode {
return blocksLeft; return blocksLeft;
} }
// input must be multiples of 128-bit blocks when calling update private void checkBlock() {
int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { 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) { if (inLen - inOfs > in.length) {
throw new RuntimeException("input length out of bound"); 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"); 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"); throw new RuntimeException("output buffer too small");
} }
inLen -= inLen % blockSize;
long blocksLeft = blocksUntilRollover(); long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE; int numOfCompleteBlocks = inLen / blockSize;
if (numOfCompleteBlocks >= blocksLeft) { if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will // Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead. // roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; checkBlock();
for (int i = 0; i < numOfCompleteBlocks; i++) { for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); embeddedCipher.encryptBlock(counter, 0, block, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) { for (int n = 0; n < blockSize; n++) {
int index = (i * AES_BLOCK_SIZE + n); int index = (i * blockSize + n);
out[outOfs + index] = out[outOfs + index] =
(byte) ((in[inOfs + index] ^ encryptedCntr[n])); (byte) ((in[inOfs + index] ^ block[n]));
} }
GaloisCounterMode.increment32(counter); 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) { if (inLen - inOfs > in.length) {
throw new RuntimeException("input length out of bound"); 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"); throw new RuntimeException("input length unsupported");
} }
// See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for // See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for
// details on the check for 'dst' having enough space for the result. // details on the check for 'dst' having enough space for the result.
long blocksLeft = blocksUntilRollover(); long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE; int numOfCompleteBlocks = inLen / blockSize;
if (numOfCompleteBlocks >= blocksLeft) { if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will // Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead. // roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; checkBlock();
for (int i = 0; i < numOfCompleteBlocks; i++) { for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); embeddedCipher.encryptBlock(counter, 0, block, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) { for (int n = 0; n < blockSize; n++) {
int index = (i * AES_BLOCK_SIZE + n); int index = (i * blockSize + n);
dst.put((byte) ((in[inOfs + index] ^ encryptedCntr[n]))); dst.put((byte) ((in[inOfs + index] ^ block[n])));
} }
GaloisCounterMode.increment32(counter); GaloisCounterMode.increment32(counter);
} }
return inLen; return inLen;
} else { } else {
int len = inLen - inLen % AES_BLOCK_SIZE; int len = inLen - inLen % blockSize;
int processed = len; int processed = len;
byte[] out = new byte[Math.min(MAX_LEN, len)]; byte[] out = new byte[Math.min(MAX_LEN, len)];
int offset = inOfs; 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. * Operate on only blocksize data leaving the remainder in the src buffer.
int update(ByteBuffer src, ByteBuffer dst) { */
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(); long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE; int numOfCompleteBlocks = src.remaining() / blockSize;
if (numOfCompleteBlocks >= blocksLeft) { if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will // Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead. // roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; checkBlock();
for (int i = 0; i < numOfCompleteBlocks; i++) { for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); embeddedCipher.encryptBlock(counter, 0, block, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) { for (int n = 0; n < blockSize; n++) {
dst.put((byte) (src.get() ^ encryptedCntr[n])); dst.put((byte) (src.get() ^ block[n]));
} }
GaloisCounterMode.increment32(counter); 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; int processed = len;
byte[] in = new byte[Math.min(MAX_LEN, len)]; byte[] in = new byte[Math.min(MAX_LEN, len)];
while (processed > MAX_LEN) { while (processed > MAX_LEN) {
@ -196,50 +240,62 @@ final class GCTR extends CounterMode {
return len; return len;
} }
// input can be arbitrary size when calling doFinal /**
int doFinal(byte[] in, int inOfs, int inLen, byte[] out, * doFinal operation by using update() for any full block operations needed,
int outOfs) throws IllegalBlockSizeException { * then operating on the final bytes in the input buffer.
try { *
if (inLen < 0) { * This method will not write any block padding to the output buffer
throw new IllegalBlockSizeException("Negative input size!"); */
} else if (inLen > 0) { public int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
int lastBlockSize = inLen % AES_BLOCK_SIZE; int outOfs) {
int completeBlkLen = inLen - lastBlockSize; if (inLen == 0) {
// process the complete blocks first return 0;
update(in, inOfs, completeBlkLen, out, outOfs); }
if (lastBlockSize != 0) { int lastBlockSize = inLen % blockSize;
// do the last partial block int completeBlkLen = inLen - lastBlockSize;
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; // process the complete blocks first
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); update(in, inOfs, completeBlkLen, out, outOfs);
for (int n = 0; n < lastBlockSize; n++) { if (lastBlockSize != 0) {
out[outOfs + completeBlkLen + n] = // do the last partial block
(byte) ((in[inOfs + completeBlkLen + n] ^ checkBlock();
encryptedCntr[n])); 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; 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 len = src.remaining();
int lastBlockSize = len % AES_BLOCK_SIZE; int lastBlockSize = len % blockSize;
try { update(src, dst);
update(src, dst); if (lastBlockSize != 0) {
if (lastBlockSize != 0) { checkBlock();
// do the last partial block // do the last partial block
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; embeddedCipher.encryptBlock(counter, 0, block, 0);
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); for (int n = 0; n < lastBlockSize; n++) {
for (int n = 0; n < lastBlockSize; n++) { dst.put((byte) (src.get() ^ block[n]));
dst.put((byte) (src.get() ^ encryptedCntr[n]));
}
} }
} finally {
reset();
} }
return len; return len;
} }

@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015 Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,11 +24,15 @@
*/ */
/* /*
* (C) Copyright IBM Corp. 2013 * (C) Copyright IBM Corp. 2013
* Copyright (c) 2015 Red Hat, Inc.
*/ */
package com.sun.crypto.provider; package com.sun.crypto.provider;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.ProviderException; import java.security.ProviderException;
import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.IntrinsicCandidate;
@ -44,27 +47,19 @@ import jdk.internal.vm.annotation.IntrinsicCandidate;
* *
* @since 1.8 * @since 1.8
*/ */
final class GHASH {
private static long getLong(byte[] buffer, int offset) { final class GHASH implements Cloneable, GCM {
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;
}
}
private static final int AES_BLOCK_SIZE = 16; 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]. // Multiplies state[0], state[1] by subkeyH[0], subkeyH[1].
private static void blockMult(long[] st, long[] subH) { private static void blockMult(long[] st, long[] subH) {
long Z0 = 0; long Z0 = 0;
@ -127,15 +122,13 @@ final class GHASH {
/* subkeyHtbl and state are stored in long[] for GHASH intrinsic use */ /* 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; private long[] subkeyHtbl;
// buffer for storing hash // buffer for storing hash
private final long[] state; private final long[] state;
// variables for save/restore calls
private long stateSave0, stateSave1;
/** /**
* Initializes the cipher in the specified mode with the given key * Initializes the cipher in the specified mode with the given key
* and iv. * and iv.
@ -151,87 +144,106 @@ final class GHASH {
} }
state = new long[2]; state = new long[2];
subkeyHtbl = new long[2*9]; subkeyHtbl = new long[2*9];
subkeyHtbl[0] = getLong(subkeyH, 0); subkeyHtbl[0] = (long)asLongView.get(subkeyH, 0);
subkeyHtbl[1] = getLong(subkeyH, 8); subkeyHtbl[1] = (long)asLongView.get(subkeyH, 8);
} }
/** // Cloning constructor
* Resets the GHASH object to its original state, i.e. blank w/ private GHASH(GHASH g) {
* the same subkey H. Used after digest() is called and to re-use state = g.state.clone();
* this object for different data w/ the same H. subkeyHtbl = g.subkeyHtbl.clone();
*/
void reset() {
state[0] = 0;
state[1] = 0;
} }
/** @Override
* Save the current snapshot of this GHASH object. public GHASH clone() {
*/ return new GHASH(this);
void save() {
stateSave0 = state[0];
stateSave1 = state[1];
} }
/** private static void processBlock(byte[] data, int ofs, long[] st,
* Restores this object using the saved snapshot. long[] subH) {
*/ st[0] ^= (long)asLongView.get(data, ofs);
void restore() { st[1] ^= (long)asLongView.get(data, ofs + 8);
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);
blockMult(st, subH); blockMult(st, subH);
} }
void update(byte[] in) { int update(byte[] in) {
update(in, 0, in.length); 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) { if (inLen == 0) {
return; return 0;
} }
ghashRangeCheck(in, inOfs, inLen, state, subkeyHtbl); int len = inLen - (inLen % AES_BLOCK_SIZE);
processBlocks(in, inOfs, inLen/AES_BLOCK_SIZE, state, subkeyHtbl); 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. // 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); inLen -= (inLen % AES_BLOCK_SIZE);
if (inLen == 0) { if (inLen == 0) {
return 0; return 0;
} }
int processed = inLen; // If ct is a direct bytebuffer, send it directly to the intrinsic
byte[] in = new byte[Math.min(MAX_LEN, inLen)]; if (ct.isDirect()) {
while (processed > MAX_LEN ) { int processed = inLen;
src.get(in, 0, MAX_LEN); processBlocksDirect(ct, inLen);
update(in, 0 , MAX_LEN); return processed;
processed -= MAX_LEN; } 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; return inLen;
} }
void doLastBlock(ByteBuffer src, int inLen) { int doFinal(ByteBuffer src, int inLen) {
int processed = update(src, inLen); int processed = 0;
if (inLen >= AES_BLOCK_SIZE) {
processed = update(src, inLen);
}
if (inLen == processed) { if (inLen == processed) {
return; return processed;
} }
byte[] block = new byte[AES_BLOCK_SIZE]; byte[] block = new byte[AES_BLOCK_SIZE];
src.get(block, 0, inLen - processed); src.get(block, 0, inLen - processed);
update(block, 0, AES_BLOCK_SIZE); 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) { if (inLen < 0) {
throw new RuntimeException("invalid input length: " + inLen); throw new RuntimeException("invalid input length: " + inLen);
} }
@ -263,7 +275,8 @@ final class GHASH {
* throw exceptions or allocate arrays as it will breaking intrinsics * throw exceptions or allocate arrays as it will breaking intrinsics
*/ */
@IntrinsicCandidate @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; int offset = inOfs;
while (blocks > 0) { while (blocks > 0) {
processBlock(data, offset, st, subH); 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[] digest() {
byte[] result = new byte[AES_BLOCK_SIZE]; byte[] result = new byte[AES_BLOCK_SIZE];
putLong(result, 0, state[0]); asLongView.set(result, 0, state[0]);
putLong(result, 8, state[1]); asLongView.set(result, 8, state[1]);
reset(); // Reset state
state[0] = 0;
state[1] = 0;
return result; 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());
}
} }

@ -165,7 +165,7 @@ public final class SunJCE extends Provider {
"|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64" + "|CFB8|CFB16|CFB24|CFB32|CFB40|CFB48|CFB56|CFB64" +
"|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64"; "|OFB8|OFB16|OFB24|OFB32|OFB40|OFB48|OFB56|OFB64";
final String BLOCK_MODES128 = BLOCK_MODES + 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"; "|OFB72|OFB80|OFB88|OFB96|OFB104|OFB112|OFB120|OFB128";
final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING"; final String BLOCK_PADS = "NOPADDING|PKCS5PADDING|ISO10126PADDING";
@ -214,9 +214,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_128/CFB/NoPadding", psA("Cipher", "AES_128/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding", "com.sun.crypto.provider.AESCipher$AES128_CFB_NoPadding",
attrs); attrs);
psA("Cipher", "AES_128/GCM/NoPadding",
"com.sun.crypto.provider.AESCipher$AES128_GCM_NoPadding",
attrs);
psA("Cipher", "AES_128/KW/NoPadding", psA("Cipher", "AES_128/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES128_KW_NoPadding", "com.sun.crypto.provider.KeyWrapCipher$AES128_KW_NoPadding",
attrs); attrs);
@ -239,9 +236,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_192/CFB/NoPadding", psA("Cipher", "AES_192/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding", "com.sun.crypto.provider.AESCipher$AES192_CFB_NoPadding",
attrs); attrs);
psA("Cipher", "AES_192/GCM/NoPadding",
"com.sun.crypto.provider.AESCipher$AES192_GCM_NoPadding",
attrs);
psA("Cipher", "AES_192/KW/NoPadding", psA("Cipher", "AES_192/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES192_KW_NoPadding", "com.sun.crypto.provider.KeyWrapCipher$AES192_KW_NoPadding",
attrs); attrs);
@ -264,9 +258,6 @@ public final class SunJCE extends Provider {
psA("Cipher", "AES_256/CFB/NoPadding", psA("Cipher", "AES_256/CFB/NoPadding",
"com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding", "com.sun.crypto.provider.AESCipher$AES256_CFB_NoPadding",
attrs); attrs);
psA("Cipher", "AES_256/GCM/NoPadding",
"com.sun.crypto.provider.AESCipher$AES256_GCM_NoPadding",
attrs);
psA("Cipher", "AES_256/KW/NoPadding", psA("Cipher", "AES_256/KW/NoPadding",
"com.sun.crypto.provider.KeyWrapCipher$AES256_KW_NoPadding", "com.sun.crypto.provider.KeyWrapCipher$AES256_KW_NoPadding",
attrs); attrs);
@ -277,6 +268,23 @@ public final class SunJCE extends Provider {
"com.sun.crypto.provider.KeyWrapCipher$AES256_KWP_NoPadding", "com.sun.crypto.provider.KeyWrapCipher$AES256_KWP_NoPadding",
attrs); 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.clear();
attrs.put("SupportedModes", "CBC"); attrs.put("SupportedModes", "CBC");
attrs.put("SupportedPaddings", "NOPADDING"); attrs.put("SupportedPaddings", "NOPADDING");

@ -217,8 +217,6 @@ public final class SecurityProviderConstants {
store("DiffieHellman", KnownOIDs.DiffieHellman); store("DiffieHellman", KnownOIDs.DiffieHellman);
store("AES", KnownOIDs.AES, "Rijndael");
store("EC", KnownOIDs.EC, "EllipticCurve"); store("EC", KnownOIDs.EC, "EllipticCurve");
store("X.509", null, "X509"); store("X.509", null, "X509");

@ -27,6 +27,7 @@ import java.security.Provider;
import java.security.Security; import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HexFormat;
import java.util.List; import java.util.List;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -84,7 +85,7 @@ import javax.crypto.KeyGenerator;
*/ */
public class Encrypt { 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 KEY_STRENGTHS[] = { 128, 192, 256 };
private static final int TEXT_LENGTHS[] = { 0, 256, 1024 }; private static final int TEXT_LENGTHS[] = { 0, 256, 1024 };
private static final int AAD_LENGTHS[] = { 0, 8, 128, 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); combination_16(outputTexts, mode, AAD, inputText, params);
for (int k = 0; k < outputTexts.size(); k++) { for (int k = 0; k < outputTexts.size(); k++) {
HexFormat hex = HexFormat.of().withUpperCase();
if (!Arrays.equals(output, outputTexts.get(k))) { 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; return output;

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HexFormat;
import java.util.List; import java.util.List;
public class GCMBufferTest implements Cloneable { public class GCMBufferTest implements Cloneable {
@ -65,6 +65,7 @@ public class GCMBufferTest implements Cloneable {
boolean theoreticalCheck; boolean theoreticalCheck;
List<Data> dataSet; List<Data> dataSet;
int inOfs = 0, outOfs = 0; int inOfs = 0, outOfs = 0;
static HexFormat hex = HexFormat.of();
static class Data { static class Data {
int id; int id;
@ -108,14 +109,21 @@ public class GCMBufferTest implements Cloneable {
new GCMParameterSpec(tag.length * 8, this.iv)); new GCMParameterSpec(tag.length * 8, this.iv));
tct = c.doFinal(pt); tct = c.doFinal(pt);
} catch (Exception e) { } catch (Exception e) {
System.out.println("Error in generating data for length " + throw new RuntimeException("Error in generating data for length " +
ptlen); ptlen, e);
} }
ct = new byte[ptlen]; ct = new byte[ptlen];
System.arraycopy(tct, 0, ct, 0, ct.length); System.arraycopy(tct, 0, ct, 0, ct.length);
System.arraycopy(tct, ct.length, tag, 0, tag.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; 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 { void test() throws Exception {
int i = 1; int i = 1;
System.out.println("Algo: " + algo + " \tOps: " + ops.toString()); System.err.println("Algo: " + algo + " \tOps: " + ops.toString());
for (Data data : dataSet) { for (Data data : dataSet) {
// If incrementalSegments is enabled, run through that test only // If incrementalSegments is enabled, run through that test only
@ -256,31 +264,31 @@ public class GCMBufferTest implements Cloneable {
sizes = new int[ops.size()]; sizes = new int[ops.size()];
while (incrementSizes(data.pt.length)) { 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) { for (int v : sizes) {
System.out.print(v + " "); System.err.print(v + " ");
} }
System.out.println("]"); System.err.println("]");
encrypt(data); encrypt(data);
} }
Arrays.fill(sizes, 0); Arrays.fill(sizes, 0);
while (incrementSizes(data.ct.length + data.tag.length)) { 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) { for (int v : sizes) {
System.out.print(v + " "); System.err.print(v + " ");
} }
System.out.println("]"); System.err.println("]");
decrypt(data); decrypt(data);
} }
} else { } else {
// Default test of 0 and 2 offset doing in place and different // Default test of 0 and 2 offset doing in place and different
// i/o buffers // i/o buffers
System.out.println("Encrypt: Data Index: " + i); System.err.println("Encrypt: Data Index: " + i);
encrypt(data); encrypt(data);
System.out.println("Decrypt: Data Index: " + i); System.err.println("Decrypt: Data Index: " + i);
decrypt(data); decrypt(data);
} }
i++; i++;
@ -298,13 +306,13 @@ public class GCMBufferTest implements Cloneable {
data.tag.length); data.tag.length);
// Test different input/output buffers // 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"); inOfs + " outOfs " + outOfs + " in/out buffer: different");
crypto(true, data, input, output); crypto(true, data, input, output);
// Test with in-place buffers // Test with in-place buffers
if (same) { 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"); inOfs + " outOfs " + outOfs + " in/out buffer: in-place");
cryptoSameBuffer(true, data, input, output); cryptoSameBuffer(true, data, input, output);
} }
@ -320,13 +328,13 @@ public class GCMBufferTest implements Cloneable {
output = data.pt; output = data.pt;
// Test different input/output buffers // 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-place: different"); inOfs + " outOfs " + outOfs + " in/out buffer: different");
crypto(false, data, input, output); crypto(false, data, input, output);
// Test with in-place buffers // Test with in-place buffers
if (same) { if (same) {
System.out.println("\tinput len: " + input.length + " inOfs " + System.err.println("\tinput len: " + input.length + " inOfs " +
inOfs + " outOfs " + outOfs + " in-place: same"); inOfs + " outOfs " + outOfs + " in-place: same");
cryptoSameBuffer(false, data, input, output); cryptoSameBuffer(false, data, input, output);
} }
@ -484,12 +492,10 @@ public class GCMBufferTest implements Cloneable {
if (ctresult.length != expectedOut.length || if (ctresult.length != expectedOut.length ||
Arrays.compare(ctresult, expectedOut) != 0) { Arrays.compare(ctresult, expectedOut) != 0) {
String s = "Ciphertext mismatch (" + v.name() + String s = "Ciphertext mismatch (" + v.name() +
"):\nresult (len=" + ctresult.length + "):" + "):\nresult (len=" + ctresult.length + "): " +
String.format("%0" + (ctresult.length << 1) + "x", hex.formatHex(ctresult) +
new BigInteger(1, ctresult)) + "\nexpected (len=" + output.length + "): " +
"\nexpected (len=" + output.length + "):" + hex.formatHex(output);
String.format("%0" + (output.length << 1) + "x",
new BigInteger(1, output));
System.err.println(s); System.err.println(s);
throw new Exception(s); throw new Exception(s);
@ -605,10 +611,9 @@ public class GCMBufferTest implements Cloneable {
output.length) != 0) { output.length) != 0) {
String s = "Ciphertext mismatch (" + v.name() + String s = "Ciphertext mismatch (" + v.name() +
"):\nresult (len=" + len + "):\n" + "):\nresult (len=" + len + "):\n" +
byteToHex(out) + hex.formatHex(out) +
"\nexpected (len=" + output.length + "):\n" + "\nexpected (len=" + output.length + "):\n" +
String.format("%0" + (output.length << 1) + "x", hex.formatHex(output);
new BigInteger(1, output));
System.err.println(s); System.err.println(s);
throw new Exception(s); throw new Exception(s);
} }
@ -623,7 +628,10 @@ public class GCMBufferTest implements Cloneable {
} }
public static void main(String args[]) throws Exception { public static void main(String args[]) throws Exception {
GCMBufferTest t;
initTest(); initTest();
// Test single byte array // Test single byte array
new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)).test(); new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)).test();
offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE))); 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))); List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)));
// Test update-update-doFinal with byte arrays and preset data sizes // 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( List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).dataSegments(
new int[] { 1, 1, GCMBufferTest.REMAINDER}); new int[] { 1, 1, GCMBufferTest.REMAINDER});
t.clone().test(); t.clone().test();
@ -678,6 +686,7 @@ public class GCMBufferTest implements Cloneable {
List.of(dtype.BYTE, dtype.HEAP, dtype.DIRECT)).differentBufferOnly(); List.of(dtype.BYTE, dtype.HEAP, dtype.DIRECT)).differentBufferOnly();
t.clone().test(); t.clone().test();
offsetTests(t.clone()); offsetTests(t.clone());
// Test update-doFinal with a direct bytebuffer and a byte array. // Test update-doFinal with a direct bytebuffer and a byte array.
t = new GCMBufferTest("AES/GCM/NoPadding", t = new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly(); List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly();
@ -710,26 +719,10 @@ public class GCMBufferTest implements Cloneable {
new GCMBufferTest("AES/GCM/NoPadding", new GCMBufferTest("AES/GCM/NoPadding",
List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)). List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
incrementalSegments().dataSet(0).test(); incrementalSegments().dataSet(0).test();
}
private static byte[] HexToBytes(String hexVal) { new GCMBufferTest("AES/GCM/NoPadding",
if (hexVal == null) { List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).
return new byte[0]; dataSegments(new int[] { 49, 0, 2 }).dataSet(0).test();
}
byte[] result = new byte[hexVal.length()/2];
for (int i = 0; i < result.length; i++) {
String byteVal = hexVal.substring(2*i, 2*i +2);
result[i] = Integer.valueOf(byteVal, 16).byteValue();
}
return result;
}
private static String byteToHex(byte[] barray) {
StringBuilder s = new StringBuilder();
for (byte b : barray) {
s.append(String.format("%02x", b));
}
return s.toString();
} }
// Test data // Test data
@ -762,8 +755,7 @@ public class GCMBufferTest implements Cloneable {
"b6e6f197168f5049aeda32dafbdaeb"), "b6e6f197168f5049aeda32dafbdaeb"),
// zero'd test data // zero'd test data
new Data("AES", 3, "272f16edb81a7abbea887357a58c1917", new Data("AES", 3, "272f16edb81a7abbea887357a58c1917",
"794ec588176c703d3d2a7a07", "794ec588176c703d3d2a7a07", new byte[256], null,
new byte[256], null,
"15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" + "15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" +
"e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" + "e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" +
"07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" + "07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" +

@ -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));
}
}

@ -107,6 +107,9 @@ public class OverlapByteBuffer {
} }
} }
System.out.println("inOfsInBuf = " + inOfsInBuf);
System.out.println("outOfsInBuf = " + outOfsInBuf);
// Copy data into shared buffer // Copy data into shared buffer
input.put(baseBuf); input.put(baseBuf);
input.flip(); input.flip();
@ -132,8 +135,6 @@ public class OverlapByteBuffer {
cipher.doFinal(in, output); cipher.doFinal(in, output);
output.flip(); output.flip();
System.out.println("inOfsInBuf = " + inOfsInBuf);
System.out.println("outOfsInBuf = " + outOfsInBuf);
ByteBuffer b = ByteBuffer.wrap(baseBuf); ByteBuffer b = ByteBuffer.wrap(baseBuf);
if (b.compareTo(output) != 0) { if (b.compareTo(output) != 0) {
System.err.println( System.err.println(

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.util.Random; import java.util.HexFormat;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/** /**
* @test * @test
@ -43,7 +44,6 @@ import javax.crypto.spec.IvParameterSpec;
* doesn't use IV). * doesn't use IV).
* @author Liwen Wang * @author Liwen Wang
* @author Parag Salvi * @author Parag Salvi
* @key randomness
*/ */
public class TestAESCipher { public class TestAESCipher {
@ -56,7 +56,9 @@ public class TestAESCipher {
"OFB32", "OFB40", "OFB48", "OFB56", "OFB64", "OFB72", "OFB80", "OFB32", "OFB40", "OFB48", "OFB56", "OFB64", "OFB72", "OFB80",
"OFB88", "OFB96", "OFB104", "OFB112", "OFB120", "GCM" }; "OFB88", "OFB96", "OFB104", "OFB112", "OFB120", "GCM" };
private static final String[] PADDING = { "NoPadding", "PKCS5Padding" }; 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 { public static void main(String argv[]) throws Exception {
TestAESCipher test = new TestAESCipher(); TestAESCipher test = new TestAESCipher();
@ -73,32 +75,31 @@ public class TestAESCipher {
} }
} }
public void runTest(String algo, String mo, String pad) throws Exception { public void runTest(String algo, String mo, String pad) throws Exception {
Cipher ci = null; Cipher ci;
byte[] iv = null; System.out.println("Testing " + algo + "/" + mo + "/" + pad);
AlgorithmParameterSpec aps = null;
SecretKey key = null; byte[] iv = new byte[16];
AlgorithmParameterSpec aps = new GCMParameterSpec(128, iv);
SecretKey key = new SecretKeySpec(this.key, 0, KEY_LENGTH,"AES");
try { try {
// Initialization // Initialization
Random rdm = new Random();
byte[] plainText = new byte[128];
rdm.nextBytes(plainText);
ci = Cipher.getInstance(algo + "/" + mo + "/" + pad, PROVIDER); ci = Cipher.getInstance(algo + "/" + mo + "/" + pad, PROVIDER);
KeyGenerator kg = KeyGenerator.getInstance(algo, PROVIDER);
kg.init(KEY_LENGTH);
key = kg.generateKey();
// encrypt // encrypt
if (!mo.equalsIgnoreCase("GCM")) { if (mo.equalsIgnoreCase("GCM")) {
ci.init(Cipher.ENCRYPT_MODE, key, aps); ci.init(Cipher.ENCRYPT_MODE, key, aps);
} else if (mo.equalsIgnoreCase("ECB")) {
ci.init(Cipher.ENCRYPT_MODE, key, (AlgorithmParameterSpec)null);
} else { } else {
ci.init(Cipher.ENCRYPT_MODE, key); ci.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
} }
byte[] cipherText = new byte[ci.getOutputSize(plainText.length)]; byte[] cipherText = new byte[ci.getOutputSize(plainText.length)];
int offset = ci.update(plainText, 0, plainText.length, cipherText, int offset = ci.update(plainText, 0, plainText.length, cipherText,
0); 0);
ci.doFinal(cipherText, offset); ci.doFinal(cipherText, offset);
if (!mo.equalsIgnoreCase("ECB")) { if (!mo.equalsIgnoreCase("ECB")) {
@ -117,25 +118,24 @@ public class TestAESCipher {
byte[] recoveredText = new byte[ci.getOutputSize(cipherText.length)]; byte[] recoveredText = new byte[ci.getOutputSize(cipherText.length)];
int len = ci.doFinal(cipherText, 0, cipherText.length, int len = ci.doFinal(cipherText, 0, cipherText.length,
recoveredText); recoveredText);
byte[] tmp = new byte[len];
System.arraycopy(recoveredText, 0, tmp, 0, len);
// Comparison // Comparison
if (!java.util.Arrays.equals(plainText, tmp)) { if (!java.util.Arrays.equals(plainText, 0 , plainText.length,
recoveredText, 0, len)) {
System.out.println("Original: "); System.out.println("Original: ");
dumpBytes(plainText); System.out.println(HexFormat.of().formatHex(plainText));
System.out.println("Recovered: "); System.out.println("Recovered: ");
dumpBytes(tmp); System.out.println(HexFormat.of().
throw new RuntimeException( formatHex(recoveredText, 0, len));
"Original text is not equal with recovered text, with mode:" throw new RuntimeException("Original text is not equal with " +
+ mo); "recovered text, with mode:" + mo);
} }
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
//CFB7 and OFB150 are for negative testing //CFB7 and OFB150 are for negative testing
if (!mo.equalsIgnoreCase("CFB7") && !mo.equalsIgnoreCase("OFB150")) { if (!mo.equalsIgnoreCase("CFB7") && !mo.equalsIgnoreCase("OFB150")) {
System.out.println("Unexpected NoSuchAlgorithmException with mode: " System.out.println("Unexpected NoSuchAlgorithmException with" +
+ mo); " mode: " + mo);
throw new RuntimeException("Test failed!"); throw new RuntimeException("Test failed!");
} }
} catch ( NoSuchProviderException | NoSuchPaddingException } catch ( NoSuchProviderException | NoSuchPaddingException
@ -146,12 +146,4 @@ public class TestAESCipher {
throw e; throw e;
} }
} }
private void dumpBytes(byte[] bytes) {
for (byte b : bytes) {
System.out.print(Integer.toHexString(b));
}
System.out.println();
}
} }

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 { 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 PROVIDER = "SunJCE";
private static final String[] MODES = { "ECb", "CbC", "OFB", "CFB150", private static final String[] MODES = { "ECb", "CbC", "OFB", "CFB150",
"cFB", "CFB7", " cFB8", "cFB16", "cFB24", "cFB32", "Cfb40", "cFB", "CFB7", " cFB8", "cFB16", "cFB24", "cFB32", "Cfb40",