8087327: CipherStream produces new byte array on every update or doFinal operation

Changed Cipher[In/Out]putStream to allocate a buffer and reuse it

Reviewed-by: weijun
diff --git a/src/java.base/share/classes/javax/crypto/CipherInputStream.java b/src/java.base/share/classes/javax/crypto/CipherInputStream.java
index c304252..36f0b9b 100644
--- a/src/java.base/share/classes/javax/crypto/CipherInputStream.java
+++ b/src/java.base/share/classes/javax/crypto/CipherInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020, 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
@@ -92,7 +92,7 @@
 
     /* the buffer holding data that have been processed by the cipher
        engine, but have not been read out */
-    private byte[] obuffer;
+    private byte[] obuffer = null;
     // the offset pointing to the next "new" byte
     private int ostart = 0;
     // the offset pointing to the last "new" byte
@@ -100,18 +100,36 @@
     // stream status
     private boolean closed = false;
 
-    /*
-     * private convenience function.
+    /**
+     * Ensure obuffer is big enough for the next update or doFinal
+     * operation, given the input length <code>inLen</code> (in bytes)
+     * The ostart and ofinish indices are reset to 0.
+     *
+     * @param inLen the input length (in bytes)
+     */
+    private void ensureCapacity(int inLen) {
+        int minLen = cipher.getOutputSize(inLen);
+        if (obuffer == null || obuffer.length < minLen) {
+            obuffer = new byte[minLen];
+        }
+        ostart = 0;
+        ofinish = 0;
+    }
+
+    /**
+     * Private convenience function, read in data from the underlying
+     * input stream and process them with cipher. This method is called
+     * when the processed bytes inside obuffer has been exhausted.
      *
      * Entry condition: ostart = ofinish
      *
-     * Exit condition: ostart <= ofinish
+     * Exit condition: ostart = 0 AND ostart <= ofinish
      *
      * return (ofinish-ostart) (we have this many bytes for you)
      * return 0 (no data now, but could have more later)
      * return -1 (absolutely no more data)
      *
-     * Note:  Exceptions are only thrown after the stream is completely read.
+     * Note: Exceptions are only thrown after the stream is completely read.
      * For AEAD ciphers a read() of any length will internally cause the
      * whole stream to be read fully and verify the authentication tag before
      * returning decrypted data or exceptions.
@@ -119,32 +137,30 @@
     private int getMoreData() throws IOException {
         if (done) return -1;
         int readin = input.read(ibuffer);
+
         if (readin == -1) {
             done = true;
+            ensureCapacity(0);
             try {
-                obuffer = cipher.doFinal();
-            } catch (IllegalBlockSizeException | BadPaddingException e) {
-                obuffer = null;
+                ofinish = cipher.doFinal(obuffer, 0);
+            } catch (IllegalBlockSizeException | BadPaddingException
+                    | ShortBufferException e) {
                 throw new IOException(e);
             }
-            if (obuffer == null)
+            if (ofinish == 0) {
                 return -1;
-            else {
-                ostart = 0;
-                ofinish = obuffer.length;
+            } else {
                 return ofinish;
             }
         }
+        ensureCapacity(readin);
         try {
-            obuffer = cipher.update(ibuffer, 0, readin);
+            ofinish = cipher.update(ibuffer, 0, readin, obuffer, ostart);
         } catch (IllegalStateException e) {
-            obuffer = null;
             throw e;
+        } catch (ShortBufferException e) {
+            throw new IOException(e);
         }
-        ostart = 0;
-        if (obuffer == null)
-            ofinish = 0;
-        else ofinish = obuffer.length;
         return ofinish;
     }
 
@@ -190,6 +206,7 @@
      *          stream is reached.
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public int read() throws IOException {
         if (ostart >= ofinish) {
             // we loop for new data as the spec says we are blocking
@@ -215,6 +232,7 @@
      * @exception  IOException  if an I/O error occurs.
      * @see        java.io.InputStream#read(byte[], int, int)
      */
+    @Override
     public int read(byte b[]) throws IOException {
         return read(b, 0, b.length);
     }
@@ -235,6 +253,7 @@
      * @exception  IOException  if an I/O error occurs.
      * @see        java.io.InputStream#read()
      */
+    @Override
     public int read(byte b[], int off, int len) throws IOException {
         if (ostart >= ofinish) {
             // we loop for new data as the spec says we are blocking
@@ -271,6 +290,7 @@
      * @return     the actual number of bytes skipped.
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public long skip(long n) throws IOException {
         int available = ofinish - ostart;
         if (n > available) {
@@ -293,6 +313,7 @@
      *             without blocking.
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public int available() throws IOException {
         return (ofinish - ostart);
     }
@@ -307,11 +328,11 @@
      *
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public void close() throws IOException {
         if (closed) {
             return;
         }
-
         closed = true;
         input.close();
 
@@ -319,15 +340,15 @@
         // AEAD ciphers are fully readed before closing.  Any authentication
         // exceptions would occur while reading.
         if (!done) {
+            ensureCapacity(0);
             try {
-                cipher.doFinal();
-            }
-            catch (BadPaddingException | IllegalBlockSizeException ex) {
+                cipher.doFinal(obuffer, 0);
+            } catch (BadPaddingException | IllegalBlockSizeException
+                    | ShortBufferException ex) {
                 // Catch exceptions as the rest of the stream is unused.
             }
         }
-        ostart = 0;
-        ofinish = 0;
+        obuffer = null;
     }
 
     /**
@@ -339,6 +360,7 @@
      * @see     java.io.InputStream#mark(int)
      * @see     java.io.InputStream#reset()
      */
+    @Override
     public boolean markSupported() {
         return false;
     }
diff --git a/src/java.base/share/classes/javax/crypto/CipherOutputStream.java b/src/java.base/share/classes/javax/crypto/CipherOutputStream.java
index fbc4356..1f98bb9 100644
--- a/src/java.base/share/classes/javax/crypto/CipherOutputStream.java
+++ b/src/java.base/share/classes/javax/crypto/CipherOutputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020, 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
@@ -80,12 +80,25 @@
     private byte[] ibuffer = new byte[1];
 
     // the buffer holding data ready to be written out
-    private byte[] obuffer;
+    private byte[] obuffer = null;
 
     // stream status
     private boolean closed = false;
 
     /**
+     * Ensure obuffer is big enough for the next update or doFinal
+     * operation, given the input length <code>inLen</code> (in bytes)
+     *
+     * @param inLen the input length (in bytes)
+     */
+    private void ensureCapacity(int inLen) {
+        int minLen = cipher.getOutputSize(inLen);
+        if (obuffer == null || obuffer.length < minLen) {
+            obuffer = new byte[minLen];
+        }
+    }
+
+    /**
      *
      * Constructs a CipherOutputStream from an OutputStream and a
      * Cipher.
@@ -123,12 +136,18 @@
      * @param      b   the <code>byte</code>.
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public void write(int b) throws IOException {
         ibuffer[0] = (byte) b;
-        obuffer = cipher.update(ibuffer, 0, 1);
-        if (obuffer != null) {
-            output.write(obuffer);
-            obuffer = null;
+        ensureCapacity(1);
+        try {
+            int ostored = cipher.update(ibuffer, 0, 1, obuffer);
+            if (ostored > 0) {
+                output.write(obuffer, 0, ostored);
+            }
+        } catch (ShortBufferException sbe) {
+            // should never happen; re-throw just in case
+            throw new IOException(sbe);
         }
     };
 
@@ -146,6 +165,7 @@
      * @exception  IOException  if an I/O error occurs.
      * @see        javax.crypto.CipherOutputStream#write(byte[], int, int)
      */
+    @Override
     public void write(byte b[]) throws IOException {
         write(b, 0, b.length);
     }
@@ -159,11 +179,17 @@
      * @param      len   the number of bytes to write.
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public void write(byte b[], int off, int len) throws IOException {
-        obuffer = cipher.update(b, off, len);
-        if (obuffer != null) {
-            output.write(obuffer);
-            obuffer = null;
+        ensureCapacity(len);
+        try {
+            int ostored = cipher.update(b, off, len, obuffer);
+            if (ostored > 0) {
+                output.write(obuffer, 0, ostored);
+            }
+        } catch (ShortBufferException e) {
+            // should never happen; re-throw just in case
+            throw new IOException(e);
         }
     }
 
@@ -180,11 +206,10 @@
      *
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public void flush() throws IOException {
-        if (obuffer != null) {
-            output.write(obuffer);
-            obuffer = null;
-        }
+        // simply call output.flush() since 'obuffer' content is always
+        // written out immediately
         output.flush();
     }
 
@@ -203,20 +228,26 @@
      *
      * @exception  IOException  if an I/O error occurs.
      */
+    @Override
     public void close() throws IOException {
         if (closed) {
             return;
         }
 
         closed = true;
+        ensureCapacity(0);
         try {
-            obuffer = cipher.doFinal();
-        } catch (IllegalBlockSizeException | BadPaddingException e) {
-            obuffer = null;
+            int ostored = cipher.doFinal(obuffer, 0);
+            if (ostored > 0) {
+                output.write(obuffer, 0, ostored);
+            }
+        } catch (IllegalBlockSizeException | BadPaddingException
+                | ShortBufferException e) {
         }
+        obuffer = null;
         try {
             flush();
         } catch (IOException ignored) {}
-        out.close();
+        output.close();
     }
 }