java - 为什么char[]比字符串更适合密码?




string security (12)

在Swing中,密码字段有一个getPassword() (返回char[] )方法,而不是通常的getText() (返回String )方法。 同样,我遇到了一个不使用String来处理密码的建议。

为什么String在密码方面会对安全构成威胁? 使用char[]感觉不方便。


  1. 如果你将密码存储为纯文本,那么字符串在Java中是不可变的 ,它将在内存中可用,直到垃圾收集器清除它,并且因为字符串池中的字符串用于可重用性,所以很有可能它会长时间保留在内存中持续时间,构成安全威胁。 由于任何有权访问内存转储的人都可以以明文形式找到密码
  2. Java推荐使用JPasswordField的getPassword()方法,它返回一个char []和不推荐使用的getText()方法,该方法以明文形式返回密码,说明安全原因。
  3. 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 []还不够,你需要擦除内容才能更安全。 我还建议使用散列或加密密码而不是纯文本,并在验证完成后立即从内存中清除它。


字符串是不可变的 。 这意味着一旦你创建了String ,如果另一个进程可以转储内存,那么除了reflection之外你没有办法在垃圾收集开始之前摆脱数据。

使用数组,您可以在完成数据后显式擦除数据。 您可以使用您喜欢的任何内容覆盖数组,即使在垃圾回收之前,密码也不会出现在系统的任何位置。

所以,是的,这一个安全问题 - 但即使使用char[]只会减少攻击者的机会窗口,而且只针对这种特定类型的攻击。

正如评论中所指出的,垃圾收集器移动的数组可能会将数据的杂散副本留在内存中。 我相信这是特定于实现的 - 垃圾收集器可以清除所有内存,以避免这种情况。 即使它确实存在,仍然有时间char[]包含实际字符作为攻击窗口。


字符数组( char[] )可以在使用后通过将每个字符设置为零而不使用字符串来清除。 如果有人可以以某种方式查看内存映像,如果使用了字符串,他们可以以纯文本形式查看密码,但如果使用char[] ,则在使用0清除数据后,密码是安全的。


已经给出了答案,但我想分享一下我最近在Java标准库中发现的问题。 虽然他们现在非常小心地用char[]替换密码字符串(当然这是一件好事),但是当从内存中清除它时,其他安全关键数据似乎被忽略了。

我在考虑例如PrivateKey类。 考虑一种情况,您可以从PKCS#12文件加载私有RSA密钥,使用它来执行某些操作。 现在,在这种情况下,只要对密钥文件的物理访问受到适当限制,单独嗅探密码对您没有多大帮助。 作为攻击者,如果您直接获得密钥而不是密码,那将会好得多。 所需的信息可以泄漏,核心转储,调试器会话或交换文件只是一些例子。

事实证明,没有任何东西可以让你从内存中清除PrivateKey的私人信息,因为没有API可以让你擦除形成相应信息的字节。

这是一个糟糕的情况,因为paper描述了这种情况如何可能被利用。

例如,OpenSSL库在释放私钥之前覆盖关键内存部分。 由于Java是垃圾收集的,我们需要显式方法来擦除和使Java密钥的私有信息无效,这些密钥将在使用密钥后立即应用。


我不认为这是一个有效的建议,但是,我至少可以猜测原因。

我认为动机是要确保您可以在使用后及时清除内存中的所有密码。 使用char[]您可以用空格或某些东西覆盖数组的每个元素。 您无法以这种方式编辑String的内部值。

但仅此一点并不是一个好的答案; 为什么不确保对char[]String的引用不会被转义? 然后没有安全问题。 但问题是String对象可以在理论上进行内部intern()编辑并在常量池中保持活动状态。 我想使用char[]禁止这种可能性。


有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存。 这减少了攻击者从系统中读取密码的时间窗口,并完全忽略了攻击者已经需要足够的访问权来劫持JVM内存来执行此操作的事实。 具有这么多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了请纠正我)。

更新

感谢我的评论,我必须更新我的答案。 显然有两种情况可以增加(非常)次要的安全性改进,因为它减少了密码落在硬盘上的时间。 我认为对于大多数用例来说,这样做太过分了。

  • 您的目标系统可能配置不当,或者您必须假设它并且您必须对核心转储感到偏执(如果系统不由管理员管理,则可能有效)。
  • 您的软件必须过于偏执,以防止数据泄露,攻击者可以访问硬件 - 使用TrueCrypt (已停产), VeraCryptCipherShed

如果可能,禁用核心转储和交换文件将解决这两个问题。 但是,它们需要管理员权限并且可能会降低功能(使用的内存更少),并且从正在运行的系统中提取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: [[email protected]

这些都是原因,应该选择char []数组而不是String作为密码。

1.由于如果你将密码存储为纯文本字符串在Java中是不可变的,它将在内存中可用,直到垃圾收集器清除它并且因为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: [[email protected]

参考来自: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.htmlhttp://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html希望这会http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html帮助。


1)因为如果你将密码存储为纯文本字符串在Java中是不可变的,它将在内存中可用,直到垃圾收集器清除它并且因为String在字符串池中用于可重用性,它很可能会保留在内存中持续时间长,构成安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,您应始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char [],你仍然可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以降低窃取密码的安全风险。

2)Java本身建议使用JPasswordField的getPassword()方法,它返回一个char []和不推荐使用的getText()方法,它以明文形式返回密码,说明安全原因。遵循Java团队的建议并坚持标准而不是反对它是很好的。


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) {
 for (char ch: pass) {
  ch = null;
 }
}

攻击者可以使用它的一种情况是崩溃转储 - 当JVM崩溃并生成内存转储时 - 您将能够看到密码。

这不一定是恶意的外部攻击者。这可以是支持用户,可以访问服务器以进行监视。他可以查看故障转储并找到密码。


简短而直截了当的答案是因为char[]String对象不可变时是可变的。

Strings在Java中是不可变对象。这就是为什么它们一旦创建就无法修改的原因,因此从内存中删除内容的唯一方法就是让它们被垃圾收集。只有这样,当对象释放的内存才能被覆盖时,数据才会消失。

现在Java中的垃圾收集不会在任何保证的时间间隔内发生。因此String可以在内存中持续很长时间,并且如果进程在此期间崩溃,则字符串的内容可能最终在内存转储或某些日志中。

使用字符数组,您可以读取密码,尽快完成密码,然后立即更改内容。







char