为什么密码的 char[] 优先于 String?

Why is char[] preferred over String for passwords?

提问人:Ahamed 提问时间:1/16/2012 最后编辑:Rakete1111Ahamed 更新时间:1/20/2023 访问量:509774

问:

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

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

java 字符串 安全 密码 char

评论


答:

4729赞 Jon Skeet 1/16/2012 #1

字符串是不可变的。这意味着一旦你创建了 ,如果另一个进程可以转储内存,你就没有办法(除了反射)在垃圾回收开始之前删除数据。String

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

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

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

评论

37赞 Yeti 9/1/2015
如果一个进程可以访问你的应用程序的内存,那么这已经是一个安全漏洞,对吧?
34赞 Jon Skeet 9/1/2015
@Yeti:是的,但它不是非黑即白的。如果他们只能获取内存的快照,那么您希望减少该快照可能造成的损害,或者减少可以拍摄真正严重快照的窗口。
74赞 Ted Hopp 9/6/2016
一种常见的攻击方法是运行一个分配大量内存的进程,然后扫描该进程以查找剩余的有用数据,例如密码。该进程不需要对另一个进程的内存空间进行任何神奇的访问;它只是依赖于其他进程在没有首先清除敏感数据的情况下死亡,并且操作系统在将其提供给新进程之前也不会清除内存(或页面缓冲区)。清除存储在某个位置的密码会切断该攻击线,这在使用 .char[]String
24赞 Mikko Rantalainen 4/21/2020
如果操作系统在将内存提供给另一个进程之前没有清除内存,则操作系统存在重大安全问题!然而,从技术上讲,清除通常是通过保护模式技巧完成的,如果CPU损坏(例如Intel Meldown),仍然可以读取旧的内存内容。
12赞 Jon Skeet 1/10/2021
@PrateekPande:只有当它存在于源代码中或显式嵌入时,它才会出现在文字池中。总的来说,这些都是坏主意......
392赞 alephx 1/16/2012 #2

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

评论

17赞 avgvstvs 2/5/2016
默认情况下不安全。如果我们谈论的是 Web 应用程序,大多数 Web 容器会以明文形式将密码传递到对象中。如果 JVM 版本为 1.6 或更低版本,它将位于永久空间中。如果它在 1.7 中,则在收集之前它仍然是可读的。(无论何时。HttpServletRequest
6赞 Holger 3/9/2017
@avgvstvs:字符串不会自动移动到 permgen 空间,这仅适用于 intern 的字符串。除此之外,permgen 空间也受到垃圾回收的影响,只是回收率较低。permgen 空间的真正问题是它的大小是固定的,这正是没有人应该盲目地调用任意字符串的原因。但是你是对的,因为实例首先存在(直到收集),之后将它们转换为数组不会改变它。intern()Stringchar[]
4赞 avgvstvs 3/10/2017
@Holger看到 docs.oracle.com/javase/specs/jvms/se6/html/......“否则,将创建一个类 String 的新实例,其中包含 CONSTANT_String_info 结构给出的 Unicode 字符序列;该类实例是字符串文本派生的结果。最后,调用新 String 实例的 intern 方法。在 1.6 中,当 JVM 检测到相同的序列时,它会为您呼叫 intern。
4赞 avgvstvs 3/12/2017
@Holger,你是对的,我把常量池和字符串池混为一谈,但 permgen 空间适用于被隔离的字符串也是错误的。在 1.7 之前,constant_pool 和 string_pool 都位于 permgen 空间中。这意味着分配给堆的唯一一类字符串是如您所说,或者我管理具有大量字符串常量的应用程序,结果我们有很多 permgen 蠕变。直到 1.7。new String()StringBuilder.toString()
7赞 Holger 3/13/2017
@avgvstvs:好吧,正如 JLS 所规定的那样,字符串常量总是被嵌入的,因此,被嵌入的字符串最终进入 permgen 空间,隐式应用于字符串常量。唯一的区别是字符串常量首先是在 permgen 空间中创建的,而调用任意字符串可能会导致在 permgen 空间中分配等效字符串。后者可以被GC处理,如果没有共享该对象的相同内容的文字字符串......intern()
93赞 Sean Owen 1/16/2012 #3

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

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

但仅凭这一点并不是一个好的答案;为什么不确保对 or 的引用不会转义呢?那么就没有安全问题了。但问题是,从理论上讲,物体可以被编辑,并在恒定的水池中保持活力。我想使用禁止这种可能性。char[]StringStringintern()char[]

评论

4赞 vgru 1/17/2012
我不会说问题在于你的参考资料会或不会“逃脱”。只是字符串在内存中会保留一段时间,而可以修改,然后是否被收集就无关紧要了。由于字符串实习需要显式地为非文字进行,这就像告诉静态字段可以引用 a 一样。char[]char[]
3赞 Dawesi 8/6/2018
内存中的密码不是表单 post 中的字符串吗?
237赞 josefx 1/16/2012 #4

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

更新

感谢评论,我必须更新我的答案。显然,在两种情况下,这可以增加(非常)小的安全性改进,因为它减少了密码登陆硬盘驱动器的时间。不过,我认为这对于大多数用例来说是矫枉过正的。

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

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

评论

39赞 Joachim Sauer 1/17/2012
我会用“只是一个微小的安全改进”来代替“完全无用”。例如,如果您碰巧对 tmp 目录、配置错误的计算机和应用程序崩溃具有读取访问权限,则可以访问内存转储。在这种情况下,您将无法安装键盘记录器,但您可以分析核心转储。
52赞 Dan Is Fiddling By Firelight 1/17/2012
使用完后立即从内存中擦除未加密的数据被认为是最佳实践,不是因为它万无一失(事实并非如此);但因为它降低了您的威胁暴露水平。这样做并不能防止实时攻击;但是,因为它提供了一种损害缓解工具,它通过显着减少在对内存快照的追溯攻击中暴露的数据量(例如,写入交换文件的应用程序内存副本,或者从正在运行的服务器中提取的内存中读取并在其状态失败之前移动到另一个服务器)。
11赞 kingdango 1/18/2012
我倾向于同意这种回应的态度。我冒昧地提出,大多数安全漏洞都发生在比内存中的位高得多的抽象级别上。当然,在超安全防御系统中,可能会有一些场景,这可能会引起相当大的关注,但对于99%的利用.NET或Java的应用程序(因为它与垃圾回收有关)来说,认真考虑这个级别是矫枉过正的。
13赞 Peter vdL 7/24/2014
在 Heartbleed 渗透到服务器内存中,泄露了密码之后,我会将字符串“只是一个小小的安全改进”替换为“绝对必要不要将 String 用于密码,而是使用 char []”。
11赞 josefx 7/31/2014
@PetervdL heartbleed 只允许读取特定的重用缓冲区集合(用于安全关键数据和网络 I/O,而两者之间没有清除 - 出于性能原因),您不能将其与 Java 字符串结合使用,因为它们在设计上不可重用。您也不能使用 Java 读取随机内存来获取 String 的内容。导致心血流血的语言和设计问题在 Java Strings 中是不可能的。
1335赞 Konrad Garus 1/17/2012 #5

虽然这里的其他建议似乎有效,但还有另一个很好的理由。使用普通版,您更有可能意外地将密码打印到日志、监视器或其他不安全的地方。 不那么容易受到攻击。Stringchar[]

考虑一下:

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

评论

51赞 bestsss 1/18/2012
@voo,但我怀疑你会通过直接写入流和串联来记录。日志记录框架会将 char[] 转换为良好的输出
49赞 Konrad Garus 1/20/2012
@Thr4wn 的默认实现是 。 表示,其余为十六进制哈希码。toStringclassname@hashcode[Cchar[]
22赞 mauhiz 7/9/2015
有趣的想法。我想指出的是,这不会转置到 Scala 中,Scala 对数组有意义 toString。
48赞 9/1/2015
我会为此编写一个类类型。它不那么晦涩难懂,也更难意外地从某个地方经过。Password
13赞 Octavia Togami 7/12/2017
@g.rocket 数组的哈希码不依赖于数组的内容,它实际上是随机的: 在线示例
745赞 Bruno 1/17/2012 #6

引用官方文档,Java Cryptography Architecture 指南是这样说的。 密码(关于基于密码的加密,但这当然更普遍地是关于密码的):char[]String

在对象中收集和存储密码似乎是合乎逻辑的 的类型。但是,这里有一个警告: type 是不可变的,即没有定义 允许您更改(覆盖)或将使用后的内容归零。此功能使对象不适合 存储安全敏感信息,例如用户密码。你 应始终在数组中收集和存储安全敏感信息。java.lang.StringObjectStringStringStringchar

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

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

一些信息,例如社会安全号码 (SSN) 和 密码,是高度敏感的。此信息不应保留 时间超过必要的时间,也超过可能看到的地方,即使 管理员。例如,不应将其发送到日志文件和 它的存在不应该通过搜索来检测。一些瞬态的 数据可以保存在可变的数据结构中,例如 char 数组,以及 使用后立即清除。清除数据结构已减少 在对象移入时在典型 Java 运行时系统上的有效性 内存对程序员透明。

该指南还对实施和使用 不具备数据语义知识的低级库 他们正在处理。例如,低级字符串解析 库可以记录它所处理的文本。应用程序可以解析 SSN 与图书馆。这会产生 SSN 处于 可供有权访问日志文件的管理员使用。

评论

6赞 bestsss 1/23/2012
这正是我在乔恩的回答下面谈到的有缺陷/虚假的参考资料,这是一个众所周知的来源,有很多批评。
46赞 user961954 8/11/2012
@bestass您能引用参考资料吗?
17赞 SnakeDoc 6/27/2014
@bestass抱歉,但已经很好地理解了它在 JVM 中的行为方式......在以安全的方式处理密码时,有充分的理由使用代替密码。Stringchar[]String
5赞 Dawesi 8/6/2018
再一次,密码作为字符串从浏览器作为“字符串”传递到请求,而不是 char?所以无论你做什么,它都是一个字符串,在这一点上它应该作和丢弃,而不是存储在内存中?
6赞 luis.espinal 1/4/2019
@Dawesi - - 这是特定于应用程序的,但一般规则是,一旦您掌握了应该是密码的东西(明文或其他),就立即这样做。例如,您可以从浏览器获取它作为 HTTP 请求的一部分。你无法控制交付,但你可以控制自己的存储,所以一旦你得到它,就把它放在一个 char[] 中,做你需要做的事情,然后将 all 设置为 '0' 并让 gc 回收它。At which point
78赞 emboss 1/20/2012 #7

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

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

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

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

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

评论

0赞 Bruno 1/21/2012
解决此问题的一种方法是使用实际上不会将其私有内容加载到内存中的实现:例如,通过 PKCS#11 硬件令牌。也许 PKCS#11 的软件实现可以手动清理内存。也许使用像 NSS 存储这样的东西(它与 Java 中的存储类型共享其大部分实现)会更好。(OSX 密钥库)将私钥的全部内容加载到其实例中,但不需要这样做。(不确定 KeyStore 在 Windows 上做什么。PrivateKeyPKCS11KeychainStorePrivateKeyWINDOWS-MY
0赞 emboss 1/21/2012
@Bruno 当然,基于硬件的令牌不会受到这种情况的影响,但是如果您或多或少被迫使用软件密钥的情况呢?并非每个部署都有足够的预算来支付 HSM。软件密钥存储在某些时候必须将密钥加载到内存中,因此IMO至少应该再次选择随意清除内存。
0赞 Bruno 1/21/2012
当然,我只是想知道一些等同于 HSM 的软件实现在清理内存方面是否可能表现得更好。例如,当在 Safari/OSX 中使用 client-auth 时,Safari 进程实际上永远不会看到私钥,操作系统提供的底层 SSL 库直接与安全守护程序通信,该守护程序会提示用户使用密钥链中的密钥。虽然这一切都是在软件中完成的,但如果将签名委托给不同的实体(甚至是基于软件的实体),可以更好地卸载或清除内存,那么像这样的类似分离可能会有所帮助。
0赞 emboss 1/21/2012
@Bruno:有趣的想法是,一个额外的间接层来清除内存,确实可以透明地解决这个问题。为软件密钥存储编写 PKCS#11 包装器已经可以解决问题了吗?
0赞 Hakanai 6/25/2021
有趣的是,你应该说他们非常小心地使用“现在”,因为我正在查看 JDK 9 中添加的这个不错的新类,它仍然没有并且根本没有通过的选项。在Java中,似乎有很多“照我说的做,而不是我做的事”。char[]ConnectionBuilderpassword(String)char[]
47赞 Oleg Mikheev 1/24/2015 #8

char 数组与字符串相比没有任何内容,除非您在使用后手动清理它,而且我还没有看到有人真正这样做。所以对我来说,char[] 与 String 的偏好有点夸张。

看看这里广泛使用的Spring Security库,问问自己 - Spring Security的人是无能的,还是char[]密码没有多大意义。当一些讨厌的黑客抓住你的RAM的内存转储时,即使你使用复杂的方法来隐藏它们,也要确保他/她会得到所有的密码。

然而,Java 一直在变化,一些可怕的功能,如 Java 8 的字符串重复数据删除功能,可能会在您不知情的情况下实习 String 对象。但那是另一回事。

评论

3赞 Holger 3/10/2017
为什么字符串重复数据删除很可怕?它仅适用于至少两个具有相同内容的字符串,那么让这两个已经相同的字符串共享同一个数组会产生什么危险呢?或者让我们反过来问:如果没有字符串重复数据删除,那么两个字符串都具有不同的数组(内容相同)这一事实会带来什么好处?无论哪种情况,只要该内容中生存时间最长的字符串还活着,就会有一个该内容的数组是活的......
0赞 Oleg Mikheev 3/10/2017
@Holger任何你无法控制的事情都是潜在的风险......例如,如果两个用户具有相同的密码,这个出色的功能会将它们都存储在单个 char[] 中,从而明显表明它们是相同的,不确定这是否是一个巨大的风险,但仍然
1赞 Holger 3/10/2017
如果您有权访问堆内存和两个字符串实例,则无论字符串是指向同一数组还是指向两个内容相同的数组,都很容易找到。特别是,因为它无论如何都是无关紧要的。如果您此时,请获取两个密码,无论是否相同。实际错误在于使用纯文本密码而不是加盐哈希。
5赞 Holger 3/13/2017
看来,您对字符串重复数据删除有一个根本的误解。它没有“实习字符串”,它所做的只是让具有相同内容的字符串指向同一个数组,这实际上减少了包含明文密码的数组实例的数量,因为除了一个数组实例之外,所有数组实例都可以立即被其他对象回收和覆盖。这些字符串仍像任何其他字符串一样被收集。如果您了解重复数据消除实际上是由垃圾回收器完成的,那么对于仅在多个 GC 周期中幸存下来的字符串,它可能会有所帮助。
2赞 Brent Bradburn 9/18/2019
“Spring Security 的人是无能的吗”:在这种情况下,这是一个重要的问题。我以前在研究 和 之间的区别时自己也想知道这一点。即使在同时进行的初始提交中,他们也采取了不一致的方法:获取和获取 CharSequence for(调用 )。BCryptBCryptPasswordEncoderStringBcryptBCryptPasswordEncoderBcrypt
44赞 Graph Theory 4/8/2015 #9

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

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

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

评论

0赞 Mohit Kanwar 7/1/2015
但这也可能存在于 char[] 比较中,我们在密码验证中也会做同样的事情。那么 char[] 比 string 好在哪里呢?
3赞 Graph Theory 7/3/2015
你是绝对正确的,无论哪种方式都可以犯错误。考虑到 Java 中没有针对基于字符串或基于 char[] 的密码的显式密码比较方法,了解这个问题是最重要的。我想说,将 compare() 用于字符串的诱惑是使用 char[] 的一个很好的理由。这样,您至少可以控制比较的完成方式(无需扩展 String,这是一个痛苦的 imo)。
1赞 Holger 3/9/2017
除了比较明文密码无论如何都不是正确的事情之外,使用 for 的诱惑与 for 一样高。如果有人关心,有一个专用的密钥类封装了实际的密码并处理了问题——哦,等等,真正的安全包专用的密钥类,这个问答只是关于它们之外的一种习惯,比如说,使用而不是(实际算法无论如何都使用)。Arrays.equalschar[]String.equalsJPasswordFieldchar[]Stringbyte[]
1赞 Holger 3/9/2017
无论如何,与安全相关的软件都应该在拒绝登录尝试之前做一些事情,这不仅消除了定时攻击的可能性,而且还抵消了暴力破解尝试。sleep(secureRandom.nextInt())
59赞 Peter Lawrey 7/8/2015 #10

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

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

评论

3赞 chux - Reinstate Monica 8/28/2015
最好也做一些事情来防止打印我们从中获得的密码长度。'***********'
0赞 Peter Lawrey 8/28/2015
@chux您可以使用零宽度字符,尽管这可能比有用更令人困惑。如果不使用 Unsafe,则无法更改 char 数组的长度。;)
11赞 jamp 2/8/2016
由于 Java 8 的字符串重复数据删除,我认为做这样的事情可能会非常具有破坏性......您最终可能会清除程序中的其他字符串,这些字符串偶然具有相同的密码 String 值。不太可能,但有可能......
1赞 jamp 2/9/2016
@PeterLawrey 它必须使用 JVM 参数启用,但它就在那里。可以在这里阅读:blog.codecentric.de/en/2014/08/......
2赞 Holger 1/7/2020
密码很有可能仍在 的内部缓冲区中,并且由于您没有在控制台窗口中以可读形式使用。但对于大多数实际用例来说,执行的持续时间才是实际问题。例如,当连接到另一台计算机时,它会花费大量时间,并告诉攻击者现在是在堆中搜索密码的正确时机。唯一的解决方案是防止攻击者读取堆内存...ScannerSystem.console().readPassword()usePassword
32赞 Geek 7/28/2015 #11

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

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

评论

0赞 avgvstvs 2/5/2016
只有当所讨论的 JVM > 1.6 时,它们才会被 GC 处理。在 1.7 之前,所有字符串都存储在 permgen 中。
2赞 Holger 3/9/2017
@avgvstvs:“所有字符串都存储在 Permgen 中”是完全错误的。只有被隔离的字符串存储在那里,如果它们不是源自代码引用的字符串文字,它们仍然是垃圾回收的。想想看。如果字符串在 1.7 之前的 JVM 中通常从未被 GC 化,那么任何 Java 应用程序怎么可能存活超过几分钟?
0赞 avgvstvs 3/10/2017
@Holger 这是错误的。interned AND 池(以前使用的字符串池)都存储在 1.7 之前的 Permgen 中。另请参阅第 5.1 节:docs.oracle.com/javase/specs/jvms/se6/html/...JVM 总是检查它们是否是相同的参考值,并会调用 FOR YOU。结果是,每当 JVM 在 or 堆中检测到相同的字符串时,它都会将它们移动到 permgen 中。在1.7之前,我使用“爬行烫发”开发了几个应用程序。这是一个真正的问题。stringsStringStringsString.intern()constant_pool
0赞 avgvstvs 3/10/2017
所以回顾一下:在 1.7 之前,字符串从堆开始,当它们被使用时,它们被放入位于 IN permgen 的字符串中,然后如果一个字符串被多次使用,它就会被保留。constant_pool
2赞 Holger 3/10/2017
@avgvstvs:没有“以前使用过的字符串池”。你把完全不同的东西扔在一起。有一个运行时字符串池包含字符串文本和显式嵌套的字符串,但没有其他字符串。每个类都有其包含编译时常量的常量池。这些字符串会自动添加到运行时池中,但仅将这些字符串添加到运行时池中,而不是每个字符串。
25赞 Pritam Banerjee 12/10/2016 #12

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

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

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

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

评论

0赞 Pritam Banerjee 9/11/2017
@fallenidol 一点也不。仔细阅读,你会发现不同之处。
13赞 Saathvik 7/28/2017 #13

Java 中的字符串是不可变的。因此,每当创建字符串时,它都会保留在内存中,直到被垃圾回收。因此,任何有权访问内存的人都可以读取字符串的值。

如果字符串的值被修改,那么它最终将创建一个新字符串。因此,原始值和修改后的值都会保留在内存中,直到它被垃圾回收。

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

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

29赞 ACV 4/26/2018 #14

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

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赞 Yugerten 12/3/2018
ch = 空;你不能这么做
3赞 Tvde1 12/9/2019
但是还没有创建字符串并将其添加到池中?request.getPassword()
2赞 Holger 1/7/2020
ch = '0'改变局部变量 ;它对数组没有影响。无论如何,你的例子是没有意义的,你从你调用的字符串实例开始,创建一个新数组,即使你正确地覆盖了新数组,它也不会改变字符串实例,所以它比只使用字符串实例没有优势。chtoCharArray()
0赞 ACV 1/8/2020
@Holger谢谢。更正了 char 数组清理代码。
3赞 Holger 1/8/2020
您可以简单地使用Arrays.fill(pass, '0');
3赞 Neeraj 12/3/2018 #15

关于是否应该使用 String 或使用 Char[] 来实现此目的是值得商榷的,因为两者都有其优点和缺点。这取决于用户需要什么。

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

Char[] 是可变的,但它的优点是,在使用它之后,程序员可以显式清理数组或覆盖值。因此,当它被使用完毕时,它会被清理干净,没有人会知道你存储的信息。

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

15赞 Aditya Rewari 4/24/2020 #16

案例字符串:

    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
1赞 Dibsyhex 3/8/2021 #17

前面的很多答案都很棒。我假设还有另一点(如果我错了,请纠正我)。

默认情况下,Java 使用 UTF-16 来存储字符串。使用字符数组、char[] 数组,便于使用 Unicode、区域字符等。这种技术允许在存储密码时平等地尊重所有字符集,并且从此不会因字符集混淆而引发某些加密问题。最后,使用字符数组,我们可以将密码数组转换为我们想要的字符集字符串。

评论

0赞 JayK 3/12/2022
Java 中的 char 是一个 16 位值,char 数组也被假定为 UTF-16 编码。docs.oracle.com/javase/7/docs/api/java/lang/......如果你打算以不同的编码存储字符串,我认为你必须使用字节数组来代替。要获取不同编码的字符串,可以使用 String.getBytes。