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, ' ');
}
我不明白为什么你需要这么激烈的措施? 当读取密码的方法弹出堆栈时, passwd 本地变量引用的数组对象将有资格进行垃圾回收。假设数组没有逃避方法范围,没有人(甚至是攻击者)可以获得对该数组的引用。
那么为什么你需要修改数组(删除密码),当你知道一旦方法弹出堆栈就有资格获得GC? 他们说:
最小化内存中敏感数据的生命周期
但对我而言,这种编程风格似乎相当......绝望。
答案 0 :(得分: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);
}
}
javac Main.java
java Main
接下来打开另一个终端,找出进程的PID(假设它是1000)并使用jmap创建堆转储:
jmap -dump:format=b,file=dump.bin 1000
安装并打开VisualVM探查器,转到文件/加载并选择刚刚创建的堆转储。
接下来转到OQL控制台并运行以下查询:
select a from char[] a where a.length == 9 && a[0] == 't'
正如您在附带的屏幕截图中看到的那样,找到了包含“topsecret”的数组,即使在进行堆转储时,也没有对该数组的可访问引用。这样就可以证明,即使它们被本地引用,对象仍然会堆在堆上,直到收集垃圾为止。
现在,如果我要将数组清空并再次尝试整个过程,则找不到包含密码的数组。
答案 1 :(得分:8)
如果我是攻击者并且在该阵列被垃圾收集之前可能导致堆转储,我可以检查其内容。无法保证GC会“很快”(或者根本不会)运行,如果你有一些非常敏感的东西,你可能不希望它出现在堆转储中的磁盘上,无论赔率多么微小。
答案 2 :(得分:2)
这个问题让我想起了来自answer
的热门Why is char[] preferred over String for passwords?中的讨论如评论中所述,垃圾收集器移动的数组可能会将数据的杂散副本留在内存中。我相信这是特定于实现的 - GC可以清除所有内存,以避免这种情况。即使它确实存在,仍然有时间char []包含实际字符作为攻击窗口。
答案 3 :(得分:2)
当读取密码的方法从堆栈中弹出时,passwd局部变量引用的数组对象将有资格进行垃圾回收。假设数组没有逃避方法范围,没有人(甚至是攻击者)可以获得对该数组的引用。
唉,这并不是那么简单:Java语言不能保证垃圾收集会在多长时间内发生,并且攻击者有可能在同一时间内读取内存。
最小化内存中敏感数据的生命周期
这个建议显然针对威胁模型,攻击者对堆内存具有间歇性读取访问权限,我们仍然必须尽最大努力使攻击者更难获取(但不一定是不可能)获取敏感数据。
你是否拥有这种威胁模型只是你可以评估的东西,但它并不像初看起来那样荒谬,因为所涉及的各种系统并不一定能保证主存的隐私,例如:
JVM
操作系统
管理程序
硬件
尽管如此,大多数这些攻击都假定一定程度的访问可以实现更严重的攻击,因此无论如何都应该加以防范,在这种情况下,减少内存中敏感数据的生命周期是相当多余的。就个人而言,我不会保护数据,而是将其存储在内存中尽可能短的时间,但是通过使用可信硬件来保护内存的隐私,在物理上防止篡改,由专业人员配置的安全操作系统,以及操作系统级访问限制为实用的,以及任何类型的备份和诊断转储的安全存储。
因此,我认为使用char[]
密码很少有用。顺便提一下,这个观点似乎是由JDBC API的设计者共享的,requires connection passwords to be passed as String
阻止了它们在建立连接后被清除。
尽管如此,可能会出现无法保证主存储器隐私的情况,并且通过尽可能减少漏洞窗口来减轻损害是合适的。您的威胁模型应该回答是否是这种情况。