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

AES-GCM init() -> update() -> final() ciphertexts do not match init() -> final()

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Not an Issue
    • Icon: P4 P4
    • None
    • 8, 8u45, 8u60
    • security-libs

      FULL PRODUCT VERSION :
      java version "1.8.0_40"
      Java(TM) SE Runtime Environment (build 1.8.0_40-b26)
      Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      found on CentOS 6.6, possibly platform-independent

      A DESCRIPTION OF THE PROBLEM :
      When plaintext is provided to an AES/GCM Cipher object via update(), the resulting ciphertext does not match the ciphertext generated if plaintext is provided directly through final(). Note that only the first block is different.

      The final-only ciphertext appears to be correct (it decrypts back to the original plaintext; the update-final method generates a BadTagException).

      ADDITIONAL REGRESSION INFORMATION:
      java version "1.8.0_40"
      Java(TM) SE Runtime Environment (build 1.8.0_40-b26)
      Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Example code is provided in the appropriate field.

      1. Create two Cipher objects with transform "AES/GCM/NOPADDING", provider "SunJCE" with a key size of 192 (any valid key size should work) and initialize them.

      2. Create a byte array of 1024 bytes (any length should work). Give the bytes any value.

      3. Create two byte arrays of 1024 bytes (or whatever length used for #2). Zeroize the bytes.

      4. Provide the plaintext to one cipher object (we'll call this Cipher U) via update(plainText, 0, plainText.length, updateCipherTextByteArray) and finalize with doFinal(plainText, 0, 0, updateCipherTextByteArray)

      5. Provide the plaintext to the other cipher object (we'll call this Cipher F) via doFinal(plainText, 0, plainText.length, finalCipherTextByteArray)

      6. Print out/compare the two ciphertexts and notice the difference in the first block (probably the first 16 bytes).


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Both printed hex-formatted ciphertexts should start with "88 d5 57 c6 3b 5b 65 a1 a5 73 48 6a 0d f8 18 99 d4 c4 9c 63 7e 9c f8 fd 77 b1 24"
      ACTUAL -
      The update-final method prints out a ciphertext beginning with "1b 82 c7 0c ab 62 73 d1 ec 0f 54 d9 95 eb 68 70 d4 c4 9c 63 7e 9c f8 fd 77 b1 24"

      Also, my code prints a (tiny) stacktrace citing the mismatch on the first byte.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.lang.Exception: Cipher texts do not match (starting at byte 0).
           at GCMError.main(GCMError.java:123)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import javax.crypto.Cipher;
      import javax.crypto.KeyGenerator;
      import javax.crypto.SecretKey;
      import javax.crypto.spec.GCMParameterSpec;

      public class GCMError {

      public static void main (String[] args) throws Exception {
      byte[] plainText = null;
      byte[] sunFinalCipherText = null;
      byte[] sunUpdateCipherText = null;
      byte[] aad = null;

      Cipher sunFinal = null;
      Cipher sunUpdate = null;

      Cipher[] ciphers = new Cipher[2];

      String trans = "AES/GCM/NOPADDING";
      String sunProvider = "SunJCE";
      String cipherName = "AES";

      int keySize = 192;
      int numBytes = 1024;

      SecretKey secretKey = null;

      KeyGenerator keygen = null;

      // Create ciphers
      sunFinal = Cipher.getInstance(trans, sunProvider);
      sunUpdate = Cipher.getInstance(trans, sunProvider);

      ciphers[0] = sunFinal;
      ciphers[1] = sunUpdate;

      // Generate a key
      keygen = KeyGenerator.getInstance(cipherName);
      keygen.init(keySize);
      secretKey = keygen.generateKey();

      // Generate IV
      byte[] iv = new byte[sunFinal.getBlockSize()];
      for (int i = 0; i < iv.length; i++) {
      iv[i] = (byte) (i*2);
      }

      // Generate some AAD data
      aad = new byte[100];
      for (int i = 0; i < aad.length; i++) {
      aad[i] = (byte) (i*2);
      }

      // Generate some plaintext
      plainText = new byte[numBytes];
      for (int i = 0; i < plainText.length; i++) {
      plainText[i] = (byte)(i+1);
      }

      // Create the paramspec
      GCMParameterSpec paramSpec = new GCMParameterSpec(sunFinal.getBlockSize()
      * Byte.SIZE, iv);

      // Initialize the ciphers;
      for (Cipher cipher : ciphers) {
      cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
      }

      // Set up some default data for the ciphertexts
      sunFinalCipherText = new byte[sunFinal.getOutputSize(numBytes)];
      for (int i = 0; i < numBytes; i++) {
      sunFinalCipherText[i] = (byte) i;
      }
      sunUpdateCipherText = sunFinalCipherText.clone();

      // Pass the AAD to ciphers
      for (Cipher cipher : ciphers) {
      cipher.updateAAD(aad, 0, aad.length);
      }

      // Do updates
      sunUpdate.update(plainText, 0, plainText.length, sunUpdateCipherText);

      // Do finals
      sunFinal.doFinal(plainText, 0, plainText.length, sunFinalCipherText);
      sunUpdate.doFinal(plainText, 0, 0, sunUpdateCipherText);

      System.out.print("Ciphertexts:");
      for (int a=0; a<sunFinalCipherText.length; a++) {
      System.out.printf(" %02x", a);
      }
      System.out.println();
      for (int i=0; i<ciphers.length; i++) {
      switch(i) {
      case 0:
      System.out.print("sunFinal: ");
      break;
      case 1:
      System.out.print("sunUpdate: ");
      break;
      case 2:
      System.out.print("ciscoFinal: ");
      break;
      case 3:
      System.out.print("ciscoUpdate:");
      break;
      }
      for (int a=0; a<sunFinalCipherText.length; a++) {
      switch(i) {
      case 0:
      System.out.printf(" %02x", sunFinalCipherText[a]);
      break;
      case 1:
      System.out.printf(" %02x", sunUpdateCipherText[a]);
      break;
      }
      }
      System.out.println();
      }

      for (int i=0; i<sunFinalCipherText.length; i++) {
      if (sunFinalCipherText[i] != sunUpdateCipherText[i]) {
      throw new Exception(String.format("Cipher texts do not match (starting at byte %d).", i));
      }
      }

      System.out.println("Cipher texts match!");
      }

      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      Do not use the update() method with AES/GCM; use final() directly instead.

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: