getPassword()
方法(返回char[]
),而不是通常的getText()
方法(返回String
)。同样,我发现建议不要使用String
来处理密码。为什么
String
在涉及密码时会对安全构成威胁?
使用char[]
感觉很不方便。字符串是不可变的。这意味着一旦创建了String
,如果另一个进程可以转储内存,除非使用反射(reflection),否则在垃圾回收开始之前,你无法摆脱数据。
使用数组后,您可以在完成后显式清除数据。您可以用任何东西覆盖数组,并且密码将不会存在于系统中的任何地方,即使在垃圾回收之前。
因此,是的,这确实是一个安全问题 - 但即使仅使用char[]
,也仅仅减少了攻击者的机会窗口,而且仅适用于此特定类型的攻击。
正如评论中所述,垃圾收集器移动数组可能会在内存中留下杂散的数据副本。我认为这取决于具体实现 - 垃圾回收器可能会在进行时清除所有内存,以避免此类情况。即使如此,在char[]
包含实际字符作为攻击窗口的时间内,仍然存在攻击的可能。
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[]
,其余部分为十六进制哈希码。 - Konrad Garus密码
类类型。这样不仅更清晰易懂,而且更难意外传递到其他地方。 - user1804599char[]
和String
密码(关于基于密码的加密,但这当然更普遍地涉及密码)有以下说法:
收集并存储密码在类型为
java.lang.String
的对象中似乎很合理。然而,这里有一个警告:类型为String
的对象是不可变的,即没有定义允许您在使用后更改(覆盖)或清除String
内容的方法。这个特性使得String
对象不适合存储诸如用户密码之类的安全敏感信息。您应该始终将安全敏感信息收集和存储在char
数组中。
Java编程语言安全编码准则第4.0版的2-2指南也提到了类似的内容(尽管它最初是在日志记录的上下文中):
指南2-2:不记录高度敏感信息
某些信息,例如社会安全号码(SSN)和密码等是非常敏感的。这些信息不应该保存的时间比必要的更长,并且不能被管理员甚至看到。例如,它不应该被发送到日志文件中,并且其存在不应通过搜索可检测。一些短暂的数据可以保存在可变数据结构中,例如字符数组,并在使用后立即清除。由于对象在内存中透明地移动,因此在典型的Java运��时系统上,清除数据结构的效果降低。
这个指南还涉及到不具有数据语义知识的低级别库的实现和使用。例如,一个低级别的字符串解析库可能会记录它所处理的文本。一个应用程序可能使用该库解析SSN。这将创建一个情况,其中可以通过访问日志文件的管理员获得SSNs。
字符数组(char[]
)可以通过将每个字符设置为零来清除使用后的数据,但字符串则不行。如果有人能够以某种方式查看内存图像,而使用字符串时密码会以明文形式显示,但如果使用char[]
,在用0清除数据后,密码就是安全的。
intern()
的原因。但你说得对,String
实例一开始就存在(直到被回收),之后将其转换为char[]
数组并不会改变这一点。 - Holgernew String()
或者 StringBuilder.toString()
。我管理有大量字符串常量的应用程序,结果导致了很多永久代空间增长,直到1.7版本。 - avgvstvsintern()
可能会导致等效的字符串分配到永久代空间。如果没有与该对象共享相同内容的字面字符串,则后者可能会被垃圾回收(GC)。 - Holger如果可能,禁用核心转储和交换文件将解决这两个问题。但是,它们需要管理员权限,可能会降低功能(可用内存较少),从运行中的系统中提取 RAM 仍然是一个有效的问题。
我认为这不是一个有效的建议,但是我至少可以猜测原因。
我认为动机是希望确保在使用密码后可以确定和及时地从内存中删除所有密码痕迹。使用 char[]
可以确保用空白或其他内容覆盖数组的每个元素。你不能通过这种方式编辑 String
的内部值。
但单凭这一点还不足以解决问题; 为什么不只是确保 char[]
或 String
的引用没有泄露? 那么就没有安全问题了。但问题在于,String
对象在理论上可以被 intern()
,并保留在常量池中。我想使用 char[]
就可以避免这种可能性。
char[]
替换密码字符串(这当然是一件好事),但是其他安全关键数据似乎被忽略了,例如PrivateKey类。考虑从PKCS#12文件加载私有RSA密钥并使用它执行某些操作的情况。在这种情况下,仅仅嗅探密码并不能帮助您太多,只要物理访问密钥文件受到适当限制。作为攻击者,如果直接获得密钥而不是密码,将会更加有利。所需信息可以通过各种方式泄漏,如核心转储、调试器会话或交换文件等。
事实证明,目前没有任何API可以让您清除来自PrivateKey
的私有信息,因为没有API可以擦拭形成相应信息的字节。
这是一个糟糕的情况,因为这篇论文描述了这种情况可能被潜在地利用。例如,OpenSSL库会在释放私钥之前覆盖关键内存部分。由于Java是垃圾收集的,我们需要明确的方法来擦除和失效Java密钥的私有信息,而这些方法必须在使用密钥后立即应用。
char[]
,因为我正在看 JDK 9 中添加的这个漂亮的新ConnectionBuilder
类,它仍然有password(String)
,根本没有传递char[]
的选项。似乎在Java中有很多"说一套做一套"的情况。 - Hakanaipublic 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 数组不会提供任何优势,而我没有见过有人真正这样做。因此,在我看来,char[] 与 String 的偏好有些夸大了。
看看广泛使用的 Spring Security 库(链接),问问自己——Spring Security 的人员是否无能或 char[] 密码是否毫无意义。当一些恶意黑客获取您 RAM 的内存转储时,请确保他们即使使用复杂的方法来隐藏密码,也将获得所有密码。
然而,Java 始终在变化,一些可怕的特性,如Java 8 的字符串去重复功能可能会在您不知情的情况下对字符串对象进行内部化操作。但那是另一个话题。
BCrypt
和BCryptPasswordEncoder
之间的区别时。即使在同时初始提交中,他们采取了不一致的方法:对于Bcrypt
采用String
,而对于调用Bcrypt
的BCryptPasswordEncoder
则采用CharSequence
。 - Brent Bradburn编辑:回到这个答案一年后的安全研究中,我意识到它不幸地暗示你可能会真正比较明文密码。请不要这样做。使用带有盐和合理迭代次数的安全单向哈希。考虑使用库:这东西很难搞定!
原始回答: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;
}
关于时序攻击的更多资源:
Arrays.equals
来比较 char[]
的诱惑与使用 String.equals
相同。如果有人在意的话,可以使用专门的密钥类封装实际密码并处理相关问题——哦等等,真正的安全包 已经 有了专门的密钥类,这个 Q&A 只是关于它们之外的一个 习惯,比如说,在 JPasswordField
中使用 char[]
而不是 String
(实际算法中仍然使用 byte[]
)。 - Holgersleep(secureRandom.nextInt())
的操作,这不仅消除了时序攻击的可能性,还可以抵御暴力破解尝试。 - Holger
char[]
位置的密码可以切断这种攻击方式,而使用String
时则不可能实现这一点。 - Ted Hopp