JDK-8191177 : Java Cipher - PBE thread-safety issue
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 9.0.1,10
  • Priority: P3
  • Status: Resolved
  • Resolution: Duplicate
  • OS: windows
  • CPU: x86_64
  • Submitted: 2017-11-02
  • Updated: 2019-08-30
  • Resolved: 2018-01-24
Related Reports
Relates :  
Description
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 ----------


Comments
> This bug has been fixed in the January 2018 CPU release. I believe this is fixed among these commits (cannot reference issues directly, because they are security-related confidential): https://hg.openjdk.java.net/jdk/jdk/rev/57aa80913140 (8207775: Better management of CipherCore buffers) https://hg.openjdk.java.net/jdk/jdk/rev/53c3b460503c (8208583: Better management of internal KeyStore buffers) https://hg.openjdk.java.net/jdk/jdk/rev/c9a3e3cac9c7 (8209129: Further improvements to cipher buffer management) https://hg.openjdk.java.net/jdk/jdk/rev/46e99460e8c9 (8172525: Improve key keying case) All three are in 8u181 and up.
30-08-2019

This bug has been fixed in the January 2018 CPU release.
24-01-2018

To reproduce the issue, run the attached test case multiple times (I could reproduce intermittently). JDK 9.0.1 - Fail JDK10-ea+30 - Fail Try #969026 | Unexpected decoded value: [?jE |S??���?] 1 of 1000000 decodes failed
14-11-2017