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

时间:2012-01-16 14:20:42

标签: java string security passwords char

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

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

20 个答案:

答案 0 :(得分:4026)

字符串是不可变的。这意味着一旦你创建了String,如果另一个进程可以转储内存,除了reflection之外没有办法可以在garbage collection启动之前删除数据。< / p>

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

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

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

答案 1 :(得分:1152)

虽然这里的其他建议似乎有效,但还有另外一个好理由。使用普通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

答案 2 :(得分:649)

引用官方文档时,Java Cryptography Architecture guide说明了char[]String密码(关于基于密码的加密,但当然更常见的是关于密码):< / p>

  

在对象中收集和存储密码似乎是合乎逻辑的   类型为java.lang.String。但是,请注意以下事项:Object   类型String是不可变的,即没有定义的方法   允许您更改(覆盖)或清除String的内容   使用后。此功能使String个对象不适合   存储安全敏感信息,如用户密码。您   应始终收集并存储安全敏感信息   而是char数组。

Guideline 2-2 of the Secure Coding Guidelines for the Java Programming Language, Version 4.0也说了类似的东西(虽然它最初是在记录的上下文中):

  

准则2-2:不要记录高度敏感的信息

     

一些信息,例如社会安全号码(SSN)和   密码,非常敏感。不应保留此信息   超过必要的时间,甚至可以看到的地方   管理员。例如,它不应该发送到日志文件和   它的存在不应该通过搜索检测到。一些短暂的   数据可以保存在可变数据结构中,例如char数组,和   使用后立即清除。清算数据结构已减少   移动对象时,典型Java运行时系统的有效性   对程序员透明的记忆。

     

本指南也对实施和使用有影响   没有数据语义知识的低级库   他们正在处理。例如,一个低级别的字符串解析   库可能会记录它所使用的文本。应用程序可以解析SSN   与图书馆。这会产生SSN的情况   可供有权访问日志文件的管理员使用。

答案 3 :(得分:332)

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

答案 4 :(得分:205)

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

<强>更新

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

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

如果可能,禁用核心转储和交换文件将解决这两个问题。但是,它们需要管理员权限并且可能会降低功能(使用的内存更少),并且从正在运行的系统中提取RAM仍然是一个有效的问题。

答案 5 :(得分:135)

  1. 在Java中字符串是不可变的如果您将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它并且因为字符串池中使用字符串以便重新使用,所以它非常高它会长时间留在记忆中的可能性,这会带来安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码
  2. Java推荐使用JPasswordField的getPassword()方法返回char []和弃用的getText()方法,该方法以明文形式返回密码,说明安全原因。
  3. toString()在日志文件或控制台中始终存在打印纯文本的风险,但如果使用数组,则不会打印数组的内容,而是打印其内存位置。

    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

  4. 最后的想法:尽管使用char []还不够,但您需要删除内容才能更安全。我还建议使用散列或加密密码而不是纯文本,并在验证完成后立即从内存中清除它。

答案 6 :(得分:79)

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

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

但仅此一点并不是一个好的答案;为什么不确保对char[]String的引用不会逃脱?然后没有安全问题。但问题是String个对象在理论上可以intern()并且在常量池中保持活跃。我想使用char[]禁止这种可能性。

答案 7 :(得分:62)

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

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

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

这是一个糟糕的情况,因为{{3}}描述了这种情况如何被利用。

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

答案 8 :(得分:47)

正如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循环的一部分,则前一个副本可能位于内存中的某个位置。

此旧版本不会出现在堆转储中,但如果您可以直接访问该进程的原始内存,则可以看到它。一般来说,你应该避免任何人有这种访问权。

答案 9 :(得分:38)

编辑:在经过一年的安全研究后回到这个答案,我意识到这实际上是一个相当不幸的暗示,你实际上会比较明文密码。请不要。 Use a secure one-way hash with a salt and a reasonable number of iterations。考虑使用一个库:这个东西很难做对!

原始答案: String.equals()使用short-circuit evaluation的事实怎么样,因此容易受到时间攻击?它可能不太可能,但您可以理论上计算密码比较时间,以确定正确的字符序列。

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;
}

有关定时攻击的更多资源:

答案 10 :(得分:38)

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

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

来自this blog。 我希望这会有所帮助。

答案 11 :(得分:33)

除非你在使用后手动清理它,否则char数组没有给你vs String,我没有看到任何人真正这样做。所以对我来说char [] vs String的偏好有点夸张。

看一下广泛使用的 Spring Security库here并问问自己 - Spring Security人员不称职或char []密码只是没有多大意义。当一些讨厌的黑客抓住你的RAM的内存转储时,即使你使用复杂的方法隐藏它们,也要确保她将获得所有密码。

然而,Java一直在变化,而String Deduplication feature of Java 8等一些可怕的功能可能会在您不知情的情况下实施String对象。但这是不同的对话。

答案 12 :(得分:29)

字符串是不可变的,一旦创建就无法更改。将密码创建为字符串将在堆或字符串池上留下对密码的杂散引用。现在,如果有人采用Java进程的堆转储并仔细扫描,他可能能够猜出密码。当然,这些未使用的字符串将被垃圾收集,但这取决于GC启动的时间。

另一方面,一旦验证完成,char []就是可变的,你可以用任何字符覆盖它们,比如所有的M或反斜杠。现在,即使有人进行堆转储,他也可能无法获取当前未使用的密码。这样可以为您提供更多控制,例如自己清除对象内容与等待GC执行此操作。

答案 13 :(得分:16)

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

Java中的

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

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

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

答案 14 :(得分:16)

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

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

答案 15 :(得分:15)

字符串是不可变的,它会转到字符串池。一旦编写,就不能被覆盖。

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';
 }
}

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

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

答案 16 :(得分:12)

java中的字符串是不可变的。因此,每当创建一个字符串时,它将保留在内存中,直到它被垃圾收集。所以任何有权访问内存的人都可以读取字符串的值。
如果修改了字符串的值,那么它将最终创建一个新字符串。因此,原始值和修改后的值都会保留在内存中,直到它被垃圾回收。

使用字符数组,一旦提供密码的目的,就可以修改或删除数组的内容。修改后甚至在垃圾收集开始之前,内存中将找不到数组的原始内容。

出于安全考虑,最好将密码存储为字符数组。

答案 17 :(得分:4)

大小写字符串:

    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数组:

    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

答案 18 :(得分:2)

为此目的应使用String还是使用Char []尚有争议,因为两者都有其优点和缺点。这取决于用户的需求。

由于Java中的字符串是不可变的,因此每当有人尝试操纵您的字符串时,它都会创建一个新的Object,而现有的String则不受影响。这可能被视为将密码存储为字符串的一个优点,但是即使使用该对象,该对象仍保留在内存中。因此,如果有人以某种方式获得了对象的存储位置,则该人可以轻松地跟踪存储在该位置的密码。

Char []是可变的,但是它的优点是程序员使用它后可以显式清理数组或覆盖值。因此,使用完毕后,它就会被清除,而且没人会知道您存储的信息。

基于上述情况,人们可以了解是使用String还是使用Char []满足他们的要求。

答案 19 :(得分:0)

上面有很多很棒的答案。我假设还有一点(如果我错了,请纠正我)。默认情况下,Java 使用 UTF-16 来存储字符串。使用字符数组 char[]array 便于使用 unicode、区域字符等。这种技术允许在存储密码时平等地考虑所有字符集,因此以后不会由于字符集混淆而引发某些加密问题。最后使用 char 数组,我们可以将密码数组转换为我们想要的字符集字符串。