如何在java中安全地擦除内存中的机密数据,并保证它不会被“优化”?

String secret="foo"; WhatILookFor.securelyWipe(secret); 

我需要知道它不会被java优化器删除。

字符串不能“擦除”。 它是不可改变的,缺少一些非常肮脏和危险的技巧,你无法改变它。

因此,最安全的解决方案是首先不要将数据放入字符串中。 使用StringBuilder或字符数组,或其他一些不可变的表示。 (然后在完成后清除它。)


为了记录,有几种方法可以更改String的后备arrays的内容。 例如,您可以使用reflection来捕获对String的后备数组的引用,并覆盖其内容。 但是,这涉及到JLS声明具有未指定行为的事情,因此您无法保证优化器不会执行意外操作。


我个人认为,最好是锁定应用程序平台,以便未经授权的人员首先无法访问内存/内存转储。 毕竟,如果平台没有得到妥善保护,那么“坏人”可能会在您删除之前获取字符串内容。 对于少量的安全关键状态,这样的步骤可能是必要的,但是如果你有很多“机密”信息需要处理,那么无法使用正常的字符串和字符串处理将是一个很大的麻烦。

您需要直接访问内存。

你真的无法用String做到这一点,因为你没有对字符串的可靠访问,并且不知道它是否被某个地方实例化,或者是否创建了一个你不知道的对象。

如果你真的需要这个,你必须做类似的事情

 public class SecureString implements CharSequence { char[] data; public void wipe() { for(int i = 0; i < data.length; i++) data[i] = '.'; // random char } } 

话虽这么说,如果你担心数据仍然在内存中,你必须意识到如果它曾经在内存中存在,那么攻击者可能已经得到了它。 实际保护自己的唯一方法是将核心转储刷新到日志文件中。

关于优化器,我非常怀疑它会优化操作。 如果你真的需要它,你可以做这样的事情:

 public int wipe() { // wipe the array to a random value java.util.Arrays.fill(data, (char)(rand.nextInt(60000)); // compute hash to force optimizer to do the wipe int hash = 0; for(int i = 0; i < data.length; i++) { hash = hash * 31 + (int)data[i]; } return hash; } 

这将强制编译器执行擦除。 它使运行时间大约是运行时间的两倍,但它的运行速度非常快,并且不会增加复杂程度。

使用“不安全”方法将数据存储在堆外。 然后,您可以在完成后将其置零,并确保它不会被JVM推送到堆中。

这是关于不安全的好post:

http://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

如果您要使用String,那么我认为您担心它会出现在内存转储中。 我建议在键字符上使用String.replace() ,以便在运行时使用String时,它会更改,然后在使用后超出范围,并且不会在内存转储中正确显示。 但是,我强烈建议您不要对敏感数据使用字符串。