为什么密码比字符串更喜欢char []?


3421

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

为什么String涉及密码安全性受到威胁?使用起来感觉不方便char[]

Answers:


4287

字符串是不可变的。这意味着一旦创建了String,如果另一个进程可以转储内存,则除了反射之外,您将无法清除数据,然后再进行垃圾回收

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

因此,是的,这一个安全性问题-但是即使使用,也char[]只会减少攻击者的机会窗口,并且仅用于这种特定类型的攻击。

如评论中所述,垃圾回收器移动的数组可能会将数据的零散副本保留在内存中。我相信这是特定于实现的-垃圾收集器可能会清除所有内存,以免发生这种情况。即使这样做,仍然有一段时间char[]包含实际角色作为攻击窗口。


3
如果某个进程可以访问您的应用程序的内存,那么这已经是安全漏洞,对吗?
雪人

5
@Yeti:是的,但不是黑白相间的。如果他们只能获取内存的快照,那么您想减少该快照可能造成的损害,或者减少可以进行真正严重快照的时间范围。
乔恩·斯基特

11
一种常见的攻击方法是运行一个分配大量内存的进程,然后对其进行扫描以查找剩余的有用数据(例如密码)。进程不需要对另一个进程的内存空间的任何魔术访问;它只是依赖于其他进程即将死去,而没有先清除敏感数据,并且操作系统也没有在将其提供给新进程之前清除内存(或页面缓冲区)。清除存储在char[]位置中的密码会切断该攻击路线,使用时是不可能的String
泰德·霍普

如果操作系统在将内存交给其他进程之前没有清除内存,则说明操作系统存在重大安全问题!但是,从技术上讲,清除通常是通过保护模式技巧完成的,并且如果CPU损坏(例如Intel Meldown),仍然可以读取旧的内存内容。
Mikko Rantalainen

1221

尽管这里的其他建议似乎是有效的,但还有另一个很好的理由。使用plain,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

40
@voo,但我怀疑您会通过直接写入流和串联来登录。日志记录框架会将char []转换为良好的输出
bestsss 2012年

41
@ Thr4wn的默认实现toStringclassname@hashcode[C代表char[],其余为十六进制哈希码。
Konrad Garus 2012年

15
有趣的主意。我想指出的是,这不会转置为Scala,后者对数组具有有意义的toString。
mauhiz

37
我会Password为此编写一个类类型。它不那么晦涩难懂,而且更难以意外通过某个地方。
2015年

8
为什么有人会认为char数组将被转换为Object?我不确定为什么每个人都喜欢这个答案。假设您这样做:System.out.println(“ Password” .toCharArray());
GC_

680

为了引用正式文档,《Java密码学体系结构指南》说了关于密码char[]String密码(关于基于密码的加密,但这当然更普遍地是关于密码):

收集密码并将其存储在type对象中似乎是合乎逻辑的java.lang.String。但是,需要注意的是:Object类型s String是不可变的,即,没有定义允许您更改(覆盖)或将String 使用后的内容归零的方法。此功能使String对象不适合存储对安全敏感的信息,例如用户密码。您应该始终将安全敏感信息收集并存储在 char阵列中。

Java编程语言版本4.0的安全编码准则的准则2-2也表示类似的内容(尽管它最初是在日志记录的上下文中):

准则2-2:请勿记录高度敏感的信息

某些信息(例如,社会安全号码(SSN)和密码)非常敏感。该信息的保存时间不应超过必要的时间,也不能保存在任何地方,即使是管理员也是如此。例如,不应将其发送到日志文件,并且不应通过搜索来检测其存在。某些瞬态数据可以保存在可变数据结构中,例如char数组,并在使用后立即清除。清除数据结构在典型的Java运行时系统上降低了效率,因为对象在内存中对程序员透明地移动。

该指南还对不具有所处理数据语义知识的低级库的实现和使用产生影响。例如,低级字符串解析库可能会记录其工作的文本。应用程序可以使用库解析SSN。这会导致管理员可以访问日志文件使用SSN。


4
这恰好是我在乔恩的回答下谈论的有缺陷/虚假的参考,这是众所周知的资料,带有很多批评。
bestsss 2012年

37
@bestass还可以引用吗?
user961954

10
@bestass对不起,但是String已经很好地理解了它以及它在JVM中的行为…… 以安全的方式处理密码时,有充分的理由使用char[]String
SnakeDoc

3
再次将密码作为字符串从浏览器作为“字符串”传递给请求,而不是char?因此,无论您做什么,它都是一个字符串,在什么时候应该对其进行操作并丢弃,永远不要存储在内存中?
达维西

3
@Dawesi-- At which point是特定于应用程序的,但是一般规则是,只要您掌握了应该是密码的内容(纯文本或其他方式),便立即这样做。例如,您从浏览器获取它作为HTTP请求的一部分。您无法控制交付,但是可以控制自己的存储,因此,一旦获取它,将其放入char []中,执行所需的操作,然后将其全部设置为'0'并让gc回收它。
luis.espinal

354

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


13
默认情况下不安全。如果我们正在谈论Web应用程序,则大多数Web容器会将密码HttpServletRequest以纯文本形式传递到对象中。如果JVM版本为1.6或更低版本,它将在permgen空间中。如果它在1.7中,则在被收集之前仍然可读。(无论何时)
avgvstvs

4
@avgvstvs:字符串不会自动移动到permgen空间,仅适用于实习字符串。除此之外,还需要以较低的速率对permgen空间进行垃圾回收。permgen空间的真正问题是它的固定大小,这正是为什么没有人会不经意地调用intern()任意字符串的原因。但是您是对的,因为String实例首先存在(直到收集到),char[]之后再将它们转换为数组不会改变它。
Holger

3
@Holger参见docs.oracle.com/javase/specs/jvms/se6/html/… “否则,将创建String类的新实例,其中包含由CONSTANT_String_info结构给出的Unicode字符序列;该类实例是字符串文字派生。最后,调用新的String实例的intern方法。” 在1.6中,JVM将在检测到相同序列时为您调用intern。
avgvstvs

3
@Holger,您是正确的,我将常量池和字符串池混为一谈,但是将permgen空间应用于实习字符串也是错误的。在1.7之前,constant_pool和string_pool都位于permgen空间中。这意味着分配给堆的唯一一类字符串是您所说的,new String()或者StringBuilder.toString() 我管理的应用程序具有很多字符串常量,结果导致了很多permgen蠕变。直到1.7。
avgvstvs

5
@avgvstvs:嗯,根据JLS的要求,字符串常量始终处于intern状态,因此,将interned字符串终止于permgen空间的声明会隐式应用于字符串常量。唯一的区别是首先在permgen空间中创建了字符串常量,而调用intern()任意字符串可能会导致在permgen空间中分配等效的字符串。如果不存在共享该对象的相同内容的文字字符串,则后者可以进行GC处理……
Holger

220

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

更新资料

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

  • 您的目标系统可能配置错误,或者必须假定目标系统配置错误,并且必须对核心转储抱有幻想(如果系统不是由管理员管理的,则可能有效)。
  • 您的软件必须过于偏执,以防止攻击者获得对硬件的访问权限而导致数据泄漏-使用TrueCrypt之类的东西(已停产),VeraCryptCipherShed之类的东西

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


33
我会用“仅对安全性进行较小的改进”来代替“完全没用”。例如,如果您碰巧拥有对tmp目录的读取访问权限,配置错误的计算机以及应用程序崩溃,则可以访问内存转储。在这种情况下,您将无法安装键盘记录器,但可以分析核心转储。
约阿希姆·绍尔

44
一旦完成处理,就从内存中清除未加密的数据被认为是最佳实践,这并不是因为它是万无一失的(不是);但是因为它降低了您的威胁暴露程度。这样做并不能阻止实时攻击。但由于它通过减少内存快照的追溯攻击中暴露的数据量(例如,已写入交换文件或从内存中拉出的应用程序内存的副本)显着减少了暴露的数据量,因此它是一种缓解损害的工具从一台正在运行的服务器上,并在其状态失败之前移至另一台服务器)。
Dan在火光中摆弄

9
我倾向于同意这种回应的态度。我冒险提出大多数安全后果,其后果发生在比内存中的位高得多的抽象水平上。当然,在超安全防御系统中可能存在一些场景,这可能是一个值得关注的问题,但是对于99%的正在利用.NET或Java的应用程序(因为它与垃圾回收有关),认真考虑这一点是过高的。
kingdango 2012年

10
在服务器内存经过Heartbleed渗透并揭示了密码之后,我将字符串“仅做了很小的安全性改进”替换为“绝对必要的,不要使用String作为密码,而是使用char []。”
彼得vdL,2014年

9
@PetervdL Heartbleed只允许读取特定的重复使用的缓冲区集合(用于安全性关键数据和网络I / O,而无需在两者之间进行清除-出于性能原因),您不能将其与Java String结合使用,因为它们在设计上是不可重用的。您也不能使用Java读取随机存储器来获取String的内容。Java Strings根本不会导致语言混乱的设计和语言问题。
josefx 2014年

86

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

我认为这样做的动机是要确保您可以在使用密码后立即确定地擦除内存中的所有密码痕迹。使用a char[]可以确保用空格或其他东西覆盖数组的每个元素。您无法编辑String这种方式。

但这并不是一个好的答案。为什么不仅仅确保对char[]String不逃避引用呢?这样就没有安全问题了。但事实是,String对象可以intern()从理论上进行编辑,并在常量池中保持活动。我想使用char[]禁止这种可能性。


4
我不会说问题是您的引用会或不会“转义”。只是字符串将在内存中保留一些额外的时间不变,而char[]可以修改,因此是否收集字符串无关紧要。而且由于需要对非文字进行显式的字符串插入,因此就像告诉a char[]可以由静态字段引用一样。
Groo 2012年

1
表单中的密码不是字符串中的密码吗?
Dawesi

66

答案已经给出,但是我想分享一个我最近在Java标准库中发现的问题。尽管他们现在非常注意将密码字符串替换为char[]任何地方都(这当然是一件好事),但是从内存中清除密码时,其他对安全性要求较高的数据似乎却被忽略了。

我在想例如私钥类。考虑一种方案,您将从PKCS#12文件中加载私有RSA密钥,并使用它执行一些操作。现在,在这种情况下,只要适当限制对密钥文件的物理访问,仅嗅探密码将无济于事。作为攻击者,如果直接获得密钥而不是密码,那么情况会更好。所需信息可能会泄漏,核心转储,调试器会话或交换文件只是一些示例。

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

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

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


解决此问题的一种方法是使用PrivateKey实际上不会将其私有内容加载到内存中的实现:例如,通过PKCS#11硬件令牌。也许PKCS#11的软件实现可以负责手动清理内存。也许使用类似NSS商店(与PKCS11Java中的商店类型共享其大部分实现)的工具会更好。该KeychainStore(OSX密钥库)负荷私钥到它的全部内容PrivateKey的情况下,但它不应该需要。(不知道什么WINDOWS-MY密钥库确实在Windows上)
布鲁诺

@Bruno当然,基于硬件的令牌不会受到此影响,但是对于您多少被迫使用软件密钥的情况呢?并非每个部署都具有负担HSM的预算。软件密钥存储区有时必须将密钥加载到内存中,因此,至少应给IMO我们一个选择,以便再次清除内存。
2012年

绝对,我只是想知道是否某些等同于HSM的软件实现在清理内存方面表现更好。例如,当将client-auth与Safari / OSX一起使用时,Safari进程实际上不会看到私钥,操作系统提供的底层SSL库直接与安全守护程序对话,后者会提示用户使用钥匙串中的密钥。尽管这一切都是在软件中完成的,但是如果将签名委托给其他实体(甚至是基于软件的实体)进行签名,则可以更好地卸载或清除内存,这样类似的分离可能会有所帮助。
布鲁诺(Bruno)

@Bruno:一个有趣的想法,一个额外的间接层,负责清除内存,确实可以透明地解决这个问题。为软件密钥存储库编写PKCS#11包装器已经可以解决问题吗?
2012年

51

正如乔恩·斯基特(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: '***********'

注意:如果字符串的char []已作为GC周期的一部分复制,则前一个副本有可能在内存中的某个位置。

这个旧副本不会出现在堆转储中,但是如果您可以直接访问该进程的原始内存,则可以看到它。通常,您应该避免任何人具有这种​​访问权限。


2
最好也做一些事情来防止打印我们从那里得到的密码的长度'***********'
chux-恢复莫妮卡2015年

@chux您可以使用宽度为零的字符,尽管这可能比有用的功能更令人困惑。如果不使用Unsafe,则无法更改char数组的长度。;)
Peter Lawrey 2015年

5
由于Java 8的String Deduplication,我认为这样做很具有破坏性……您可能最终会清除程序中其他具有与String相同值的字符串。不太可能,但可能...
2016年

1
@PeterLawrey必须使用JVM参数启用它,但是它在那里。可以在这里阅读:blog.codecentric.de/en/2014/08/…–
jamp

1
密码很有可能仍位于Scanner的内部缓冲区中,并且由于您未使用System.console().readPassword(),所以仍在控制台窗口中以可读的形式存在。但是对于大多数实际用例,usePassword执行的持续时间是实际的问题。例如,当连接到另一台机器时,这会花费大量时间,并告诉攻击者现在是在堆中搜索密码的正确时间。唯一的解决方案是防止攻击者读取堆内存…
Holger

42

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

1. 由于字符串在Java中是不可变的,因此,如果您将密码存储为纯文本格式,则密码将在内存中可用,直到垃圾回收器将其清除为止,并且由于字符串在字符串池中用于可重用性,因此很有可能长时间保留在内存中,构成安全威胁。

由于有权访问内存转储的任何人都可以以明文形式找到密码,这是另一个原因,您应该始终使用加密密码而不是纯文本。由于字符串是不可变的,因此无法更改字符串的内容,因为任何更改都会产生一个新的字符串,而如果使用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

从此博客引用。我希望这有帮助。


10
这是多余的。此答案是@SrujanKumarGulla stackoverflow.com/a/14060804/1793718编写的答案的精确版本。请不要复制粘贴或重复相同的答案两次。
2016年

1.)System.out.println(“字符密码:” + charPassword)之间有什么区别?和2.)System.out.println(charPassword); 因为它给出与输出相同的“未知”。
Vaibhav_Sharma

3
@Lucky不幸的是,您链接到的较早答案与该答案来自同一博客的窃,现已删除。参见meta.stackoverflow.com/questions/389144/…。这个答案只是从同一个博客中剪切和粘贴而来,没有添加任何内容,因此它应该只是链接到原始来源的评论。
skomisa

41

编辑:经过一年的安全研究,回到这个答案,我意识到,这很不幸地暗示您实际上会比较纯文本密码。请不要。使用带有盐和合理迭代次数的安全单向哈希。考虑使用图书馆:这东西很难弄对!

原始答案: 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;
}

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


但这也可以在char []比较中找到,在某个地方我们也将在密码验证中做同样的事情。那么char []比字符串好吗?
Mohit Kanwar

3
您是绝对正确的,无论哪种方式都可能犯错误。考虑到问题,这是最重要的事情,考虑到Java中没有针对基于字符串或基于char []的密码的显式密码比较方法。我想说对字符串使用compare()的诱惑是与char []一起使用的一个很好的理由。这样,您至少可以控制比较的完成方式(无需扩展String,这很痛苦)。
图论

除了比较纯文本密码不是正确的选择外,Arrays.equalsfor 的诱惑与for char[]一样高String.equals。如果有人在乎,会有专用的密钥类来封装实际的密码并解决问题—等等,真正的安全软件包专用的密钥类,此问与答只是一种习惯,例如JPasswordField,使用char[]而不是String(实际算法仍在使用byte[])。
Holger

与安全相关的软件sleep(secureRandom.nextInt())无论如何都要在拒绝登录尝试之前执行类似的操作,这不仅消除了定时攻击的可能性,而且还抵消了暴力尝试。
Holger

36

除非您在使用后手动清理它,否则char数组不会给您vs String带来什么好处,而且我还没有看到有人真正这样做过。所以对我来说,char [] vs String的偏爱有点夸张。

这里看看广泛使用的 Spring Security库,问问自己-Spring Security的人是不称职的还是char []密码没有多大意义。当一些讨厌的黑客抓取您RAM的内存转储时,请确保他/她将获得所有密码,即使您使用复杂的方式将其隐藏。

但是,Java一直在变化,某些令人恐惧的功能(例如Java 8的String Deduplication功能)可能会在您不知情的情况下插入String对象。但这是另一回事。


为什么字符串重复数据删除会令人恐惧?它仅在至少两个具有相同内容的字符串时适用,因此,让这两个已经相同的字符串共享同一数组会带来什么危险?或者让我们反过来问:如果没有字符串重复数据删除,那么两个字符串都有一个不同的数组(具有相同的内容)会带来什么好处?无论哪种情况,只要该内容的寿命最长的字符串还活着,就会存在该内容的数组活着……
Holger

@Holger任何您无法控制的东西都有潜在的风险……例如,如果两个用户使用相同的密码,那么这个很棒的功能会将他们两个都存储在单个char []中,从而表明它们是相同的,不确定是否巨大的风险,但仍然
Oleg Mikheev '17

如果您可以访问堆内存和两个字符串实例,则字符串是指向同一数组还是指向具有相同内容的两个数组都没有关系,每个字符串都易于找出。特别是,因为它无关紧要。此时,您将同时获取两个密码,无论是否相同。实际错误在于使用纯文本密码而不是加盐的哈希。
Holger

@Holger验证密码,它必须在内存中以纯文本格式显示一段时间(10毫秒),即使只是为了从中创建加盐的哈希也是如此。然后,如果碰巧甚至在内存中保留了两个相同的密码10毫秒,则重复数据删除可能会发挥作用。如果它确实对字符串进行了实习,它们将在内存中保留更长的时间。几个月没有重启的系统将收集很多此类信息。只是理论上。
Oleg Mikheev '17

看来,您对String Deduplication有一个根本的误解。它并不是“实习字符串”,而是让内容相同的字符串指向同一数组,这实际上减少了包含纯文本密码的数组实例的数量,因为除了一个数组实例之外的所有实例都可以回收和覆盖。被其他物体立即破坏。这些字符串仍然像其他任何字符串一样被收集。如果您了解重复数据删除实际上是由垃圾收集器完成的,那么对于仅在多个GC循环中幸存下来的字符串,它可能会有所帮助。
霍尔格

30

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

另一方面,一旦完成身份验证,就可以更改char [],您可以使用任何字符(如所有M或反斜杠)覆盖它们。现在,即使有人进行堆转储,他也可能无法获得当前未使用的密码。从某种意义上来说,这给了您更多的控制权,例如您自己清除对象内容与等待GC完成操作。


仅当有问题的JVM> 1.6时,才会对它们进行GC。在1.7之前,所有字符串都存储在permgen中。
avgvstvs's

@avgvstvs:“所有字符串都存储在permgen中”完全是错误的。仅将实习字符串存储在此处,并且如果它们不是源自代码引用的字符串文字,则它们仍会被垃圾回收。考虑一下。如果通常在1.7之前的JVM中从未对字符串进行GC,那么任何Java应用程序如何都能存活超过几分钟?
Holger

@Holger这是错误的。实习stringsString池(先前使用的字符串池)之前1.7均存储在PermGen的。另外,请参见5.1节: docs.oracle.com/javase/specs/jvms/se6/html/…JVM 始终检查Strings它们是否具有相同的参考值,并会调用String.intern()FOR YOU。结果是,每次JVM在constant_pool或堆中检测到相同的String时,它将把它们移入permgen。我使用“蠕变蠕变”直到1.7的多个应用程序。这是一个真正的问题。
avgvstvs

回顾一下:直到1.7,字符串开始在堆中使用,当使用它们时,将它们放入constant_pool位于permgen中的字符串中,然后,如果多次使用字符串,则会对其进行检查。
avgvstvs

@avgvstvs:没有“以前使用过的字符串池”。您将完全不同的东西放在一起。有一个运行时字符串池,其中包含字符串文字和显式内联的字符串,但没有其他字符串。每个类都有其常量池,其中包含编译时常量。这些字符串会自动添加到运行时池中,但只会添加到这些字符串,而不是每个字符串。
Holger

21

字符串是不可变的,它进入字符串池。一旦写入,就不能覆盖。

char[] 是一个数组,使用密码后应该覆盖它,这是应该这样做的方法:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {

Arrays.fill(pass, '0');
}

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

那不一定是恶意的外部攻击者。这可能是支持用户,可以访问服务器以进行监视。他可以窥视一个崩溃转储并找到密码。


2
ch = null; 您不能做到这一点
Yugerten

但是还没有request.getPassword()创建字符串并将其添加到池中吗?
Tvde1

1
ch = '0'改变局部变量ch; 它对数组没有影响。而且您的示例毫无意义,您从调用的字符串实例开始toCharArray(),创建了一个新数组,即使正确覆盖了新数组,它也不会更改字符串实例,因此与仅使用字符串相比,它没有任何优势实例。
霍尔格

@霍尔格谢谢。更正了char数组清除代码。
ACV

3
您可以简单地使用Arrays.fill(pass, '0');
Holger

18

简短而直接的答案是,因为char[]可变的而String对象不是可变的。

StringsJava中的对象是不可变的。这就是为什么一旦创建便无法对其进行修改,因此从内存中删除其内容的唯一方法是对其进行垃圾回收。只有当对象释放的内存可以被覆盖并且数据将消失时,才会这样。

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

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


@fallenidol一点也不。仔细阅读,您会发现差异。
Pritam Banerjee

12

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

使用字符数组,一旦达到密码的目的,就可以修改或删除数组的内容。在修改数组之后甚至在进行垃圾回收之前,都不会在内存中找到数组的原始内容。出于

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


2

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

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

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

基于以上情况,可以根据需要选择使用String还是使用Char []。


0

大小写字符串:

    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 ARRAY:

    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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.