使用System.console()读取密码

Ale*_*lex 4 java

java.io.Console类的javadoc中有一个安全说明:

安全说明:如果应用程序需要读取密码或其他安全数据,则应使用readPassword()或readPassword(String,Object ...)并在处理后手动将返回的字符数组归零,以最大限度地缩短内存中敏感数据的生命周期.

 Console cons;
 char[] passwd;
 if ((cons = System.console()) != null &&
     (passwd = cons.readPassword("[%s]", "Password:")) != null) {
     ...
     java.util.Arrays.fill(passwd, ' ');
 }
Run Code Online (Sandbox Code Playgroud)

我不明白为什么你需要这么激烈的措施?当读取密码的方法弹出堆栈时,passwd局部变量引用的数组对象将有资格进行垃圾回收.没有人(甚至是攻击者)可以获得对该数组的引用,假设数组没有转义方法范围.

那么为什么你需要修改数组(删除密码),当你知道一旦方法弹出堆栈就有资格获得GC?他们说:

最小化内存中敏感数据的生命周期

但对我来说,这种编程风格似乎相当......绝望.

Boh*_*rdt 11

仅仅因为对象有资格进行垃圾收集并不意味着它会立即被垃圾收集.在实际执行垃圾收集之前的那段时间内,攻击者可能会获得堆内存转储,例如,他们可以从中检索密码.

通过将其归零,机会窗口被最小化.

编辑:实践实验:

创建以下Java程序:

public class Main {
    private static void readPassword() {
        char[] password = System.console().readPassword();
    }

    public static void main(String[] args) throws Exception {
        readPassword();
        Thread.sleep(1000 * 3600);
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. javac Main.java
  2. java Main
  3. 输入密码(例如topsecret),按回车键

接下来打开另一个终端,找出进程的PID(假设它是1000)并使用jmap创建堆转储:

jmap -dump:format=b,file=dump.bin 1000
Run Code Online (Sandbox Code Playgroud)

安装并打开VisualVM Profiler,转到文件/加载并选择刚刚创建的堆转储.

接下来转到OQL控制台并运行以下查询:

select a from char[] a where a.length == 9 && a[0] == 't'
Run Code Online (Sandbox Code Playgroud)

正如您在附带的屏幕截图中看到的那样,找到了包含"topsecret"的数组,即使在进行堆转储时,也没有对该数组的可访问引用.这样就证明了对象即使在本地引用它们也会留在堆上直到收集垃圾.

VisualVM的

现在,如果我将数组清空并再次尝试整个过程,则将找不到包含密码的数组.


Tod*_*odd 8

如果我是攻击者并且在该阵列被垃圾收集之前可能导致堆转储发生,我可以检查其内容.无法保证GC将"很快"(或根本不会)运行,并且如果您有一些非常敏感的东西,您可能不希望它出现在堆转储中的磁盘上,无论多么轻微.