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

Ner*_*erd 7 java security

String secret="foo";
WhatILookFor.securelyWipe(secret);
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*n C 7

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

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


对于记录,有几种方法可以更改String的后备阵列的内容.例如,您可以使用反射来捕获对String的后备数组的引用,并覆盖其内容.但是,这涉及到JLS状态具有未指定行为的操作,因此您无法保证优化器不会执行意外操作.


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

  • @ErwinBolwidt - 好吧.最近,我下载了Java 8源代码,我看了一下.我错了.似乎(至少在Java 8中)归零被推迟到JVM尝试分配堆对象的时间点. (2认同)

cor*_*iKa 6

您需要直接访问内存.

您真的无法使用String执行此操作,因为您没有对字符串的可靠访问权限,并且不知道它是否已在某处实习,或者是否创建了您不知道的对象.

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

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

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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