如何解决如何在 Java 14 中销毁 SecretKey?
我试图在解密后清除我的 Secretkey
。
从我读过的内容来看,从 Java 8 开始,SecretKeys
可以通过 destroy
方法销毁。
我使用的是 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 encodingKey = 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] = '\0';
}
}
if (bytes != null) {
for (int i = 0; i < bytes.length; i++) {
bytes[i] = 0;
}
}
}
getAESKey:
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 中,因为它允许销毁 Spec,并清除原始哈希数组。
解决方法
实际上,对此没有简单的解决方案。问题在于 destroy
方法是一种“可选”方法。并非 SecretKey
的所有实现都实现了它。如果您使用的是未实现该方法的 SecretKey
类型,则会出现此异常并且没有简单的解决方案。
不幸的是,您不能自己实现该方法,因为(通常)它所属的类是由 Java SE 库提供的。
即使你搞清楚了如何销毁密钥,也存在包含密码1的String
的问题。 (这个问题更多是一个安全风险,因为搜索包含密码的 String
可能比搜索未知字节序列更容易。)
选项:
-
忘记这个问题。不要破坏他们在内存中的密钥/密码。 (有关为什么这并不像听起来那么糟糕的解释,请参见下文。)
-
寻找替代 JSSE 加密库,其中 AES 密钥的 SecretKey 实现确实实现了
destroy
。我猜测充气城堡图书馆可能会。 (如果他们不这样做,您始终可以选择下载源代码并修补它们。) -
讨厌的反射。您可以确定哪个实际类实现了密钥,并查看其代码以了解它如何在内部表示密钥。然后,您可以使用反射来打破抽象并访问其私有状态,并在密钥上写入零。
为什么不破坏密钥不是灾难?
所以一些安全专家可能不同意这一点,但我仍然认为这是一个有效的观点。
当您将内存中的密钥或密码归零时,您(表面上)是在防止以下类型的攻击:
- 将 Java 调试器附加到 JVM 进程并使用它来定位和读取密钥。
- 读取 JVM 进程内存。
- 读取已写入磁盘的内存页。
这些攻击有多容易?那么前两个要求黑客已经进入主机并升级到(可能)root权限。在第三种情况下,您可以这样做,但黑客也可以窃取写入交换页的硬盘。
在所有情况下,黑客都必须找到密钥。与(比如说)C/C++ 程序不同,密钥不会存储在固定位置。相反,黑客必须通过模式匹配或通过查找引用链来找到它。 (Java 调试器会更容易,前提是键对象仍然可以访问。)另一方面,一旦键被垃圾回收,内存中的副本将消失,而交换中的副本将进入下一个操作系统写出关键对象曾经存在的(现在)脏页的时间。在那之后……出于所有实际目的,它“消失了”。
所以倒退一点。我说为了进行这种攻击,黑客已经需要root权限。 (或硬盘驱动器,这很可能相当于同一件事。)现在,如果他们拥有它,他们就可以通过其他方式窃取密钥。例如:
-
使用调试器在(比如说)
destroy
方法上设置断点,并在它被销毁之前获取密钥。 -
在创建密钥之前使用调试器捕获密码。
-
窃取服务器 SSL 证书(或其他)的私钥,以便他们可以从网络流量中获取密码。
-
安装软件击键记录器。
-
将您的应用程序代码替换为通过某些侧通道泄露密钥或密码的版本。
当然,他们可以安装后门等。简而言之,如果黑客已经将系统破坏到了对 JVM 进行“内存中窃取”攻击所需的程度,那么这可能是您最不想要的担心。
现在,安全专家可能会说对黑客进行分层防御是“最佳实践”。这是有一定道理的。但是,如果安全性对您来说很重要,您应该进行适当的安全性分析(不仅仅是“勾选框”审计)并找出真正的风险是什么。这将(可能2)告诉您,最好专注于确保系统安全,而不是担心有人(具有 root 权限)是否可以从内存中窃取密钥。
1 - 虽然不是你的情况,因为我看到你正在使用 char[]
... 可以清除。除了这仍然容易受到我谈到的所有其他攻击。
2 - 或者可能不会。但是你需要做分析!
您必须自己实现 destroy 方法。文档对此进行了解释。
https://docs.oracle.com/javase/8/docs/api/javax/crypto/SecretKey.html
,我可能找到了解决方案,我尝试使用: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;
}
可能不太好,因为在 SecretKey 上调用了 getEncoded
,所以内存中可能有一个 SecretKey?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。