18

在编写加密实用程序类时,我遇到了以下方法的问题:

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)

现在,SecretKeySpec (javadocs 8)java8类已经被Destroyable (javadocs 8)Destroyable#destroy方法现在可以根据这个声明default

默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

然后代码编译没有任何问题,尽管类ScretKeySpec本身没有改变,只有接口SecretKey已经改变。

问题是oracle's jdk8destroy方法中有以下实现:

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 具有误导性,因为除了合法调用销毁之外,没有尝试有效地销毁密钥,UnsupportedOperationException会更合适,你会立即知道发生了什么)

  • 我的方法是(类型检查/转换/调用)

    if(Destroyable.class.isInstance(key))
        ((Destroyable)key).destroy();
    

    用于判断是否销毁有误?什么是替代方案?

  • 这是一种误解,还是他们只是忘记在其中添加有意义的实现ScretKeySpec

4

2 回答 2

7

这是一种误解,还是他们只是忘记在 SecretKeySpec 中添加有意义的实现?

好在他们没有忘记。SecretKeySpec确实需要一个实现,但它还没有完成。请参阅错误JDK-8008795。抱歉,没有关于何时修复此问题的预计时间。

理想情况下,destroy在添加默认方法并将接口改装到现有类上时会添加有效的实现,但它没有发生,可能是因为调度。

您引用的教程中的“二进制兼容性”的概念是一个相当严格的定义,这是Java 语言规范第 13 章使用的定义。基本上它是关于库类的有效转换,当与针对这些库类的旧版本编译的类结合使用时,不会在运行时导致类加载或链接错误。这与导致编译时错误的源不兼容和导致系统运行时行为的通常不需要的变化的行为不兼容形成对比。比如抛出之前没有抛出的异常。

这并不是为了尽量减少您的代码被破坏的事实。这仍然是不兼容的。(对不起。)

作为一种解决方法,您可以添加instanceof PrivateKey || instanceof SecretKey(因为这些显然是缺少destroy实现的类)并让测试断言它们确实 throw DestroyFailedException,否则如果instanceof Destroyable执行测试中的其余逻辑。当这些实例destroy在未来的 Java 版本中得到合理的实现时,测试将再次失败;这将是一个将测试更改回调用destroy所有 Destroyables 的信号。(另一种选择可能是完全忽略这些类,但有效的代码路径可能最终会在相当长的一段时间内保持未被发现。)

于 2014-07-21T05:53:26.663 回答
5

我只是在推测,但我认为在默认实现中抛出异常的想法destroy是提醒您敏感数据没有被破坏。如果默认实现为空,并且没有覆盖默认实现的实现,您可能会错误地认为敏感数据已被破坏。

我认为DestroyFailedException无论如何你都应该捕获异常,无论它是从默认实现还是从真正的实现抛出,因为它警告你没有被破坏,你应该决定如何处理这种情况。

destroy方法的合同在 Java 7 和 Java 8 之间没有改变(除了关于默认实现的注释)说 -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 失败,随后对该对象的某些方法的调用将不会导致IllegalStateException被抛出。如果destroyed 什么都不做,这仍然是正确的,因此默认实现(什么都不做) throws DestroyFailedException

于 2014-07-20T13:43:22.647 回答