在Swing中,密码字段有一个 getPassword()
(返回 char[]
)方法而不是通常的方法 getText()
(返回 String
) 方法。同样,我遇到了一个不使用的建议 String
处理密码。
为什么 String
在密码问题上对安全构成威胁?
使用感觉不方便 char[]
。
在Swing中,密码字段有一个 getPassword()
(返回 char[]
)方法而不是通常的方法 getText()
(返回 String
) 方法。同样,我遇到了一个不使用的建议 String
处理密码。
为什么 String
在密码问题上对安全构成威胁?
使用感觉不方便 char[]
。
字符串是不可变的。这意味着一旦你创造了 String
,如果另一个进程可以转储内存,那就没办法了(除了 反射你以前可以摆脱数据 垃圾收集 踢进去。
使用数组,您可以在完成数据后显式擦除数据。您可以使用您喜欢的任何内容覆盖数组,即使在垃圾回收之前,密码也不会出现在系统的任何位置。
是的,这个 是 安全问题 - 但即使使用 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
引用官方文件, Java密码体系结构指南 这说的 char[]
与 String
密码(关于基于密码的加密,但当然更常见的是关于密码):
在对象中收集和存储密码似乎是合乎逻辑的 类型
java.lang.String
。但是,这里有一点需要注意:Object
的 类型String
是不可变的,即没有定义的方法 允许您更改(覆盖)或清除a的内容String
使用后。这个功能使String
对象不适合 存储安全敏感信息,如用户密码。您 应始终收集并存储安全敏感信息char
数组代替。
Java编程语言4.0版安全编码指南的准则2-2 也说类似的东西(虽然它最初是在日志记录的背景下):
准则2-2:不要记录高度敏感的信息
一些信息,例如社会安全号码(SSN)和 密码,非常敏感。不应保留此信息 超过必要的时间,甚至可以看到的地方 管理员。例如,它不应该发送到日志文件和 它的存在不应该通过搜索检测到。一些短暂的 数据可以保存在可变数据结构中,例如char数组,和 使用后立即清除。清算数据结构已减少 移动对象时,典型Java运行时系统的有效性 内存对程序员透明。
该指南也对实施和使用有影响 没有数据语义知识的低级库 他们正在处理。例如,一个低级字符串解析 库可能会记录它所使用的文本。应用程序可以解析SSN 与图书馆。这会产生SSN的情况 可供具有日志文件访问权限的管理员使用。
字符数组(char[]
)可以在使用后通过将每个字符设置为零而不将字符串设置为清除。如果有人能够以某种方式查看内存映像,如果使用了字符串,他们可以使用纯文本查看密码,但是如果 char[]
使用0清除数据后,密码是安全的。
有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存。这减少了攻击者从系统中读取密码的时间窗口,并完全忽略了攻击者已经需要足够的访问权来劫持JVM内存来执行此操作的事实。具有这么多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了请纠正我)。
更新
感谢我的评论,我必须更新我的答案。显然有两种情况可以增加(非常)次要的安全性改进,因为它减少了密码落在硬盘上的时间。我认为对于大多数用例来说,这样做太过分了。
如果可能,禁用核心转储和交换文件将解决这两个问题。但是,它们需要管理员权限并且可能会降低功能(使用的内存更少),并且从正在运行的系统中提取RAM仍然是一个有效的问题。
getPassword()
JPasswordField的方法,它返回一个char []并且不推荐使用 getText()
以明文形式返回密码的方法,说明安全原因。 的toString() 在日志文件或控制台中始终存在打印纯文本的风险,但如果使用Array,则不会打印数组的内容而是打印其内存位置。
String strPwd = "passwd";
char[] charPwd = new char[]{'p','a','s','s','w','d'};
System.out.println("String password: " + strPwd );
System.out.println("Character password: " + charPwd );
字符串密码:passwd
字符密码:[C @ 110b2345
最后的想法: 虽然使用char []还不够,但您需要擦除内容才能更安全。我还建议使用散列或加密密码而不是纯文本,并在验证完成后立即从内存中清除它。
我不认为这是一个有效的建议,但是,我至少可以猜测原因。
我认为动机是要确保您可以在使用后及时清除内存中的所有密码。有了 char[]
你可以使用空格或某些东西来覆盖数组的每个元素。您无法编辑a的内部值 String
那样。
但仅此一点并不是一个好的答案;为什么不只是确保参考 char[]
要么 String
不逃避?然后没有安全问题。但事情就是这样 String
对象可以 intern()
在理论上并在恒定的池中保持活力。我想用 char[]
禁止这种可能性。
已经给出了答案,但我想分享一下我最近在Java标准库中发现的问题。虽然他们现在非常小心地用更换密码字符串 char[]
在任何地方(当然这都是好事),其他安全关键数据在从内存中清除时似乎被忽视了。
我在考虑例如该 专用密钥 类。考虑一种情况,您可以从PKCS#12文件加载私有RSA密钥,使用它来执行某些操作。现在,在这种情况下,只要对密钥文件的物理访问受到适当限制,单独嗅探密码对您没有多大帮助。作为攻击者,如果您直接获得密钥而不是密码,那将会好得多。所需的信息可以泄漏,核心转储,调试器会话或交换文件只是一些例子。
事实证明,没有任何东西可以让你清除a的私人信息 PrivateKey
从内存中,因为没有API可以让你擦除形成相应信息的字节。
这是一个糟糕的情况 纸 描述了这种情况如何被潜在地利用。
例如,OpenSSL库在释放私钥之前覆盖关键内存部分。由于Java是垃圾收集的,我们需要显式方法来擦除和使Java密钥的私有信息无效,这些密钥将在使用密钥后立即应用。
正如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循环的一部分,则前一个副本可能位于内存中的某个位置。
此旧副本不会出现在堆转储中,但如果您可以直接访问该进程的原始内存,则可以看到它。一般来说,你应该避免任何人有这种访问。
编辑: 在经过一年的安全研究之后回到这个答案,我意识到它实际上比较明确地比较了明文密码。请不要。 使用带有salt和合理迭代次数的安全单向散列。考虑使用库:这个东西很难做对!
原始答案: 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;
}
有关定时攻击的更多资源:
这些都是人们应该选择的原因 炭[] 数组而不是 串 用于密码。
1。 因为如果你将密码作为纯文本存储在字符串中,字符串是不可变的,它将在内存中可用,直到垃圾收集器清除它,并且因为String在字符串池中用于可重用性,所以很有可能它会长时间保留在内存中,这构成了安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,您应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char [],你仍然可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
2。 Java本身建议使用JPasswordField的getPassword()方法,它返回一个char []和不推荐使用的getText()方法,它以明文形式返回密码,说明安全原因。很好地遵循Java团队的建议并坚持标准而不是反对它。
3。 使用String时,始终存在在日志文件或控制台中打印纯文本的风险,但如果使用Array,则不会打印数组的内容,而是打印其内存位置。虽然不是一个真正的原因,但仍然有意义。
String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);
String password: Unknown
Character password: [C@110b053
参考来自: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html 希望这可以帮助。