Java Cipher - PBE thread-safety issue

瘦欲@ 提交于 2019-12-05 08:46:01

I tend to believe this is most likely a manifestation of a JVM bug related to finalization and arrays. Below is a more generic test case. Run with java -Xmx10m -cp . UnexpectedArrayContents, the smaller the heap the more likely to fail. Not sure if calls to clone() really matter, just tried to be close to the original snippet.

// Omitting package and imports for brevity
// ...
public class UnexpectedArrayContents
{
    void demonstrate()
    {
        IntStream.range(0, 20000000).parallel().forEach(i -> {
            String expected = randomAlphaNumeric(10);
            byte[] expectedBytes = expected.getBytes(StandardCharsets.UTF_8);
            ArrayHolder holder = new ArrayHolder(expectedBytes);
            byte[] actualBytes = holder.getBytes();
            String actual = new String(actualBytes, StandardCharsets.UTF_8);
            if (!Objects.equals(expected, actual))
            {
                System.err.println("attempt#" + i + " failed; expected='" + expected + "' actual='" + actual + "'");
                System.err.println("actual bytes: " + DatatypeConverter.printHexBinary(actualBytes));
            }
        });
    }

    static class ArrayHolder
    {
        private byte[] _bytes;
        ArrayHolder(final byte[] bytes)
        {
            _bytes = bytes.clone();
        }

        byte[] getBytes()
        {
            return _bytes.clone();
        }

        @Override
        protected void finalize()
            throws Throwable
        {
            if (_bytes != null)
            {
                Arrays.fill(_bytes, (byte) 'z');
                _bytes = null;
            }
            super.finalize();
        }
    }

    private static final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final Random RND = new Random();

    static String randomAlphaNumeric(int count) {
        final StringBuilder sb = new StringBuilder();
        while (count-- != 0) {
            int character = RND.nextInt(ALPHA_NUMERIC_STRING.length());
            sb.append(ALPHA_NUMERIC_STRING.charAt(character));
        }
        return sb.toString();
    }

    public static void main(String[] args)
        throws Exception
    {
        new UnexpectedArrayContents().demonstrate();
    }
}

Update:

Now the bug is tracked as JDK-8191002. Affected versions: 8,9,10.

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);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!