8179098: Crypto AES/ECB encryption/decryption performance regression (introduced in jdk9b73)
Summary: Do bounds check per encryption/decryption call instead of per block
Reviewed-by: ascarpino, redestad
--- a/src/java.base/share/classes/com/sun/crypto/provider/AESCrypt.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/AESCrypt.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2018, 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
@@ -39,7 +39,6 @@
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.Arrays;
-import java.util.Objects;
import jdk.internal.HotSpotIntrinsicCandidate;
@@ -351,8 +350,8 @@
*/
void encryptBlock(byte[] in, int inOffset,
byte[] out, int outOffset) {
- Objects.checkFromIndexSize(inOffset, AES_BLOCK_SIZE, in.length);
- Objects.checkFromIndexSize(outOffset, AES_BLOCK_SIZE, out.length);
+ // Array bound checks are done in caller code, i.e.
+ // FeedbackCipher.encrypt/decrypt(...) to improve performance.
implEncryptBlock(in, inOffset, out, outOffset);
}
@@ -430,8 +429,8 @@
*/
void decryptBlock(byte[] in, int inOffset,
byte[] out, int outOffset) {
- Objects.checkFromIndexSize(inOffset, AES_BLOCK_SIZE, in.length);
- Objects.checkFromIndexSize(outOffset, AES_BLOCK_SIZE, out.length);
+ // Array bound checks are done in caller code, i.e.
+ // FeedbackCipher.encrypt/decrypt(...) to improve performance.
implDecryptBlock(in, inOffset, out, outOffset);
}
--- a/src/java.base/share/classes/com/sun/crypto/provider/CipherBlockChaining.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/CipherBlockChaining.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -30,6 +30,7 @@
import java.util.Objects;
import jdk.internal.HotSpotIntrinsicCandidate;
+import sun.security.util.ArrayUtil;
/**
@@ -145,9 +146,9 @@
if (plainLen <= 0) {
return plainLen;
}
- cryptBlockSizeCheck(plainLen);
- cryptNullAndBoundsCheck(plain, plainOffset, plainLen);
- cryptNullAndBoundsCheck(cipher, cipherOffset, plainLen);
+ ArrayUtil.blockSizeCheck(plainLen, blockSize);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
return implEncrypt(plain, plainOffset, plainLen,
cipher, cipherOffset);
}
@@ -196,9 +197,9 @@
if (cipherLen <= 0) {
return cipherLen;
}
- cryptBlockSizeCheck(cipherLen);
- cryptNullAndBoundsCheck(cipher, cipherOffset, cipherLen);
- cryptNullAndBoundsCheck(plain, plainOffset, cipherLen);
+ ArrayUtil.blockSizeCheck(cipherLen, blockSize);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
return implDecrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
}
@@ -218,23 +219,4 @@
}
return cipherLen;
}
-
- private void cryptBlockSizeCheck(int len) {
- if ((len % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
- }
-
- private static void cryptNullAndBoundsCheck(byte[] array, int offset, int len) {
- Objects.requireNonNull(array);
-
- if (offset < 0 || offset >= array.length) {
- throw new ArrayIndexOutOfBoundsException(offset);
- }
-
- int endIndex = offset + len - 1;
- if (endIndex < 0 || endIndex >= array.length) {
- throw new ArrayIndexOutOfBoundsException(endIndex);
- }
- }
}
--- a/src/java.base/share/classes/com/sun/crypto/provider/CipherFeedback.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/CipherFeedback.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -27,6 +27,7 @@
import java.security.InvalidKeyException;
import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
/**
* This class represents ciphers in cipher-feedback (CFB) mode.
@@ -149,9 +150,9 @@
*/
int encrypt(byte[] plain, int plainOffset, int plainLen,
byte[] cipher, int cipherOffset) {
- if ((plainLen % numBytes) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(plainLen, numBytes);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
int nShift = blockSize - numBytes;
int loopCount = plainLen / numBytes;
@@ -225,9 +226,10 @@
*/
int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
byte[] plain, int plainOffset) {
- if ((cipherLen % numBytes) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+
+ ArrayUtil.blockSizeCheck(cipherLen, numBytes);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
int nShift = blockSize - numBytes;
int loopCount = cipherLen / numBytes;
--- a/src/java.base/share/classes/com/sun/crypto/provider/CounterMode.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/CounterMode.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2018, 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,9 +26,9 @@
package com.sun.crypto.provider;
import java.security.InvalidKeyException;
-import java.util.Objects;
import jdk.internal.HotSpotIntrinsicCandidate;
+import sun.security.util.ArrayUtil;
/**
* This class represents ciphers in counter (CTR) mode.
@@ -175,8 +175,9 @@
if (len == 0) {
return 0;
}
- Objects.checkFromIndexSize(inOff, len, in.length);
- Objects.checkFromIndexSize(outOff, len, out.length);
+
+ ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+ ArrayUtil.nullAndBoundsCheck(out, outOff, len);
return implCrypt(in, inOff, len, out, outOff);
}
--- a/src/java.base/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -27,6 +27,7 @@
import java.security.InvalidKeyException;
import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
/**
* This class represents ciphers in electronic codebook (ECB) mode.
@@ -112,9 +113,10 @@
* @return the length of the encrypted data
*/
int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- if ((len % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(len, blockSize);
+ ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+ ArrayUtil.nullAndBoundsCheck(out, outOff, len);
+
for (int i = len; i >= blockSize; i -= blockSize) {
embeddedCipher.encryptBlock(in, inOff, out, outOff);
inOff += blockSize;
@@ -141,9 +143,10 @@
* @return the length of the decrypted data
*/
int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
- if ((len % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(len, blockSize);
+ ArrayUtil.nullAndBoundsCheck(in, inOff, len);
+ ArrayUtil.nullAndBoundsCheck(out, outOff, len);
+
for (int i = len; i >= blockSize; i -= blockSize) {
embeddedCipher.decryptBlock(in, inOff, out, outOff);
inOff += blockSize;
--- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018, 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
@@ -30,6 +30,8 @@
import java.security.*;
import javax.crypto.*;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
+import sun.security.util.ArrayUtil;
+
/**
* This class represents ciphers in GaloisCounter (GCM) mode.
@@ -406,8 +408,8 @@
/**
* Performs encryption operation.
*
- * <p>The input plain text <code>in</code>, starting at <code>inOff</code>
- * and ending at <code>(inOff + len - 1)</code>, is encrypted. The result
+ * <p>The input plain text <code>in</code>, starting at <code>inOfs</code>
+ * and ending at <code>(inOfs + len - 1)</code>, is encrypted. The result
* is stored in <code>out</code>, starting at <code>outOfs</code>.
*
* @param in the buffer with the input data to be encrypted
@@ -420,18 +422,21 @@
* @return the number of bytes placed into the <code>out</code> buffer
*/
int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
- if ((len % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(len, blockSize);
checkDataLength(processed, len);
processAAD();
+
if (len > 0) {
+ ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
+ ArrayUtil.nullAndBoundsCheck(out, outOfs, len);
+
gctrPAndC.update(in, inOfs, len, out, outOfs);
processed += len;
ghashAllToS.update(out, outOfs, len);
}
+
return len;
}
@@ -451,7 +456,10 @@
throw new ShortBufferException
("Can't fit both data and tag into one buffer");
}
- if (out.length - outOfs < (len + tagLenBytes)) {
+ try {
+ ArrayUtil.nullAndBoundsCheck(out, outOfs,
+ (len + tagLenBytes));
+ } catch (ArrayIndexOutOfBoundsException aiobe) {
throw new ShortBufferException("Output buffer too small");
}
@@ -459,6 +467,8 @@
processAAD();
if (len > 0) {
+ ArrayUtil.nullAndBoundsCheck(in, inOfs, len);
+
doLastBlock(in, inOfs, len, out, outOfs, true);
}
@@ -492,9 +502,7 @@
* @return the number of bytes placed into the <code>out</code> buffer
*/
int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
- if ((len % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(len, blockSize);
checkDataLength(ibuffer.size(), len);
@@ -504,6 +512,7 @@
// 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;
@@ -532,22 +541,28 @@
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(ibuffer.size(), (len - tagLenBytes));
- if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
+ try {
+ ArrayUtil.nullAndBoundsCheck(out, outOfs,
+ (ibuffer.size() + 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 (len != 0) {
+ if (len > 0) {
ibuffer.write(in, inOfs, len);
}
--- a/src/java.base/share/classes/com/sun/crypto/provider/OutputFeedback.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/OutputFeedback.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -27,6 +27,7 @@
import java.security.InvalidKeyException;
import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
/**
* This class represents ciphers in output-feedback (OFB) mode.
@@ -148,10 +149,10 @@
*/
int encrypt(byte[] plain, int plainOffset, int plainLen,
byte[] cipher, int cipherOffset) {
+ ArrayUtil.blockSizeCheck(plainLen, numBytes);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
- if ((plainLen % numBytes) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
int nShift = blockSize - numBytes;
int loopCount = plainLen / numBytes;
@@ -189,6 +190,9 @@
*/
int encryptFinal(byte[] plain, int plainOffset, int plainLen,
byte[] cipher, int cipherOffset) {
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
+
int oddBytes = plainLen % numBytes;
int len = encrypt(plain, plainOffset, (plainLen - oddBytes),
cipher, cipherOffset);
--- a/src/java.base/share/classes/com/sun/crypto/provider/PCBC.java Thu Jul 12 08:44:39 2018 +0800
+++ b/src/java.base/share/classes/com/sun/crypto/provider/PCBC.java Fri Jul 13 02:36:42 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, 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
@@ -27,6 +27,7 @@
import java.security.InvalidKeyException;
import java.security.ProviderException;
+import sun.security.util.ArrayUtil;
/**
@@ -136,9 +137,10 @@
int encrypt(byte[] plain, int plainOffset, int plainLen,
byte[] cipher, int cipherOffset)
{
- if ((plainLen % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(plainLen, blockSize);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, plainLen);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, plainLen);
+
int i;
int endIndex = plainOffset + plainLen;
@@ -176,9 +178,10 @@
int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
byte[] plain, int plainOffset)
{
- if ((cipherLen % blockSize) != 0) {
- throw new ProviderException("Internal error in input buffering");
- }
+ ArrayUtil.blockSizeCheck(cipherLen, blockSize);
+ ArrayUtil.nullAndBoundsCheck(cipher, cipherOffset, cipherLen);
+ ArrayUtil.nullAndBoundsCheck(plain, plainOffset, cipherLen);
+
int i;
int endIndex = cipherOffset + cipherLen;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/util/ArrayUtil.java Fri Jul 13 02:36:42 2018 +0000
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018, 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 sun.security.util;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.security.*;
+import jdk.internal.util.Preconditions;
+
+
+/**
+ * This class holds the various utility methods for array range checks.
+ */
+
+public final class ArrayUtil {
+
+ private static final BiFunction<String, List<Integer>,
+ ArrayIndexOutOfBoundsException> AIOOBE_SUPPLIER =
+ Preconditions.outOfBoundsExceptionFormatter
+ (ArrayIndexOutOfBoundsException::new);
+
+ public static void blockSizeCheck(int len, int blockSize) {
+ if ((len % blockSize) != 0) {
+ throw new ProviderException("Internal error in input buffering");
+ }
+ }
+
+ public static void nullAndBoundsCheck(byte[] array, int offset, int len) {
+ // NPE is thrown when array is null
+ Preconditions.checkFromIndexSize(offset, len, array.length, AIOOBE_SUPPLIER);
+ }
+}