如何销毁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
可能比搜索未知字节序列更容易。)
选项:
忘记问题了。不要破坏他们在内存中的密钥/密码。 (请参阅下文了解为什么这并不像听起来那么糟糕。)
寻找替代的 JSSE 加密库,其中 AES 密钥的 SecretKey 实现 实现 destroy
。我猜 Bouncy Castle 图书馆可能会。 (如果他们不这样做,您始终可以选择下载源代码并对其进行修补。)
恶心的反映。您可以找出哪个实际 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?
我试图在解密后清除我的 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
可能比搜索未知字节序列更容易。)
选项:
忘记问题了。不要破坏他们在内存中的密钥/密码。 (请参阅下文了解为什么这并不像听起来那么糟糕。)
寻找替代的 JSSE 加密库,其中 AES 密钥的 SecretKey 实现 实现
destroy
。我猜 Bouncy Castle 图书馆可能会。 (如果他们不这样做,您始终可以选择下载源代码并对其进行修补。)恶心的反映。您可以找出哪个实际 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?