提问人:Ahamed 提问时间:1/16/2012 最后编辑:Rakete1111Ahamed 更新时间:1/20/2023 访问量:509774
为什么密码的 char[] 优先于 String?
Why is char[] preferred over String for passwords?
问:
在 Swing 中,密码字段有一个 (returns ) 方法,而不是通常的 (returns ) 方法。同样,我遇到了一个建议,不要用于处理密码。getPassword()
char[]
getText()
String
String
为什么在密码方面会对安全构成威胁?
使用起来感觉不方便。String
char[]
答:
字符串是不可变的。这意味着一旦你创建了 ,如果另一个进程可以转储内存,你就没有办法(除了反射)在垃圾回收开始之前删除数据。String
使用数组,您可以在完成数据处理后显式擦除数据。你可以用任何你喜欢的东西覆盖数组,并且密码不会出现在系统中的任何位置,甚至在垃圾回收之前。
所以,是的,这是一个安全问题 - 但即使使用也只会减少攻击者的机会窗口,而且它仅适用于这种特定类型的攻击。char[]
如注释中所述,垃圾回收器移动的数组可能会在内存中留下数据的杂散副本。我相信这是特定于实现的 - 垃圾收集器可能会在进行时清除所有内存,以避免这种事情。即使确实如此,仍然有一段时间包含实际字符作为攻击窗口。char[]
评论
char[]
String
字符数组 () 可以在使用后通过将每个字符设置为零和字符串不来清除。如果有人能以某种方式看到内存图像,那么如果使用字符串,他们可以看到纯文本的密码,但如果使用字符串,则在清除带有 0 的数据后,密码是安全的。char[]
char[]
评论
HttpServletRequest
intern()
String
char[]
new String()
StringBuilder.toString()
intern()
我不认为这是一个有效的建议,但是,我至少可以猜到原因。
我认为动机是希望确保您可以在使用密码后立即确定地删除内存中的所有密码痕迹。使用 a,您可以肯定地用空白或其他东西覆盖数组的每个元素。您无法以这种方式编辑 a 的内部值。char[]
String
但仅凭这一点并不是一个好的答案;为什么不确保对 or 的引用不会转义呢?那么就没有安全问题了。但问题是,从理论上讲,物体可以被编辑,并在恒定的水池中保持活力。我想使用禁止这种可能性。char[]
String
String
intern()
char[]
评论
char[]
char[]
有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存。这减少了攻击者从系统中读取密码的时间窗口,并完全忽略了攻击者已经需要足够的访问权限来劫持 JVM 内存才能执行此操作的事实。拥有如此多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了,请纠正我)。
更新
感谢评论,我必须更新我的答案。显然,在两种情况下,这可以增加(非常)小的安全性改进,因为它减少了密码登陆硬盘驱动器的时间。不过,我认为这对于大多数用例来说是矫枉过正的。
- 您的目标系统可能配置不当,或者您必须假设它配置不当,并且您必须对核心转储感到偏执(如果系统不是由管理员管理,则可能是有效的)。
- 你的软件必须过于偏执,以防止攻击者使用TrueCrypt(已停产)、VeraCrypt或CipherShed等方式访问硬件时发生数据泄露。
如果可能,禁用核心转储和交换文件将解决这两个问题。但是,它们需要管理员权限,并且可能会减少功能(要使用的内存更少),并且从正在运行的系统中提取 RAM 仍然是一个有效的问题。
评论
虽然这里的其他建议似乎有效,但还有另一个很好的理由。使用普通版,您更有可能意外地将密码打印到日志、监视器或其他不安全的地方。 不那么容易受到攻击。String
char[]
考虑一下:
public static void main(String[] args) {
Object pw = "Password";
System.out.println("String: " + pw);
pw = "Password".toCharArray();
System.out.println("Array: " + pw);
}
指纹:
String: Password
Array: [C@5829428e
评论
toString
classname@hashcode
[C
char[]
Password
引用官方文档,Java Cryptography Architecture 指南是这样说的。 密码(关于基于密码的加密,但这当然更普遍地是关于密码的):char[]
String
在对象中收集和存储密码似乎是合乎逻辑的 的类型。但是,这里有一个警告: type 是不可变的,即没有定义 允许您更改(覆盖)或将使用后的内容归零。此功能使对象不适合 存储安全敏感信息,例如用户密码。你 应始终在数组中收集和存储安全敏感信息。
java.lang.String
Object
String
String
String
char
Java 编程语言 4.0 版安全编码指南的准则 2-2 也说了类似的话(尽管它最初是在日志记录的上下文中):
准则 2-2:不记录高度敏感的信息
一些信息,例如社会安全号码 (SSN) 和 密码,是高度敏感的。此信息不应保留 时间超过必要的时间,也超过可能看到的地方,即使 管理员。例如,不应将其发送到日志文件和 它的存在不应该通过搜索来检测。一些瞬态的 数据可以保存在可变的数据结构中,例如 char 数组,以及 使用后立即清除。清除数据结构已减少 在对象移入时在典型 Java 运行时系统上的有效性 内存对程序员透明。
该指南还对实施和使用 不具备数据语义知识的低级库 他们正在处理。例如,低级字符串解析 库可以记录它所处理的文本。应用程序可以解析 SSN 与图书馆。这会产生 SSN 处于 可供有权访问日志文件的管理员使用。
评论
String
char[]
String
At which point
答案已经给出了,但我想分享一下我最近在 Java 标准库中发现的一个问题。虽然他们现在非常小心地将密码字符串替换为任何地方(这当然是一件好事),但在从内存中清除其他安全关键数据时,似乎忽略了这些数据。char[]
我正在考虑例如PrivateKey类。请考虑这样一种情况:您将从 PKCS#12 文件加载私钥 RSA 密钥,并使用它来执行某些操作。现在在这种情况下,只要适当限制对密钥文件的物理访问,仅嗅探密码对您没有多大帮助。作为攻击者,如果您直接获取密钥而不是密码,您的处境会好得多。所需的信息可以是泄漏歧管、核心转储、调试器会话或交换文件只是一些示例。
事实证明,没有什么可以让你从内存中清除 a 的私人信息,因为没有 API 可以让你擦除构成相应信息的字节。PrivateKey
这是一个糟糕的情况,因为本文描述了这种情况如何被潜在地利用。
例如,OpenSSL 库在释放私钥之前会覆盖关键内存部分。由于 Java 是垃圾回收的,因此我们需要显式方法来擦除 Java 密钥的私有信息并使之失效,这些密钥将在使用密钥后立即应用。
评论
PrivateKey
PKCS11
KeychainStore
PrivateKey
WINDOWS-MY
char[]
ConnectionBuilder
password(String)
char[]
char 数组与字符串相比没有任何内容,除非您在使用后手动清理它,而且我还没有看到有人真正这样做。所以对我来说,char[] 与 String 的偏好有点夸张。
看看这里广泛使用的Spring Security库,问问自己 - Spring Security的人是无能的,还是char[]密码没有多大意义。当一些讨厌的黑客抓住你的RAM的内存转储时,即使你使用复杂的方法来隐藏它们,也要确保他/她会得到所有的密码。
然而,Java 一直在变化,一些可怕的功能,如 Java 8 的字符串重复数据删除功能,可能会在您不知情的情况下实习 String 对象。但那是另一回事。
评论
CharSequence
for(调用 )。BCrypt
BCryptPasswordEncoder
String
Bcrypt
BCryptPasswordEncoder
Bcrypt
编辑:经过一年的安全研究,回到这个答案,我意识到它提出了一个相当不幸的暗示,即您实际上会比较明文密码。请不要。使用带有盐和合理迭代次数的安全单向哈希。考虑使用图书馆:这些东西很难正确!
原答案: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;
}
有关定时攻击的更多资源:
- 定时攻击的教训
- 关于对 Information Security Stack Exchange 进行定时攻击的讨论
- 当然,还有定时攻击维基百科页面
评论
Arrays.equals
char[]
String.equals
JPasswordField
char[]
String
byte[]
sleep(secureRandom.nextInt())
正如 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: '***********'
注意:如果字符串的 char[] 已作为 GC 循环的一部分被复制,则前一个副本可能在内存中的某个位置。
这个旧副本不会出现在堆转储中,但如果您可以直接访问进程的原始内存,则可以看到它。一般来说,您应该避免任何人拥有此类访问权限。
评论
'***********'
Scanner
System.console().readPassword()
usePassword
字符串是不可变的,一旦创建就无法更改。将密码创建为字符串将在堆或字符串池上留下对密码的杂散引用。现在,如果有人对 Java 进程进行堆转储并仔细扫描,他可能会猜出密码。当然,这些未使用的字符串将被垃圾回收,但这取决于 GC 何时启动。
另一方面,char[] 是可变的,一旦身份验证完成,你可以用任何字符覆盖它们,比如所有 M 或反斜杠。现在,即使有人进行堆转储,他也可能无法获取当前未使用的密码。从某种意义上说,这为您提供了更多的控制权,例如自己清除对象内容,而不是等待 GC 执行此操作。
评论
strings
String
Strings
String.intern()
constant_pool
constant_pool
简短而直接的答案是,因为它是可变的,而对象不是。char[]
String
Strings
在 Java 中是不可变的对象。这就是为什么它们一旦创建就无法修改的原因,因此从内存中删除其内容的唯一方法是将它们垃圾回收。只有当对象释放的内存可以被覆盖时,数据才会消失。
现在,Java 中的垃圾回收不会以任何保证的时间间隔发生。因此,可以在内存中保留很长时间,如果进程在此期间崩溃,则字符串的内容可能最终出现在内存转储或某些日志中。String
使用字符数组,您可以读取密码,尽快完成使用,然后立即更改内容。
评论
Java 中的字符串是不可变的。因此,每当创建字符串时,它都会保留在内存中,直到被垃圾回收。因此,任何有权访问内存的人都可以读取字符串的值。
如果字符串的值被修改,那么它最终将创建一个新字符串。因此,原始值和修改后的值都会保留在内存中,直到它被垃圾回收。
使用字符数组,一旦达到密码的目的,就可以修改或删除数组的内容。数组的原始内容在修改后,甚至在垃圾回收开始之前,都不会在内存中找到。
出于安全考虑,最好将密码存储为字符数组。
字符串是不可变的,它进入字符串池。一旦写入,就无法覆盖。
char[]
是一个数组,一旦你使用了密码,你就应该覆盖它,这是应该做的:
char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
allowUser = true;
cleanPassword(passw);
cleanPassword(dbPassword);
passw = null;
}
private static void cleanPassword (char[] pass) {
Arrays.fill(pass, '0');
}
攻击者可能使用它的一种情况是故障转储,当 JVM 崩溃并生成内存转储时,您将能够看到密码。
这不一定是恶意的外部攻击者。这可能是出于监视目的而有权访问服务器的支持用户。他/她可以窥视崩溃转储并找到密码。
评论
request.getPassword()
ch = '0'
改变局部变量 ;它对数组没有影响。无论如何,你的例子是没有意义的,你从你调用的字符串实例开始,创建一个新数组,即使你正确地覆盖了新数组,它也不会改变字符串实例,所以它比只使用字符串实例没有优势。ch
toCharArray()
Arrays.fill(pass, '0');
关于是否应该使用 String 或使用 Char[] 来实现此目的是值得商榷的,因为两者都有其优点和缺点。这取决于用户需要什么。
由于 Java 中的字符串是不可变的,因此每当有人试图操作您的字符串时,它都会创建一个新对象,而现有的字符串不受影响。这可以看作是将密码存储为 String 的一个优势,但即使在使用后,该对象仍保留在内存中。因此,如果有人以某种方式获得了对象的内存位置,则该人可以轻松跟踪存储在该位置的密码。
Char[] 是可变的,但它的优点是,在使用它之后,程序员可以显式清理数组或覆盖值。因此,当它被使用完毕时,它会被清理干净,没有人会知道你存储的信息。
基于上述情况,人们可以了解是使用 String 还是使用 Char[] 来满足他们的要求。
案例字符串:
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 ARRAY:
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 使用 UTF-16 来存储字符串。使用字符数组、char[] 数组,便于使用 Unicode、区域字符等。这种技术允许在存储密码时平等地尊重所有字符集,并且从此不会因字符集混淆而引发某些加密问题。最后,使用字符数组,我们可以将密码数组转换为我们想要的字符集字符串。
评论