在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
在Swing中,密码字段有一个getPassword()(returns char[])方法,而不是通常的getText()(return String)方法。同样,我也遇到了一个建议,不要使用String来处理密码。
为什么字符串对密码安全构成威胁?使用char[]感觉不方便。
当前回答
答案已经给出了,但我想与大家分享一下我最近在Java标准库中发现的一个问题。尽管他们现在非常小心地将密码字符串替换为随处可见的char[](这当然是一件好事),但在从内存中清除密码时,其他安全关键数据似乎被忽略了。
我正在考虑例如PrivateKey类。考虑一个场景,您将从PKCS#12文件加载一个专用RSA密钥,并使用它执行一些操作。现在,在这种情况下,只要对密钥文件的物理访问受到适当限制,单独嗅探密码对您没有多大帮助。作为一名攻击者,如果您直接获得密钥而不是密码,您的情况会更好。所需的信息可能是多方面的泄漏,核心转储、调试器会话或交换文件只是一些示例。
事实证明,没有任何东西可以让您从内存中清除PrivateKey的私有信息,因为没有API可以让您擦除构成相应信息的字节。
这是一个糟糕的情况,因为本文描述了这种情况如何被潜在利用。
例如,OpenSSL库在释放私钥之前会覆盖关键内存段。由于Java是垃圾收集的,所以我们需要显式方法来清除Java密钥的私有信息并使其无效,这些私有信息将在使用密钥后立即应用。
其他回答
前面的很多答案都很好。还有一点我在假设(如果我错了,请纠正我)。
默认情况下,Java使用UTF-16存储字符串。使用字符数组、char[]数组,有助于使用Unicode、区域字符等。该技术允许所有字符集在存储密码时受到同等尊重,因此不会因字符集混淆而引发某些加密问题。最后,使用字符数组,我们可以将密码数组转换为所需的字符集字符串。
字符数组(char[])可以在使用后通过将每个字符设置为零而不设置为字符串来清除。如果有人能够以某种方式看到内存映像,那么如果使用字符串,他们可以看到纯文本的密码,但是如果使用char[],则在用0清除数据后,密码是安全的。
正如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循环的一部分被复制,那么前一个副本可能在内存中的某个位置。
这个旧副本不会出现在堆转储中,但如果您可以直接访问进程的原始内存,您可以看到它。通常,您应该避免任何人有这样的访问权限。
简单明了的答案是因为char[]是可变的,而String对象不是可变的。
Java中的字符串是不可变的对象。这就是为什么它们一旦创建就不能被修改,因此从内存中删除它们的内容的唯一方法就是垃圾收集。只有当对象释放的内存可以被覆盖时,数据才会消失。
现在,Java中的垃圾收集不会在任何保证的时间间隔内发生。因此,字符串可以在内存中保存很长时间,如果在此期间进程崩溃,字符串的内容可能会在内存转储或某些日志中结束。
使用字符数组,您可以读取密码,尽快完成处理,然后立即更改内容。
字符串是不可变的。这意味着一旦创建了String,如果另一个进程可以转储内存,那么在垃圾收集开始之前,就没有办法(除了反射之外)清除数据。
使用数组,您可以在完成后显式擦除数据。您可以用任何您喜欢的内容覆盖数组,并且密码不会出现在系统中的任何位置,甚至在垃圾收集之前。
是的,这是一个安全问题,但即使使用char[]也只会减少攻击者的机会窗口,而且只针对这种特定类型的攻击。
如注释中所述,垃圾收集器移动的数组可能会在内存中留下数据的零散副本。我认为这是特定于实现的——垃圾收集器可以在运行时清除所有内存,以避免这种情况。即使这样,char[]仍有一段时间包含实际字符作为攻击窗口。