Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
603 views
in Technique[技术] by (71.8m points)

encryption - Java Cipher - PBE thread-safety issue

It seems that I have a thread-safety issue with Cipher and/or PBEKeySpec.

  • JDK : 1.8.0_102, 1.8.0_151 and 9.0.1+11
  • PBKDF2 algorithm: PBKDF2WithHmacSHA1
  • Cipher algorithm: AES/CFB/NoPadding
  • Key algorithm: AES

I know these classes aren't tread-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.

I've been able to reproduce the problem:

@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");
}

Output:

Try #656684 | Unexpected decoded value: [?jE    |S???]
Try  #33896 | Unexpected decoded value: [?jE    |S???]

2 of 1000000 decodes failed

I don't understand how this code can fail, is there a bug in the Cipher and/or PBEKeySpec classes? Or have I missed something in my test?

Any help would be very welcomed.


UPDATE

OpenJDK issue: https://bugs.openjdk.java.net/browse/JDK-8191177

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It was indeed a JDK bug in the PBKDF2KeyImpl.getEncoded() method.

More details in the bug report https://bugs.openjdk.java.net/browse/JDK-8191177 and the related issue https://bugs.openjdk.java.net/browse/JDK-8191002.

It has been fixed and shipped within the Java January 2018 CPU release.

UPDATE: This has been fixed for JDK 9 and later by the use of a reachabilityFence().

Because of the lack of this fence in the ealier version of JDK you should use a workaround: ? as first discovered by Hans Boehm, it just so happens that one way to implement the equivalent of reachabilityFence(x) even now is "synchronized(x) {}" ?

In our case, the workaround is:

SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm);
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(charset), iterationCount, keyStrength);
SecretKey secret = factory.generateSecret(spec);
SecretKeySpec key;
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized(secret) {
  key = new SecretKeySpec(secret.getEncoded(), keyAlgorithm);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...