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

为什么字符串对密码安全构成威胁?使用char[]感觉不方便。


当前回答

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

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

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

这是一个糟糕的情况,因为本文描述了这种情况如何被潜在利用。

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

其他回答

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

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

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

这是一个糟糕的情况,因为本文描述了这种情况如何被潜在利用。

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

简单明了的答案是因为char[]是可变的,而String对象不是可变的。

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

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

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

引用一份官方文档,Java Cryptography Architecture指南介绍了char[]与String密码(关于基于密码的加密,但这更一般地是关于密码):

将密码收集并存储在对象中似乎是合乎逻辑的然而,这里有一个警告:类型String是不可变的,即没有定义允许您更改(覆盖)或清零字符串的内容使用后。此功能使字符串对象不适合存储诸如用户密码之类的安全敏感信息。你应始终在char数组。

Java编程语言4.0版安全编码指南2-2也提到了类似的内容(尽管最初是在日志记录的上下文中):

准则2-2:不记录高度敏感信息一些信息,如社会安全号码(SSN)和密码是高度敏感的。此信息不应保留无论在什么地方,甚至在管理员。例如,不应将其发送到日志文件和它的存在不应该通过搜索来检测。一些瞬态数据可以保存在可变数据结构中,例如char数组,以及使用后立即清除。清除数据结构减少了当对象移入时,在典型的Java运行时系统上的有效性存储器对程序员来说是透明的。本指南还对实施和使用没有数据语义知识的低级库他们正在处理。例如,低级字符串解析库可以记录其工作的文本。应用程序可以解析SSN与图书馆。这会导致SSN管理员可以访问日志文件。

大小写字符串:

    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

虽然这里的其他建议似乎有效,但还有一个很好的理由。使用纯字符串时,意外将密码打印到日志、监视器或其他不安全的地方的可能性要大得多。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