Jon*_*eet 4164
字符串是不可变的.这意味着一旦你创建了String,如果另一个进程可以转储内存,那么除了反射之外没有办法可以在垃圾收集开始之前摆脱数据.
使用数组,您可以在完成数据后显式擦除数据.您可以使用您喜欢的任何内容覆盖数组,并且即使在垃圾回收之前,密码也不会出现在系统的任何位置.
所以,是的,这是一个安全问题 - 但即使char[]只是减少攻击者的机会窗口,它只适用于这种特定类型的攻击.
正如评论中所指出的那样,垃圾收集器移动的数组可能会将数据的杂散副本留在内存中.我相信这是特定于实现的 - 垃圾收集器可以清除所有内存,以避免这种情况.即使它确实存在,仍然存在char[]将实际角色包含为攻击窗口的时间.
Kon*_*rus 1187
虽然这里的其他建议似乎有效,但还有另一个好的理由.简单地说,String你不小心将密码打印到日志,监视器或其他一些不安全的地方.char[]不那么脆弱了.
考虑一下:
public static void main(String[] args) {
Object pw = "Password";
System.out.println("String: " + pw);
pw = "Password".toCharArray();
System.out.println("Array: " + pw);
}
Run Code Online (Sandbox Code Playgroud)
打印:
String: Password
Array: [C@5829428e
Run Code Online (Sandbox Code Playgroud)
Bru*_*uno 668
引用官方文档时,Java Cryptography Architecture指南说明了这char[]与String密码(关于基于密码的加密,但当然更常见的是关于密码):
在类型的对象中收集和存储密码似乎是合乎逻辑的
java.lang.String.但是,这里有一点需要注意:Object类型的sString是不可变的,即没有定义的方法允许你改变(覆盖)或清零String后使用的内容.此功能使String对象不适合存储安全敏感信息,如用户密码.您应该始终在char数组中收集和存储安全敏感信息 .
Java编程语言安全编码指南的准则2-2,版本4.0也说了类似的东西(虽然它最初是在日志记录的上下文中):
准则2-2:不要记录高度敏感的信息
某些信息(如社会安全号码(SSN)和密码)非常敏感.即使是管理员,也不应将此信息保存的时间超过必要的时间,也不应保存在何处.例如,它不应该发送到日志文件,并且不应通过搜索检测到它的存在.一些瞬态数据可以保存在可变数据结构中,例如char数组,并在使用后立即清除.清除数据结构降低了典型Java运行时系统的有效性,因为对象在内存中透明地移动到程序员.
本指南还对低级库的实现和使用有影响,这些低级库没有他们正在处理的数据的语义知识.例如,低级字符串解析库可以记录它所处理的文本.应用程序可以使用库解析SSN.这会产生一种情况,即管理员可以使用SSN访问日志文件.
ale*_*phx 343
char[]通过将每个字符设置为零而不将字符串设置为零,可以在使用后清除字符数组().如果某人可以以某种方式查看内存映像,如果使用字符串,他们可以以纯文本形式查看密码,但如果char[]使用,则在使用0清除数据后,密码是安全的.
jos*_*efx 214
有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存.这减少了攻击者从系统中读取密码的时间窗口,并完全忽略了攻击者已经需要足够的访问权来劫持JVM内存来执行此操作的事实.具有这么多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了请纠正我).
更新
感谢我的评论,我必须更新我的答案.显然有两种情况可以增加(非常)次要的安全性改进,因为它减少了密码落在硬盘上的时间.我仍然认为对大多数用例来说都是过度杀伤力.
如果可能,禁用核心转储和交换文件将解决这两个问题.但是,它们需要管理员权限并且可能会降低功能(使用的内存更少),并且从正在运行的系统中提取RAM仍然是一个有效的问题.
Sea*_*wen 85
我不认为这是一个有效的建议,但是,我至少可以猜测原因.
我认为,动机是要确保您可以在使用后及时清除内存中的所有密码.使用a,char[]您可以用空白或某些东西覆盖数组的每个元素.您无法编辑该方式的内部值String.
但仅此一点并不是一个好的答案; 为什么不只是确保参考char[]或String不逃避?然后没有安全问题.但事实是String物体可以intern()在理论上编辑并在恒定池中保持活力.我想使用char[]禁止这种可能性.
emb*_*oss 65
已经给出了答案,但我想分享一下我最近在Java标准库中发现的问题.虽然他们现在非常注意char[]随处更换密码字符串(当然这是一件好事),但其他安全关键数据在从内存中清除时似乎被忽略了.
我在考虑例如PrivateKey类.考虑一种情况,您将从PKCS#12文件加载私有RSA密钥,使用它来执行某些操作.现在,在这种情况下,只要正确限制对密钥文件的物理访问,单独嗅探密码对您没有多大帮助.作为攻击者,如果您直接获取密钥而不是密码,那将会好得多.所需的信息可以泄漏,核心转储,调试器会话或交换文件只是一些例子.
事实证明,没有任何东西可以让你清除PrivateKey内存中的私有信息,因为没有API可以让你擦除形成相应信息的字节.
这是一个糟糕的情况,因为本文描述了这种情况如何可能被利用.
例如,OpenSSL库会在释放私钥之前覆盖关键内存部分.由于Java是垃圾收集的,我们需要显式方法来擦除和使Java密钥的私有信息无效,这些密钥将在使用密钥后立即应用.
Pet*_*rey 50
正如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);
}
}
Run Code Online (Sandbox Code Playgroud)
什么时候跑
please enter a password
hello world
password: '***********'
Run Code Online (Sandbox Code Playgroud)
注意:如果String的char []已被复制为GC循环的一部分,则前一个副本可能在内存中的某个位置.
此旧副本不会出现在堆转储中,但如果您可以直接访问该进程的原始内存,则可以看到它.一般来说,你应该避免任何人有这种访问.
Hum*_*ing 43
这些都是原因,应该为密码选择char []数组而不是String.
1. 由于字符串在Java中是不可变的,如果将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它,并且因为字符串在字符串池中用于可重用性,所以它很可能会在长时间留在记忆中,这会带来安全威胁.
由于任何有权访问内存转储的人都可以以明文形式找到密码,因此这应该始终使用加密密码而不是纯文本.由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果使用char [],您仍然可以将所有元素设置为空或零.因此,在字符数组中存储密码可以明显降低窃取密码的安全风险.
2. Java本身建议使用JPasswordField的getPassword()方法,该方法返回char [],而不是弃用的getText()方法,该方法以明文形式返回密码,说明安全原因.遵循Java团队的建议并坚持标准而不是反对它们是很好的.
3. 使用String时,始终存在在日志文件或控制台中打印纯文本的风险,但如果使用数组,则不会打印数组的内容,而是打印其内存位置.虽然不是一个真正的原因,但它仍然有意义.
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
Run Code Online (Sandbox Code Playgroud)
参考此博客.我希望这有帮助.
Gra*_*ory 41
编辑:经过一年的安全研究后回到这个答案,我意识到它实际上比较明确地比较了明文密码.请不要.使用带有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;
}
Run Code Online (Sandbox Code Playgroud)
有关定时攻击的更多资源:
Ole*_*eev 34
除非你在使用后手动清理它,否则char数组没有给你vs String,我没有看到任何人真正这样做.所以对我来说char [] vs String的偏好有点夸张.
看看这里广泛使用的 Spring Security库,并问自己 - Spring Security人员不称职或char []密码只是没有多大意义.当一些讨厌的黑客抓住你的RAM的内存转储时,即使你使用复杂的方法隐藏它们,也要确保她将获得所有密码.
但是,Java一直在变化,Java 8的字符串重复数据删除功能等一些可怕的功能可能会在您不知情的情况下实际执行String对象.但这是不同的对话.
Gee*_*eek 30
字符串是不可变的,一旦创建就无法更改.将密码创建为字符串将在堆或字符串池上留下对密码的杂散引用.现在,如果有人采用Java进程的堆转储并仔细扫描,他可能能够猜出密码.当然,这些未使用的字符串将被垃圾收集,但这取决于GC启动的时间.
另一方面,一旦认证完成,char []就是可变的,你可以用任何字符覆盖它们,比如所有的M或反斜杠.现在,即使有人进行堆转储,他也可能无法获取当前未使用的密码.这样可以为您提供更多控制,例如自己清除对象内容与等待GC执行此操作.
Pri*_*jee 17
简短而直截了当的答案是因为char[]当String对象不可变时是可变的.
Strings在Java中是不可变对象.这就是为什么一旦创建它们就无法修改的原因,因此从内存中删除它们内容的唯一方法就是让它们被垃圾收集.只有这样,当对象释放的内存才能被覆盖时,数据才会消失.
现在Java中的垃圾收集不会在任何保证的时间间隔内发生.因此String可以在内存中持续很长时间,并且如果进程在此期间崩溃,则字符串的内容可能最终在内存转储或某些日志中.
使用字符数组,您可以读取密码,尽快完成密码,然后立即更改内容.
ACV*_*ACV 17
字符串是不可变的,它将转到字符串池.一旦编写,就无法覆盖.
char[] 是一个数组,您应该在使用密码后覆盖它,这是应该如何完成的:
char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
allowUser = true;
cleanPassword(passw);
cleanPassword(dbPassword);
passw=null;
}
private static void cleanPassword (char[] pass) {
for (char ch: pass) {
ch = '0';
}
}
Run Code Online (Sandbox Code Playgroud)
攻击者可以使用它的一种情况是故障转储 - 当JVM崩溃并生成内存转储时 - 您将能够看到密码.
这不一定是恶意的外部攻击者.这可以是支持用户,可以访问服务器以进行监视.他可以查看故障转储并找到密码.
Saa*_*vik 12
java中的字符串是不可变的.因此,每当创建一个字符串时,它将保留在内存中,直到它被垃圾收集.所以任何有权访问内存的人都可以读取字符串的值.
如果修改了字符串的值,那么它将最终创建一个新字符串.因此,原始值和修改后的值都会保留在内存中,直到它被垃圾回收.
使用字符数组,一旦提供密码的目的,就可以修改或删除数组的内容.修改后甚至在垃圾收集开始之前,内存中将找不到数组的原始内容.出于
安全考虑,最好将密码存储为字符数组.
案例字符串:
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
Run Code Online (Sandbox Code Playgroud)
案例字符数组:
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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
378097 次 |
| 最近记录: |