Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-7142509

Cipher.doFinal(ByteBuffer,ByteBuffer) fails to process when in.remaining() == 0

    XMLWordPrintable

Details

    • b27
    • x86
    • windows_xp
    • Verified

    Backports

      Description

        FULL PRODUCT VERSION :
        java version "1.7.0_02"
        Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
        Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)
        - OR -
        Java HotSpot(TM) Server VM (build 22.0-b10, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows XP [Version 5.1.2600]

        A DESCRIPTION OF THE PROBLEM :
        When invoking Cipher.doFinal(ByteBuffer, ByteBuffer) with input.remaining() == 0 and at least one of the buffers is a direct buffer, the method fails to process any data internally buffered in the cipher and store it in the output buffer.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1. Create and initialize a cipher, e.g. for the DES/CBC/PKCS5Padding transformation.
        2. Allocate two separate byte buffers inBuffer and outBuffer, at least one of which is a direct buffer, of sufficient size, and put some random data into inBuffer.
        3. Invoke cipher.update(inBuffer, outBuffer), which processes all data except for the last (partial or complete) block. inBuffer.hasRemaining() should now return false.
        4. Invoke cipher.doFinal(inBuffer, outBuffer) to process the last block which is buffered in the cipher, applying padding if necessary.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        outBuffer should now contain all encrypted blocks, including the last one that was processed by doFinal(ByteBuffer, ByteBuffer).
        Subsequently calling doFinal() [no arguments] twice will yield byte arrays with equal contents, the ciphertext for the empty message.
        ACTUAL -
        outBuffer contains all blocks EXCEPT the last one that should have been processed by doFinal(ByteBuffer, ByteBuffer).
        Subsequently calling doFinal() [no arguments] twice will yield byte arrays with DIFFERENT contents, as the first invocation finally processes the data internally buffered in the cipher.

        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------
        package javax.crypto.test;

        import java.nio.ByteBuffer;
        import java.security.SecureRandom;
        import java.util.Arrays;
        import java.util.Random;

        import javax.crypto.Cipher;
        import javax.crypto.SecretKey;
        import javax.crypto.spec.SecretKeySpec;

        /**
         * Simple test case to show that {@link javax.crypto.Cipher#doFinal(ByteBuffer, ByteBuffer)} fails to process the data
         * internally buffered in the cipher when <tt>input.remaining() == 0<tt> and at least one buffer is a direct buffer.
         */
        public class BrokenDoFinal {

        public static void main(String args[]) throws Exception {
        Random random = new SecureRandom();

        byte[] keyBytes = new byte[8];
        random.nextBytes(keyBytes);
        SecretKey key = new SecretKeySpec(keyBytes, "DES");

        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        int size = 12;

        byte[] testdata = new byte[size];
        random.nextBytes(testdata);

        byte[] expected = cipher.doFinal(testdata);

        // first case: both buffers are heap buffers
        System.out.print("Test case 1 (only heap buffers) : ");
        ByteBuffer in1 = ByteBuffer.allocate(size);
        ByteBuffer out1 = ByteBuffer.allocate(cipher.getOutputSize(size));

        // this succeeds
        try {
        encrypt(in1, out1, cipher, testdata, expected);
        System.out.println("ok");
        } catch (AssertionError e) {
        System.out.println("FAILED -- " + e.getMessage());
        }
        if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
        System.out.println(" Internal buffers still held data!");
        }

        // second case: input buffer is direct buffer, output buffer is heap buffer
        System.out.print("Test case 2 (input buffer is direct) : ");
        ByteBuffer in2 = ByteBuffer.allocateDirect(size);
        ByteBuffer out2 = ByteBuffer.allocate(cipher.getOutputSize(size));

        // this fails
        try {
        encrypt(in2, out2, cipher, testdata, expected);
        System.out.println("ok");
        } catch (AssertionError e) {
        System.out.println("FAILED -- " + e.getMessage());
        }
        if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
        System.out.println(" Internal buffers still held data!");
        }

        // third case: input buffer is heap buffer, output buffer is direct buffer
        System.out.print("Test case 3 (output buffer is direct) : ");
        ByteBuffer in3 = ByteBuffer.allocate(size);
        ByteBuffer out3 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));

        // this fails, too
        try {
        encrypt(in3, out3, cipher, testdata, expected);
        System.out.println("ok");
        } catch (AssertionError e) {
        System.out.println("FAILED -- " + e.getMessage());
        }
        if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
        System.out.println(" Internal buffers still held data!");
        }

        // fourth case: both buffers are direct buffers
        System.out.print("Test case 4 (both buffers are direct) : ");
        ByteBuffer in4 = ByteBuffer.allocateDirect(size);
        ByteBuffer out4 = ByteBuffer.allocateDirect(cipher.getOutputSize(size));

        // and this of course also fails
        try {
        encrypt(in4, out4, cipher, testdata, expected);
        System.out.println("ok");
        } catch (AssertionError e) {
        System.out.println("FAILED -- " + e.getMessage());
        }
        if (!Arrays.equals(cipher.doFinal(), cipher.doFinal())) {
        System.out.println(" Internal buffers still held data!");
        }
        }

        private static void encrypt(ByteBuffer in, ByteBuffer out, Cipher cipher, byte[] testdata, byte[] expected)
        throws Exception {
        in.put(testdata);
        in.flip();

        // process all data
        cipher.update(in, out);
        if (in.hasRemaining()) {
        throw new RuntimeException("buffer not empty");
        }

        // finish encryption and process all data buffered in the cipher
        cipher.doFinal(in, out);
        out.flip();

        // validate output size
        if (out.remaining() != expected.length) {
        throw new AssertionError("incomplete encryption output, expected " + expected.length
        + " bytes but was only " + out.remaining() + " bytes");
        }

        // validate output data
        byte[] encrypted = new byte[out.remaining()];
        out.get(encrypted);
        if (Arrays.equals(expected, encrypted) == false) {
        throw new AssertionError("bad encryption output");
        }
        }

        }
        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        Use Cipher.doFinal() [no arguments] to retrieve a byte array with the last block instead. If necessary for channel I/O, put the contents of the byte array into the output buffer.

        Attachments

          Issue Links

            Activity

              People

                wetmore Bradford Wetmore
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                0 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved:
                  Imported:
                  Indexed: