在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
当前回答
简单明了的答案是因为char[]是可变的,而String对象不是可变的。
Java中的字符串是不可变的对象。这就是为什么它们一旦创建就不能被修改,因此从内存中删除它们的内容的唯一方法就是垃圾收集。只有当对象释放的内存可以被覆盖时,数据才会消失。
现在,Java中的垃圾收集不会在任何保证的时间间隔内发生。因此,字符串可以在内存中保存很长时间,如果在此期间进程崩溃,字符串的内容可能会在内存转储或某些日志中结束。
使用字符数组,您可以读取密码,尽快完成处理,然后立即更改内容。
其他回答
编辑:经过一年的安全研究后,我回到了这个答案,我意识到这意味着你可能会比较明文密码。请不要。使用一个安全的单向散列和一个合理的迭代次数。考虑使用图书馆:这东西很难弄对!
原始答案:String.equals()使用短路求值,因此容易受到定时攻击,这一事实如何?这可能不太可能,但理论上可以对密码比较计时,以确定正确的字符序列。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// Quits here if Strings are different lengths.
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// Quits here at first different character.
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
关于定时攻击的更多资源:
定时攻击的教训关于信息安全堆栈交换上定时攻击的讨论当然,定时攻击维基百科页面
字符串是不可变的。这意味着一旦创建了String,如果另一个进程可以转储内存,那么在垃圾收集开始之前,就没有办法(除了反射之外)清除数据。
使用数组,您可以在完成后显式擦除数据。您可以用任何您喜欢的内容覆盖数组,并且密码不会出现在系统中的任何位置,甚至在垃圾收集之前。
是的,这是一个安全问题,但即使使用char[]也只会减少攻击者的机会窗口,而且只针对这种特定类型的攻击。
如注释中所述,垃圾收集器移动的数组可能会在内存中留下数据的零散副本。我认为这是特定于实现的——垃圾收集器可以在运行时清除所有内存,以避免这种情况。即使这样,char[]仍有一段时间包含实际字符作为攻击窗口。
简单明了的答案是因为char[]是可变的,而String对象不是可变的。
Java中的字符串是不可变的对象。这就是为什么它们一旦创建就不能被修改,因此从内存中删除它们的内容的唯一方法就是垃圾收集。只有当对象释放的内存可以被覆盖时,数据才会消失。
现在,Java中的垃圾收集不会在任何保证的时间间隔内发生。因此,字符串可以在内存中保存很长时间,如果在此期间进程崩溃,字符串的内容可能会在内存转储或某些日志中结束。
使用字符数组,您可以读取密码,尽快完成处理,然后立即更改内容。
除非您在使用后手动清理,否则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循环的一部分被复制,那么前一个副本可能在内存中的某个位置。
这个旧副本不会出现在堆转储中,但如果您可以直接访问进程的原始内存,您可以看到它。通常,您应该避免任何人有这样的访问权限。