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

XML DatatypeConverter base64 parser causing JDK AES encryption packages to fail for certain payloads

XMLWordPrintable

    • x86_64

      (Transferring from JCE/AES: The base 64 decoding is causing byte arrays to be created that are not a multiple of 16, which is required for AES. Details in the currently 4th comment).

      FULL PRODUCT VERSION :
      java version "1.8.0_60"
      Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
      Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Reproduced on multiple OSes, including Linux (CentOS 7) and MacOS 10.10:

      Linux localhost.localdomain 3.10.0-229.14.1.el7.x86_64 #1 SMP Tue Sep 15 15:05:51 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

      Darwin localhost.localdomain 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64



      EXTRA RELEVANT SYSTEM CONFIGURATION :
      Reproduction of the issue has been achieved on several systems and VMs. It does not appear to be host or OS specific.

      A DESCRIPTION OF THE PROBLEM :
      The AES cipher APIs throw "IllegalBlockSize" exceptions on recent 1.8 (and 1.7) JVM/JDK editions. When the AES cipher is given a block of data to encrypt or decrypt the library will throw an exception when the data block is not a multiple of 16 bytes in size. The problem is particularly an issue when stream readers wrap the Cipher APIs and the block size can not be controlled. Older JVM/JDKs worked fine, the issue was introduced recently.

      This has been reported before, here: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8075224 but that test driver was incomplete and not generic enough to reproduce the issue. We are seeing this issue in our calls to AES and have attached a re-factored test driver that shows the issue clearly.


      REGRESSION. Last worked in version 7u75

      ADDITIONAL REGRESSION INFORMATION:
      Here's a report of what versions work with the attached test driver and what versions do not work:

      [spadmin@localhost ~]$ ./runJvmVersionTests.sh

      Testing against JDK Version: jdk1.7.0_15
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_15
      Decrypted payload from file: <?xml version="1.0" encoding="UTF-8"?><YESEFTTransactionLog><BatchNo>0</BatchNo><TotalCredits>0</TotalCredits><TotalDebits>0</TotalDebits><CreditNoTrans>0</CreditNoTrans><DebitNoTrans>0</DebitNoTrans><DeclinedNoTrans>0</Dec

      Testing against JDK Version: jdk1.7.0_45
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_45
      Decrypted payload from file: <?xml version="1.0" encoding="UTF-8"?><YESEFTTransactionLog><BatchNo>0</BatchNo><TotalCredits>0</TotalCredits><TotalDebits>0</TotalDebits><CreditNoTrans>0</CreditNoTrans><DebitNoTrans>0</DebitNoTrans><DeclinedNoTrans>0</Dec

      Testing against JDK Version: jdk1.7.0_51
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_51
      Decrypted payload from file: <?xml version="1.0" encoding="UTF-8"?><YESEFTTransactionLog><BatchNo>0</BatchNo><TotalCredits>0</TotalCredits><TotalDebits>0</TotalDebits><CreditNoTrans>0</CreditNoTrans><DebitNoTrans>0</DebitNoTrans><DeclinedNoTrans>0</Dec

      Testing against JDK Version: jdk1.7.0_75
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_75
      FAIL: IllegalBlockSizeException on 1.7.0_75

      Testing against JDK Version: jdk1.7.0_79
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_79
      FAIL: IllegalBlockSizeException on 1.7.0_79

      Testing against JDK Version: jdk1.7.0_80
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.7.0_80
      FAIL: IllegalBlockSizeException on 1.7.0_80

      Testing against JDK Version: jdk1.8.0_05
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_05
      Decrypted payload from file: <?xml version="1.0" encoding="UTF-8"?><YESEFTTransactionLog><BatchNo>0</BatchNo><TotalCredits>0</TotalCredits><TotalDebits>0</TotalDebits><CreditNoTrans>0</CreditNoTrans><DebitNoTrans>0</DebitNoTrans><DeclinedNoTrans>0</Dec

      Testing against JDK Version: jdk1.8.0_11
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_11
      Decrypted payload from file: <?xml version="1.0" encoding="UTF-8"?><YESEFTTransactionLog><BatchNo>0</BatchNo><TotalCredits>0</TotalCredits><TotalDebits>0</TotalDebits><CreditNoTrans>0</CreditNoTrans><DebitNoTrans>0</DebitNoTrans><DeclinedNoTrans>0</Dec

      Testing against JDK Version: jdk1.8.0_31
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_31
      FAIL: IllegalBlockSizeException on 1.8.0_31

      Testing against JDK Version: jdk1.8.0_40
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_40
      FAIL: IllegalBlockSizeException on 1.8.0_40

      Testing against JDK Version: jdk1.8.0_51
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_51
      FAIL: IllegalBlockSizeException on 1.8.0_51

      Testing against JDK Version: jdk1.8.0_60
      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_60
      FAIL: IllegalBlockSizeException on 1.8.0_60

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached test driver program. It will fail on JVM/JDKs that do not handle oddly sized data blocks.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The code should run correctly and successfully on all JVM/JDK versions. The Cipher API for AES encryption should handle oddly sized data blocks.
      ACTUAL -
      Test driver fails on JVM/JDK versions that have a problem with the following stack trace:

      JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:1.8.0_25
      java.io.IOException: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
      at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:115)
      at javax.crypto.CipherInputStream.read(CipherInputStream.java:233)
      at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
      at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
      at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
      at java.io.InputStreamReader.read(InputStreamReader.java:184)
      at java.io.BufferedReader.fill(BufferedReader.java:161)
      at java.io.BufferedReader.readLine(BufferedReader.java:324)
      at java.io.BufferedReader.readLine(BufferedReader.java:389)
      at JVMAESEncryptionTest.main(JVMAESEncryptionTest.java:127)
      Caused by: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
      at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:913)
      at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
      at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
      at javax.crypto.Cipher.doFinal(Cipher.java:2004)
      at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:112)
      ... 9 more
      Error while reading data from encyrpted file.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      java.io.IOException: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
      at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:115)
      at javax.crypto.CipherInputStream.read(CipherInputStream.java:233)
      at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
      at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
      at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
      at java.io.InputStreamReader.read(InputStreamReader.java:184)
      at java.io.BufferedReader.fill(BufferedReader.java:161)
      at java.io.BufferedReader.readLine(BufferedReader.java:324)
      at java.io.BufferedReader.readLine(BufferedReader.java:389)
      at JVMAESEncryptionTest.main(JVMAESEncryptionTest.java:127)
      Caused by: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
      at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:913)
      at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
      at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
      at javax.crypto.Cipher.doFinal(Cipher.java:2004)
      at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:112)
      ... 9 more
      Error while reading data from encyrpted file.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------

      import java.io.*;
      import java.security.InvalidAlgorithmParameterException;
      import java.security.InvalidKeyException;
      import java.security.Key;
      import java.security.KeyStore;
      import java.security.KeyStoreException;
      import java.security.MessageDigest;
      import java.security.NoSuchAlgorithmException;
      import java.security.Provider;
      import java.security.Security;
      import java.security.UnrecoverableKeyException;
      import java.security.cert.CertificateException;
      import java.security.spec.KeySpec;
      import java.util.*;

      import javax.crypto.*;
      import javax.crypto.*;
      import javax.crypto.spec.DESKeySpec;
      import javax.crypto.spec.IvParameterSpec;
      import javax.crypto.spec.SecretKeySpec;

      /**
       * This is related to the following Oracle bug details:
       *
       * bugs.java.com/bugdatabase/view_bug.do?bug_id=8075224
       *
       * @author adam.hampton
       *
       */
      public class JVMAESEncryptionTest {

      public JVMAESEncryptionTest() {
      // TODO Auto-generated constructor stub
      }

      // Code to convert a hex string to a byte array.
      // Credit for this quick & dirty implementation goes to:
      // http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
      public static byte[] hexStringToByteArray(String s) {
      int len = s.length();
      byte[] data = new byte[len / 2];
      for (int i = 0; i < len; i += 2) {
      data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
      + Character.digit(s.charAt(i+1), 16));
      }
      return data;
      }

      public static void main(String[] args) {

      String jvmVersion = System.getProperty("java.version");
      String newLineSep = System.getProperty("line.separator");

      System.out.println("JVM AES Encryption Test, Oracle Java bug 8075224 - java.version:" + jvmVersion);

      // Example Baste64 encoded, encrypted data.
      ArrayList<String> encryptedPayloadLines = new ArrayList<String>();
      encryptedPayloadLines.add("adP2rz4phDL01MmgPz/B+QoWvWr8UAwKcBNOVcDOvbHI1tIpIaW5a+Vyd8eK6K2WW2mjyhrhIGI9");
      encryptedPayloadLines.add("TeL552EnzKmOjefkCV8miv1yxUG+TfshXNVlaS5n4xHQ178cnsqyOylg0HoOGOIGBwE/HMEo1jlf");
      encryptedPayloadLines.add("l/+bRy7o8j5+ruDvqs4ztSnG/lj09dCIvkOSUS454adFtllpYATyWSQLBesNbqWrerDKH52GTBiR");
      encryptedPayloadLines.add("3OvXyYGAqdlah3iN4MQ56B8HtOZt3CD9y+v0lhYmVHeBrVZ0m5+b2EG12xDBCezy7JitALQP/175");
      encryptedPayloadLines.add("pt9+rbQLDBMJKvd7DKZs");

      StringBuilder fullB64DataSB = new StringBuilder();
      for (String lineFromFile : encryptedPayloadLines) {
      fullB64DataSB.append(lineFromFile);
      }
      String fullB64Data = fullB64DataSB.toString();

      // Convert the Base64 encoded payload into an array of byte data.
      // This is a headache because we want to test against all of the
      // 1.6, 1.7, 1.8 and 1.9 JVMs here and the Base64.Decoder package did
      // not appear until 1.8. Sigh.
      byte [] decodedB64 = javax.xml.bind.DatatypeConverter.parseBase64Binary(fullB64Data);

      // Used for sanity checking the base 64 decoding:
      int testFlag = 0;
      if (testFlag != 0) {
      System.out.println("Original b64 string size: " + fullB64Data.length());
      System.out.println("Decoded payload size: " + decodedB64.length);

      for (int i=0;i<decodedB64.length;i++) {
      byte thisByte = decodedB64[i];
      System.out.print(Integer.toHexString(thisByte));
      }
      System.out.println();
      }

      String fileEncryptionKey = "C9063155B07A542BD678B1E2969C1B775E47BFA9";
      byte[] deskeydata = hexStringToByteArray(fileEncryptionKey);
      deskeydata = Arrays.copyOf(deskeydata, 16);
      SecretKeySpec secretKey = new SecretKeySpec(deskeydata, "AES");

      // Declare an initialization vector.
      byte[] newiv = new byte [] { (byte)99, (byte)126, (byte)11, (byte)-10, (byte)-128, (byte)-58, (byte)-122, (byte)-16, (byte)-48, (byte)-82, (byte)40, (byte)69, (byte)52, (byte)-121, (byte)121, (byte)88 };
      IvParameterSpec ivspec = new IvParameterSpec(newiv);

      Cipher c;
      try {
      c = Cipher.getInstance("AES/CBC/PKCS5Padding");
      c.init(2, secretKey, ivspec);
      } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
      e.printStackTrace();
      System.err.println("Failed to construct Cipher object.");
      return;
      }
              
              // Process the example B64 decoded data from RAM.
              InputStream is = new ByteArrayInputStream(decodedB64);
              CipherInputStream ci = new CipherInputStream(is, c);
              
              InputStreamReader reader;
              BufferedReader br;
      try {
      reader = new InputStreamReader(ci, "UTF-8");
      br = new BufferedReader(reader);
      } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
      System.err.println("Failed to construct a reader for encrypted stream");
      return;
      }
              
      String xmlString = "";
      StringBuilder xmlData = new StringBuilder();
      try {
      while ( (xmlString = br.readLine()) != null) {
      xmlData.append(xmlString);
      }
      reader.close();
                  ci.close();
      br.close();
      } catch (IOException e) {
      /*
      if (e.getMessage().contains("IllegalBlockSizeException")) {
      System.err.println("FAIL: IllegalBlockSizeException on JVM " + jvmVersion);
      System.exit(1);
      }
      */
      e.printStackTrace();
      System.err.println("Error while reading data from encyrpted file.");
      return;
      }

      System.out.println("Decrypted payload from file: " + xmlData.toString());

      return;
      }

      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      There are no workarounds for code that needs to use a stream to pass data to an AES Cipher.

      For code that encodes its own AES payloads block-by-block the caller must ensure it passes a 16 byte aligned byte array to the Cipher APIs.

      SUPPORT :
      YES

            rgrigoriadi Roman Grigoriadi (Inactive)
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: