用Java破坏数据

时间:2020-03-06 14:35:32  来源:igfitidea点击:

无论如何,Java中是否有删除数据(例如,变量值,对象)并确保无法从内存中恢复的数据?在Java中将`null'分配给变量是否会从内存中删除该值?有任何想法吗?适用于其他语言的答案也是可以接受的。

解决方案

不,除非我们对硬件有直接的回答。变量有可能会被缓存在某个地方。敏感数据甚至可以存储在swap中:)如果只考虑RAM,则可以使用垃圾回收器。在高级lang中,通常我们无法直接访问内存,因此无法控制此方面。例如,在.NET中,有一个使用互操作和直接内存访问的SecureString类。

由于虚拟内存的奇观,几乎不可能以完全无法检索的方式从内存中删除某些内容。最好的选择是将值字段清零。然而:

  • 这并不意味着该对象的旧(未清零)副本不会保留在未使用的交换页面上,该页面在重新启动后可能会持续存在。
  • 它也不会阻止某人将调试器添加到应用程序并在对象归零之前四处乱逛,或者使VM崩溃并在堆转储中四处乱逛。

什么都不会被删除,它几乎可以被应用程序访问或者删除。
一旦无法访问,该空间将在需要时成为后续使用的候选空间,并且该空间将被覆盖。
在直接内存访问的情况下,总会有一些东西要读取,但是它可能是垃圾,没有任何意义。

将Object设置为null并不意味着对象已从内存中删除。如果没有更多对该对象的引用,虚拟机将将该对象标记为已准备好进行垃圾回收。根据代码,即使将其设置为null,在这种情况下也不会删除它,但仍可能会引用它。 (本质上来说,如果我们希望将其收集为垃圾并且不是,那将导致内存泄漏!)

一旦将其标记为可以收集,我们就无法控制垃圾收集器何时将其删除。我们可以弄乱垃圾回收策略,但我不建议这样做。
剖析应用程序并查看该对象及其ID,我们可以看到引用它的对象。 Java提供1.6.0_07及更高版本的VisualVM或者我们可以使用NetBeans

在当今时代,完全无法挽回的事情几乎是不可能的。
当我们通常删除某些内容时,唯一会发生的事情是将内存中的第一个位置清空。第一个位置曾经包含有关必须为该程序或者其他内容保留多少内存的信息。

但是所有其他信息仍然存在,直到被其他人覆盖。

我评估TinyShredder或者使用设置为Gutmann-pass的CCleaner

正如zacherates所说,在删除对对象的引用之前,请将对象的敏感字段清零。请注意,我们不能将String的内容归零,因此请使用char数组并将每个元素归零。

我认为最好的选择(并不复杂)是使用char [],然后更改数组中的每个位置。关于可能将其复制到内存中的其他注释仍然适用。

通过将新的随机内容写入原始数据(字节,字符,整数,双精度)及其数组(字节[],...),可以擦除它们。

必须通过覆盖对象的原始属性来清理对象数据;将变量设置为null只会使对象可用于GC,但不会立即失效。 VM转储将包含它们,任何人都可以看到。

诸如String之类的不可变数据不能以任何方式覆盖。任何修改都只是复制。我们应避免将敏感数据保留在此类对象中。

P.S.如果我们谈论密码,最好使用加密强度高的哈希函数(MD5,SHA1等),永远不要使用明文形式的密码。

如果我们正在考虑保护密码/密钥管理,则可以编写一些JNI代码,该代码使用特定于平台的API以安全的方式存储密钥,而不会将数据泄漏到JVM管理的内存中。例如,我们可以将密钥存储在物理内存中锁定的页面中,并且可以阻止IO总线访问内存。

编辑:要评论一些先前的答案,JVM可以在不删除其先前位置的情况下将对象重新放置在内存中,因此,即使char [],bytes,int和其他"可擦除"数据类型也不是答案,如果我们真的想要确保没有敏感信息存储在JVM管理的内存中或者交换到硬盘驱动器上。

将敏感数据存储在数组中,然后尽快将其"归零"。

虚拟内存系统可以将RAM中的任何数据复制到磁盘。 RAM中的数据(或者核心转储)也可以通过调试工具进行检查。为了最大程度地减少这种情况的发生,我们应该努力做到以下几点

  • 保持时间间隔尽可能短地存在于内存中
  • 请注意内部缓冲数据的IO管道(例如BufferedInputStream)
  • 将对机密的引用保留在堆栈中并从堆中移出
  • 不要使用诸如String之类的不可变类型来保存机密

Java中的加密API使用这种方法,我们创建的任何API也应支持该方法。例如," KeyStore.load"允许调用者清除密码" char []",并且在调用完成时,基于密码的加密的KeySpec也将清除。

理想情况下,我们可以使用" finally"块将数组归零,如下所示:

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream is = …
char[] pw = System.console().readPassword();
try {
 ks.load(is, pw);
}
finally {
  Arrays.fill(pw, '##代码##');
}