在编写加密实用程序类时,我遇到了以下方法的问题:
public static void destroy(Key key) throws DestroyFailedException {
if(Destroyable.class.isInstance(key)) {
((Destroyable)key).destroy();
}
}
@Test
public void destroySecretKeySpec() {
byte[] rawKey = new byte[32];
new SecureRandom().nextBytes(rawKey);
try {
destroy(new SecretKeySpec(rawKey , "AES"));
} catch(DestroyFailedException e) {
Assert.fail();
}
}
在javax.crypto.spec.SecretKeySpec
的特定情况下,上述方法可以在java7
中正常工作,因为SecretKeySpec (javadocs 7)未实现Destroyable (javadocs 7)
现在java8
SecretKeySpec (javadocs 8)已成为Destroyable (javadocs 8),而Destroyable#destroy方法现在为default
,根据此statement
默认方法使您可以向库的接口添加新功能,并使用为这些接口的旧版本编写的代码确保二进制兼容性。
然后代码编译没有任何问题,尽管类ScretKeySpec
本身没有被更改,单独的接口SecretKey已经。
问题在于oracle's jdk8
destroy
方法具有以下实现:
public default void destroy() throws DestroyFailedException {
throw new DestroyFailedException();
}
导致运行时出现异常。
因此二进制兼容性可能没有被破坏,但现有代码已经被破坏了。上述测试通过java7
,但未通过java8
所以我的问题是:
如何处理可能导致异常的默认方法 - 因为未实现或不支持 - 或者在运行时出现意外行为?除了做
Method method = key.getClass().getMethod("destroy");
if(! method.isDefault()) {
((Destroyable)key).destroy();
}
仅对java8有效,在将来的版本中可能不正确,因为默认方法可能会得到有意义的实现。
将此默认方法保留为空而不是抛出异常(IMO会误导,因为除了合法的破坏调用之外,没有尝试过有效地破坏密钥,这不是更好吗{{3}本来会更合适,你会马上知道发生了什么)
我的方法是(类型检查/演员/召唤)
if(Destroyable.class.isInstance(key))
((Destroyable)key).destroy();
确定是否破坏?什么是另类?
这是一种误解还是只是忘记在ScretKeySpec
添加有意义的实施?
答案 0 :(得分:7)
这是一种误解还是只是忘记在SecretKeySpec中添加有意义的实现?
他们没有忘记。 SecretKeySpec
确实需要一个实现,但它还没有完成。请参阅错误JDK-8008795。对不起,修复后没有ETA。
理想情况下,在添加默认方法并将接口改装到现有类时,会添加destroy
的有效实现,但这可能不会发生,可能是因为调度。
您引用的教程中的“二进制兼容性”概念是一个相当严格的定义,它是Java Language Specification, chapter 13使用的定义。基本上它是关于库类的有效转换,它们在运行时不会导致类加载或链接错误,当与针对这些库类的旧版本编译的类相结合时。这与源不兼容性形成对比,后者导致编译时错误和行为不兼容,这通常会导致系统运行时行为发生不必要的变化。比如抛出之前没有抛出的异常。
这不是为了尽量减少您的代码被破坏的事实。它仍然是不兼容的。 (对不起。)
作为一种解决方法,您可以添加instanceof PrivateKey || instanceof SecretKey
(因为这些显然是缺少destroy
实现的类)并且让测试断言它们会抛出DestroyFailedException
,否则如果instanceof Destroyable
1}}执行测试中的其余逻辑。当这些实例在Java的某些未来版本中获得合理的destroy
实现时,测试将再次失败;这将是一个信号,可以将测试更改回所有Destroyables上的destroy
。 (另一种方法可能是完全忽略这些类,但是有效的代码路径最终可能会在很长一段时间内被忽略。)
答案 1 :(得分:5)
我只是推测,但我认为在destroy
的默认实现中抛出异常的想法是提醒您敏感数据没有被销毁。如果默认实现为空,并且没有实现覆盖默认实现,则可能会错误地认为敏感数据已被破坏。
我认为你应该抓住DestroyFailedException
异常,无论它是从默认实现还是从实际实现抛出,因为它警告你没有任何东西被破坏,你应该决定如何处理这个情况。
{7}和Java 8之间没有改变的destroy
方法的合同(除了关于默认实现的评论之外)说 - Sensitive information associated with this Object is destroyed or cleared. Subsequent calls to certain methods on this Object will result in an IllegalStateException being thrown.
和:
抛出:
DestroyFailedException - 如果destroy操作失败。
如果destroy失败,对此Object的某些方法的后续调用将不导致IllegalStateException
被抛出。如果被破坏,那仍然是真的,因此默认实现(什么都不做)会抛出DestroyFailedException
。