在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
当前回答
Java中的字符串是不可变的。因此,无论何时创建字符串,它都将保留在内存中,直到被垃圾收集。因此,任何访问内存的人都可以读取字符串的值。
如果字符串的值被修改,那么它将最终创建一个新字符串。因此,原始值和修改后的值都保留在内存中,直到被垃圾收集。
使用字符数组,一旦达到密码的目的,就可以修改或删除数组的内容。在修改数组之后,甚至在垃圾收集开始之前,都不会在内存中找到数组的原始内容。
出于安全考虑,最好将密码存储为字符数组。
其他回答
大小写字符串:
String password = "ill stay in StringPool after Death !!!";
// some long code goes
// ...Now I want to remove traces of password
password = null;
password = "";
// above attempts wil change value of password
// but the actual password can be traced from String pool through memory dump, if not garbage collected
案例CHAR阵列:
char[] passArray = {'p','a','s','s','w','o','r','d'};
// some long code goes
// ...Now I want to remove traces of password
for (int i=0; i<passArray.length;i++){
passArray[i] = 'x';
}
// Now you ACTUALLY DESTROYED traces of password form memory
引用一份官方文档,Java Cryptography Architecture指南介绍了char[]与String密码(关于基于密码的加密,但这更一般地是关于密码):
将密码收集并存储在对象中似乎是合乎逻辑的然而,这里有一个警告:类型String是不可变的,即没有定义允许您更改(覆盖)或清零字符串的内容使用后。此功能使字符串对象不适合存储诸如用户密码之类的安全敏感信息。你应始终在char数组。
Java编程语言4.0版安全编码指南2-2也提到了类似的内容(尽管最初是在日志记录的上下文中):
准则2-2:不记录高度敏感信息一些信息,如社会安全号码(SSN)和密码是高度敏感的。此信息不应保留无论在什么地方,甚至在管理员。例如,不应将其发送到日志文件和它的存在不应该通过搜索来检测。一些瞬态数据可以保存在可变数据结构中,例如char数组,以及使用后立即清除。清除数据结构减少了当对象移入时,在典型的Java运行时系统上的有效性存储器对程序员来说是透明的。本指南还对实施和使用没有数据语义知识的低级库他们正在处理。例如,低级字符串解析库可以记录其工作的文本。应用程序可以解析SSN与图书馆。这会导致SSN管理员可以访问日志文件。
有些人认为,一旦您不再需要密码,就必须覆盖用于存储密码的内存。这缩短了攻击者从系统读取密码的时间窗口,并完全忽略了这样一个事实,即攻击者已经需要足够的访问权限来劫持JVM内存。具有如此多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了,请纠正我)。
使现代化
感谢这些评论,我不得不更新我的答案。显然,在两种情况下,这可以增加(非常)轻微的安全改进,因为它可以减少密码在硬盘上的时间。但我仍然认为这对大多数用例来说都是过度的。
您的目标系统可能配置不正确,或者您必须假设它是错误的,并且您必须对核心转储(如果系统不是由管理员管理的,则可能是有效的)心存疑虑。您的软件必须过于偏执,以防止攻击者使用TrueCrypt(已停产)、VeraCrypt或CipherShed等工具访问硬件时发生数据泄漏。
如果可能,禁用内核转储和交换文件可以解决这两个问题。然而,它们需要管理员权限,可能会减少功能(使用的内存更少),从运行系统中取出RAM仍然是一个值得关注的问题。
除非您在使用后手动清理,否则char数组不会为您提供字符串,我还没有看到有人真正这样做。所以对我来说,char[]和String的偏好有点夸张。
看看这里广泛使用的SpringSecurity库,问问你自己——SpringSecurity的家伙们是无能还是字符密码根本没有意义。当一些讨厌的黑客从你的RAM中获取内存转储时,即使你使用复杂的方法隐藏密码,也要确保他/她会得到所有密码。
然而,Java一直在变化,一些可怕的特性,如Java8的字符串重复数据消除特性,可能会在您不知情的情况下实习字符串对象。但这是一个不同的对话。
正如Jon Skeet所说,除了使用反射,没有其他方法。
然而,如果反射是您的选择,您可以这样做。
public static void main(String[] args) {
System.out.println("please enter a password");
// don't actually do this, this is an example only.
Scanner in = new Scanner(System.in);
String password = in.nextLine();
usePassword(password);
clearString(password);
System.out.println("password: '" + password + "'");
}
private static void usePassword(String password) {
}
private static void clearString(String password) {
try {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] chars = (char[]) value.get(password);
Arrays.fill(chars, '*');
} catch (Exception e) {
throw new AssertionError(e);
}
}
运行时
please enter a password
hello world
password: '***********'
注意:如果String的char[]作为GC循环的一部分被复制,那么前一个副本可能在内存中的某个位置。
这个旧副本不会出现在堆转储中,但如果您可以直接访问进程的原始内存,您可以看到它。通常,您应该避免任何人有这样的访问权限。