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

Java Cipher - PBE thread-safety issue

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "9.0.1"
      Java(TM) SE Runtime Environment (build 9.0.1+11)
      Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [version 10.0.10586]

      A DESCRIPTION OF THE PROBLEM :
      It seems that I have a thread-safety issue with Cipher and/or PBEKeySpec.

      PBKDF2 algorithm: PBKDF2WithHmacSHA1
      Cipher algorithm: AES/CFB/NoPadding
      Key algorithm: AES

      I know these classes aren't thread-safe if we use the same instances, but that's not the case, I'm getting a new instance at each decode. But even that, sometimes the decode fails, there is no exception, just an unexpected decoded value.

      (Issue opened on stackoverflow also: https://stackoverflow.com/questions/46971788/java-cipher-pbe-thread-safety-issue )

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Decode in parallel multiple times the same encoded string using new instances of:
      * SecretKeyFactory
      * PBEKeySpec
      * SecretKey
      * SecretKeySpec
      * Cipher

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Every decode of the same encrypted string should give the same result
      ACTUAL -
      Sometimes, the decoded string is an unexpected value

      REPRODUCIBILITY :
      This bug can be reproduced often.

      ---------- BEGIN SOURCE ----------
      @Test
      public void shouldBeThreadSafe() {

          final byte[] encoded = {
              27, 26, 18, 88, 84, -87, -40, -91, 70, -74, 87, -21, -124,
              -114, -44, -24, 7, -7, 104, -26, 45, 96, 119, 45, -74, 51
          };
          final String expected = "dummy data";
          final Charset charset = StandardCharsets.UTF_8;

          final String salt = "e47312da-bc71-4bde-8183-5e25db6f0987";
          final String passphrase = "dummy-passphrase";

          // Crypto configuration
          final int iterationCount = 10;
          final int keyStrength = 128;
          final String pbkdf2Algorithm = "PBKDF2WithHmacSHA1";
          final String cipherAlgorithm = "AES/CFB/NoPadding";
          final String keyAlgorithm = "AES";

          // Counters
          final AtomicInteger succeedCount = new AtomicInteger(0);
          final AtomicInteger failedCount = new AtomicInteger(0);

          // Test
          System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "10");
          IntStream.range(0, 1000000).parallel().forEach(i -> {
              try {

                  SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm);
                  KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(charset), iterationCount, keyStrength);
                  SecretKey tmp = factory.generateSecret(spec);
                  SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), keyAlgorithm);
                  Cipher cipher = Cipher.getInstance(cipherAlgorithm);


                  int blockSize = cipher.getBlockSize();
                  IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(encoded, blockSize));
                  byte[] dataToDecrypt = Arrays.copyOfRange(encoded, blockSize, encoded.length);
                  cipher.init(Cipher.DECRYPT_MODE, key, iv);
                  byte[] utf8 = cipher.doFinal(dataToDecrypt);

                  String decoded = new String(utf8, charset);
                  if (!expected.equals(decoded)) {
                      System.out.println("Try #" + i + " | Unexpected decoded value: [" + decoded + "]");
                      failedCount.incrementAndGet();
                  } else {
                      succeedCount.incrementAndGet();
                  }
              } catch (Exception e) {
                  System.out.println("Try #" + i + " | Decode failed");
                  e.printStackTrace();
                  failedCount.incrementAndGet();
              }
          });

          System.out.println(failedCount.get() + " of " + (succeedCount.get() + failedCount.get()) + " decodes failed");
      }
      ---------- END SOURCE ----------

            apetcher Adam Petcher (Inactive)
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: