如何销毁Java14中的SecretKey?

How to destroy SecretKey in Java 14?

我试图在解密后清除我的 Secretkey。 根据我的阅读,SecretKeys 可以通过 destroy 方法销毁,因为 Java 8。 我正在使用 Java 14,所以应该可以。

但是,每当我对一个键使用 destroy 方法时,都会抛出一个 DestroyFailedException。 我还看到人们在他们的代码中忽略了该异常,但是,如果我这样做,我可以在调用 destroy 方法后打印密钥。

这里是我的解密方法:

private byte[] decrypt(byte[] encryptedText, char[] password) throws InvalidKeyException,
        InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, DestroyFailedException {

    ByteBuffer bb = ByteBuffer.wrap(encryptedText);

    byte[] iv = new byte[ivLengthByte];
    bb.get(iv);

    byte[] salt = new byte[saltLengthByte];
    bb.get(salt);

    byte[] cipherText = new byte[bb.remaining()];
    bb.get(cipherText);

    SecretKey key;
    key = crypto.getAESKeyFromPassword(password, salt);

    Cipher cipher;
    cipher = Cipher.getInstance(algorithm);

    cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(tagLengthBit, iv));

    byte[] plainText = cipher.doFinal(cipherText);

    Main.clearArray(password, null);
    Main.clearArray(null, iv);
    Main.clearArray(null, salt);
    Main.clearArray(null, cipherText);

    key.destroy();

    cipher = null;

    return plainText;

}

调用 destroy 方法后,如前所述,我(假设我忽略了异常)能够通过 String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded() 打印密钥);

编辑: 在数组上使用我的 Clear 方法后,我仍然可以打印它:

byte[] temp = key.getEncoded();
        Main.clearArray(null, temp);

清除数组:

protected static void clearArray(char[] chars, byte[] bytes) {
    if (chars != null) {
        for (int i = 0; i < chars.length; i++) {
            chars[i] = '[=13=]';
        }

    }
    if (bytes != null) {
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = 0;
        }

    }

}

获取AES密钥:

protected SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");

    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

    return secret;

}

最终编辑:

最好的解决办法是将 frim PBKDF2 换成 argon2。 https://github.com/kosprov/jargon2-api Argon2 允许使用原始哈希,然后您可以将该字节数组存储在上面提到的 SecureKeySpec 中,因为它允许破坏规范,并清除原始哈希数组。

你必须自己实现销毁方法。文档对此进行了解释。

https://docs.oracle.com/javase/8/docs/api/javax/crypto/SecretKey.html

实际上,没有简单的解决方法。问题在于 destroy 方法是一种“可选”方法。并非 SecretKey 的所有实现都实现了它。如果您使用的 SecretKey 类型未实现该方法,则会出现此异常并且没有简单的解决方案。

不幸的是,您不能自己实现该方法,因为(通常)它所属的 class 由 Java SE 库提供。

即使你想出了如何销毁密钥,也存在 String 包含密码 1 的问题。 (这个问题更多的是安全风险,因为搜索包含密码的 String 可能比搜索未知字节序列更容易。)

选项:

  1. 忘记问题了。不要破坏他们在内存中的密钥/密码。 (请参阅下文了解为什么这并不像听起来那么糟糕。)

  2. 寻找替代的 JSSE 加密库,其中 AES 密钥的 SecretKey 实现 实现 destroy。我猜 Bouncy Castle 图书馆可能会。 (如果他们不这样做,您始终可以选择下载源代码并对其进行修补。)

  3. 恶心的反映。您可以找出哪个实际 class 实现了密钥,并查看其代码以了解它在内部如何表示密钥。然后你可以使用反射来打破抽象并访问它的私有状态并且......在密钥上写零。


为什么不销毁密钥不是灾难?

所以一些安全专家可能不同意这一点,但我仍然认为这是一个有效的观点。

当您将内存中的密钥或密码归零时,您(表面上)可以防止以下类型的攻击:

  • 将 Java 调试器附加到 JVM 进程并使用它来定位和读取密钥。
  • 读取 JVM 进程内存。
  • 正在读取已写入磁盘的内存页面。

这些攻击有多容易?嗯,前两个要求黑客已经进入主机并升级到(可能)root 权限。在第三种情况下,您可以那样做,但黑客也可以窃取写入交换页的硬盘。

在所有情况下,黑客都必须找到 密钥。与(比如)C/C++ 程序不同,密钥不会存储在固定位置。相反,黑客必须通过模式匹配或查找引用链来找到它。 (一个 Java 调试器会更容易,前提是键对象仍然可以访问。)另一方面,一旦键被垃圾收集,内存中的副本就会消失,交换中的副本下次 OS 写出关键对象曾经存在的(现在的)脏页时将会去。在那之后……出于所有实际目的,它都“消失了”。

所以倒带一点。我说过,为了进行这种攻击,黑客已经需要root权限。 (或硬盘驱动器,这很可能是同一件事。)现在,如果他们有硬盘驱动器,他们还有其他方法可以窃取密钥。例如:

  • 使用调试器在 destroy 方法上设置断点,并在 销毁之前抓取密钥。

  • 在创建密钥之前使用调试器捕获密码。

  • 窃取服务器 SSL 证书(或其他)的私钥,以便他们可以从网络流量中获取密码。

  • 安装软件击键记录器。

  • 将您的应用程序代码替换为通过某些侧通道泄露密钥或密码的版本。

当然,他们可以安装后门等。简而言之,如果黑客已将系统破坏到对 JVM 进行“从内存中窃取内容”攻击所需的程度,那可能是最少的您的后顾之忧。

现在安全专家可能会说,对黑客进行分层防御是“最佳实践”。这是有一定道理的。但是,如果安全性对您来说很重要,您应该进行适当的安全性分析(而不仅仅是“勾选”审计)并找出真正的风险是什么。这将(可能2)告诉你,最好专注于使 系统 安全而不是担心某人(具有 root 权限)是否可以窃取密钥内存不足。


1 - 虽然不是你的情况,因为我看到你正在使用 char[] ...可以清除。除了这仍然容易受到我所说的所有 other 攻击。
2 - 或者也许不会。但是你需要做分析!

我可能找到了解决方案,我试过使用这个:https://github.com/dbsystel/SecureSecretKeySpec

唯一的问题是键必须是一个字节数组,这样做:

protected SecureSecretKeySpec getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    SecretKeyFactory factory= SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");

    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);

    byte[] temp = factory.generateSecret(spec).getEncoded();

    SecureSecretKeySpec sec= new SecureSecretKeySpec(temp, "AES");

    Main.clearArray(null, temp);

    return sec;

}

可能不太好,因为有一个getEncoded调用了SecretKey,所以Memory中可能有一个SecretKey?