Java Cipher-PBE线程安全问题 [英] Java Cipher - PBE thread-safety issue
问题描述
似乎我的Cipher和/或PBEKeySpec存在线程安全问题。
- JDK:1.8.0_102,1.8。 0_151和9.0.1 + 11
- PBKDF2算法:PBKDF2WithHmacSHA1
- 密码算法:AES / CFB / NoPadding
- 关键算法:AES
我知道,如果我们使用相同的实例,这些类就不是安全的,但是那不是情况下,每次解码我都会得到一个新实例。
但是,即使那样,有时解码失败,也没有例外,只是意外的解码值。
我已经能够重现问题:
@Test
public void shouldBeThreadSafe(){
最终字节[]编码= {
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 Charset charset = StandardCharsets.UTF_8;
final String salt = e47312da-bc71-4bde-8183-5e25db6f0987;
final String passphrase = dummy-passphrase;
//加密配置
final interationCount = 10;
final int keyStrength = 128;
final String pbkdf2Algorithm = PBKDF2WithHmacSHA1;
final String cipherAlgorithm = AES / CFB / NoPadding;
final String keyAlgorithm = AES;
//计数器
最终AtomicInteger successCount = new AtomicInteger(0);
final AtomicInteger failedCount =新的AtomicInteger(0);
//测试
System.setProperty( java.util.concurrent.ForkJoinPool.common.parallelism, 10);
IntStream.range(0,1000000).parallel()。forEach(i-> {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm);
KeySpec规范=新的PBEKeySpec(passphrase.toCharArray(),salt.getBytes(charset),iterationCount,keyStrength);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec( tmp.getEncoded(),keyAlgorithm);
密码= Cipher.getInstance(cipherAlgorithm);
int blockSize = cipher.getBlockSize();
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(encoded,blockSize));
byte [] dataToDecrypt = Arrays.copyOfRange(encoded,blockSize,encoding.length);
cipher.init(Cipher.DECRYPT_MODE,key, iv);
byte [] utf8 = cipher.doFinal(dataToDecrypt);
解码后的字符串=新字符串(utf8,字符集);
if(!e xpected.equals(decoded)){
System.out.println( Try# + i + |意外的解码值:[ +解码+]);
failedCount.incrementAndGet();
}否则{
successCount.incrementAndGet();
}
} catch(Exception e){
System.out.println( Try# + i + |解码失败);
e.printStackTrace();
failedCount.incrementAndGet() ;
}
});
System.out.println(failedCount.get()+ of +(succeedCount.get()+ failedCount.get()) +解码失败);
}
输出:
尝试#656684 |意外的解码值:[ jE|S 。]
尝试#33896 |意外的解码值:[ jE| S ]
2000000次解码失败
我不不了解该代码如何失败,Cipher和/或PBEKeySpec类中是否存在错误?还是我错过了测试中的内容?
任何帮助都非常欢迎。
UPDATE
OpenJDK问题: https://bugs.openjdk.java.net/browse/JDK-8191177
这确实是 PBKDF2KeyImpl.getEncoded()
方法中的一个JDK错误。
错误报告中的更多详细信息 https:/ /bugs.openjdk.java.net/browse/JDK-8191177 和相关问题 https://bugs.openjdk.java.net/browse/JDK-8191002 。
该问题已在Java中修复并于2018年1月发布。 CPU版本。
更新:此问题已在JDK 9和更高版本中通过使用reachabilityFence()修复。
由于JDK的早期版本中没有此防护栏,因此应使用一种变通方法:«最早由Hans Boehm发现的一种碰巧就是即使现在也实现等效的reachabilityFence(x)的方法是 synchronized(x){} »
在我们的示例中,解决方法是:
< pre class = lang-java prettyprint-override>
SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2Algorithm);
KeySpec spec =新的PBEKeySpec(passphrase.toCharArray(),salt.getBytes(charset),迭代计数,keyStrength);
SecretKey secret = factory.generateSecret(spec);
SecretKeySpec键;
// noinspection SynchronizationOnLocalVariableOrMethodParameter
synced(secret){
key = new SecretKeySpec(secret.getEncoded(),keyAlgorithm);
}
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
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);
}
这篇关于Java Cipher-PBE线程安全问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!