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

在...创建的问题 Fri, Jan 13, 2017 12:00 AM

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

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

    
3184
  1. 你能引用一下这个建议的来源吗?我无法想到char[]更安全的原因,除非是最业余的威胁。
    2012-01-16 14:23:56Z
  2. @ Viruzzo看看javadocs。 getText() JPasswordField方法因为安全原因而被弃用getPassword()“。
    2012-01-16 14:32:20Z
  3. char []?为什么不用byte []?在任何线程转储/堆分析器中,您都可以看到字符串,如果包含密码,则很容易注意到。对于char []来说,它更难,但是,如果你的堆分析器将逐个显示字符,它将与String相同。对于byte [],它不太可能。
    2012-01-17 22:38:58Z
  4. 只是为了提供一个有效的方案,这可能很重要,在法医学中通常的做法是确保在转储内存之前不要关闭任何需要调查的计算机,以防万一他们正在使用软件加密。即使是最好的加密系统也需要维持一个能够解密的状态。
    2013-06-15 11:23:10Z
  5. @ scottlafoy两个char[]String对象将通过引用传递;个人char值不会。当对象通过 value 而不是引用传递时,会创建多个副本。通常,这不是问题,因为这些对象通常设计得很小。
    2013-06-15 11:25:34Z
  6. 醇>
    18答案                              18 跨度>                         

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

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

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

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

        
    4046
    2018-04-20 17:50:27Z
    1. 我的理解是,通过运行时可以完成的内存重组量,即使char[]的副本也可能留在内存中,直到该内存永远不会被清除被重用了。我不知道有哪个来源证实了这一点。无论哪种方式,我都怀疑String会在GC时间被清除,而是在对象重新分配到该内存时。
      2012-01-16 14:30:09Z
    2. @ Mark Peters:如果GC启动,在清空char数组之前,你有另一个副本。但是这个副本至少在内存的繁重使用区域中存在,因此很快被覆盖的可能性很高。
      2012-01-16 14:32:25Z
    3. @ Xeon06:在.NET中有 SecureString 哪个更好 - 但使用起来比较痛苦。
      2012-01-16 20:42:39Z
    4. 我们是否掩盖了这样一个事实:可以读取此数据的恶意实体已经读取了对您系统的内存访问权限?看起来更令人担忧的是,做这个char []而不是String就像在海洋里撒尿一样。
      2015-06-12 19:40:56Z
    5. @ corsika:正如我在答案中提到的,它正在减少一个特定的攻击向量,就是这样。它使攻击者更难。
      2015-06-12 19:42:19Z
    6. 醇>

    虽然这里的其他建议似乎有效,但还有另外一个好理由。使用普通的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
    
        
    1161
    2012-01-19 18:11:54Z
    1. @ voo,但我怀疑你是否通过直接写入来记录流和连接。 logging framework会将char []转换为良好的输出
      2012-01-18 01:14:03Z
    2. @ Thr4wn toString的默认实现是classname@hashcode[C代表char[],其余为十六进制哈希码。
      2012-01-20 12:39:51Z
    3. 有趣的想法。我想指出,这不会转换为Scala,它对数组有一个有意义的toString。
      2015-07-09 08:36:07Z
    4. 我为此写了一个Password类类型。它不那么模糊,更难以意外地传递到某个地方。
      2015-08-31 17:32:12Z
    5. 为什么有人会假设char数组将被转换为Object?我不确定为什么每个人都喜欢这个答案。假设您这样做:System.out.println(“Password”.toCharArray());
      2016-08-22 13:23:01Z
    6. 醇>

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

      

    在对象中收集和存储密码似乎是合乎逻辑的   类型java.lang.String。但是,这里有一个警告:Object秒   类型String是不可变的,即没有定义的方法   允许您更改(覆盖)或清零String的内容   使用后。此功能使String对象不适合   存储安全敏感信息,如用户密码。您   应始终收集并存储安全敏感信息    而是char阵列。

    Java编程语言4.0版安全编码指南的准则2-2 也说了类似的东西(尽管它最初是在c记录的基础上):

      

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

         

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

         

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

        
    652
    2012-01-17 03:20:57Z
    1. 这正是我在Jon的回答中谈到的有缺陷的/伪造的参考,这是一个众所周知的来源,有很多批评。
      2012-01-22 20:45:20Z
    2. @ bestass你能引用一个参考吗?
      2012-08-11 11:27:37Z
    3. @ bestass抱歉,但String非常清楚它在JVM中的行为......在处理密码时有充分的理由使用char[]代替String以安全的方式。
      2014-06-27 00:50:21Z
    4. 再次将密码作为字符串从浏览器传递给请求,因为'string'不是char?所以无论你做什么,它都是一个字符串,此时它应该被操作并丢弃,从不存储在内存中?
      2018-08-05 19:50:24Z
    5. @ Dawesi - At which point - 这是应用程序特定的,但一般规则就是在你掌握一些应该是密码的东西时这样做(明文或其他)。例如,您可以从浏览器中获取它作为HTTP请求的一部分。你无法控制交付,但你可以控制你自己的存储,所以一旦你得到它,把它放在一个char [],做你需要做的事情,然后将所有设置为'0'然后让gc收回它。
      2019-01-03 19:25:05Z
    6. 醇>

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

        
    334
    2012-03-10 17:50:52Z
    1. 默认情况下不安全。如果我们正在讨论Web应用程序,大多数Web容器都会以明文形式将密码传递给HttpServletRequest对象。如果JVM版本是1.6或更低,它将在permgen空间。如果它在1.7中,它将在收集之前仍然可读。 (无论何时。)
      2016-02-05 15:19:41Z
    2. @ avgvstvs:字符串不会自动移动到permgen空间,仅适用于实习字符串。除此之外,permgen空间也受到垃圾收集,只是速度较低。 permgen空间的真正问题在于它的固定大小,这正是为什么没有人应该盲目地在任意字符串上调用intern()的原因。但你是对的,因为String实例首先存在(直到收集)并且之后将它们转换为char[]个数组并不会改变它。
      2017-03-09 10:04:48Z
    3. @ Holger见 docs.oracle.com/javase/specs/jvms/se6/html/... ”否则,将创建一个类String的新实例,其中包含CONSTANT_String_info给出的Unicode字符序列结构;该类实例是字符串文字派生的结果。最后,调用新String实例的实习方法。“在1.6中,JVM会在检测到相同序列时为您调用实习生。
      2017-03-09 16:43:12Z
    4. @ Holger,你是对的我将常量池和字符串池混为一谈,但是permgen space only 应用于实习字符串也是错误的。在1.7之前,constant_pool和string_pool都位于permgen空间中。这意味着分配给堆的唯一一类字符串就像你说的那样,new String()StringBuilder.toString()我管理了具有大量字符串常量的应用程序,结果我们有很多permgen creep。直到1.7。
      2017-03-11 18:43:21Z
    5. @ avgvstvs:好吧,字符串常量是,作为JLS的命令,总是被实现,因此实现字符串的语句最终在permgen空间中,隐含地应用于字符串常量。唯一的区别是字符串常量首先在permgen空间中创建,而在任意字符串上调用intern()可能会导致在permgen空间中分配等效字符串。如果没有相同内容的文字字符串共享该对象,后者可以获得GC;
      2017-03-13 11:20:49Z
    6. 醇>

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

    更新强>

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

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

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

        
    206
    2015-07-19 22:52:01Z
    1. 我将“完全没用”替换为“只是一个小的安全性改进”。例如,如果您碰巧具有对tmp目录的读访问权,配置错误的计算机以及应用程序崩溃,则可以访问内存转储。在这种情况下,您将无法安装键盘记录器,但您可以分析核心转储。
      2012-01-16 16:23:17Z
    2. 一旦你完成它就从内存中擦除未加密的数据被认为是最好的做法,不是因为它是万无一失的(事实并非如此);但因为它降低了你的威胁暴露水平。这样做不会阻止实时攻击;但是因为它有助于减轻损害工具通过显着减少内存快照上的追溯攻击中暴露的数据量(例如,写入交换文件的应用程序内存副本,或者从正在运行的服务器中取出的内存读取并移至在其状态失败之前的另一个。)
      2012-01-16 16:26:12Z
    3. 我倾向于同意这种回应的态度。我冒昧地提出大多数安全漏洞都发生在比内存中的位高得多的抽象层次。当然,在超安全防御系统中可能存在这样的情况,这可能是相当令人担忧的,但是在这个级别上认真思考对于99%的利用.NET或Java的应用程序来说是过度的(因为它与垃圾收集有关)。
      2012-01-17 18:18:47Z
    4. 在Heartbleed渗透服务器内存,泄露密码之后,我会将字符串“只是一个小小的安全性改进”替换为“绝对不必使用字符串作为密码,但是使用char []代替。“
      2014-07-24 05:15:05Z
    5. @ PetervdL heartbleed只允许读取特定的重用缓冲区集合(用于安全关键数据和网络I /O而不在其间清除 - 出于性能原因),你不能将它与Java String结合使用,因为它们在设计上是不可重用的。您也不能使用Java读取随机内存以获取String的内容。 Java Strings无法实现导致出现问题的语言和设计问题。
      2014-07-30 17:39:33Z
    6. 醇>
    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 []还不够,但您需要删除内容才能更安全。我还建议使用散列或加密密码而不是纯文本,并在验证完成后立即从内存中清除它。

          
    135
    2017-06-06 08:26:12Z
    1. “因为任何有权访问内存转储的人都可以以明文形式找到密码”如何获取内存转储的访问权并在那里找到密码?
      2015-07-01 05:44:13Z
    2. @ MohitKanwar通过jmap或jvisualvm触发内存转储。如果设置了heapdumponoutofmemoryerror,则可能会通过导致内存不足来触发远程堆转储。一旦你有转储,你可以使用jvisualvm或eclipse内存分析器工具或命令行工具来检查它,并检查所有对象及其值。
      2016-08-05 17:22:29Z
    3. 醇>

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

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

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

        
    79
    2012-01-16 14:27:54Z
    1. 我不会说问题是你的引用将会或不会“逃避”。只是字符串将在内存中保持一段时间不被修改,而char[]可以被修改,然后它是否被收集无关紧要。因为字符串实习需要为非文字明确地完成,所以它就像告诉静态字段可以引用char[]一样。
      2012-01-16 17:26:50Z
    2. 内存中的密码不是表单中的字符串吗?
      2018-08-05 19:43:17Z
    3. 醇>

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

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

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

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

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

        
    62
    2012-01-19 19:39:47Z
    1. 解决这个问题的一种方法是使用PrivateKey的实现,它实际上并没有在内存中加载其私有内容:例如,通过PKCS#11硬件令牌。也许PKCS#11的软件实现可以负责手动清理内存。也许使用像NSS商店(它的大多数实现与Java中的PKCS11商店类型共享)更好。 KeychainStore(OSX密钥库)将私钥的完整内容加载到其PrivateKey实例中,但它不需要。 (不确定WINDOWS-MY KeyStore在Windows上做了什么。)
      2012-01-21 13:31:55Z
    2. @Bruno当然,基于硬件的令牌不会受此影响,但是你或多或少被迫使用软件密钥的情况呢?并非每个部署都有预算来支付HSM。软件密钥库在某些时候必须将密钥加载到内存中,因此我们至少应该给IMO选项,以便随意清除内存。
      2012-01-21 14:18:47Z
    3. 当然,我只是想知道某些软件实现是否等同于HSM在清理记忆方面做得更好。例如,当使用带有Safari /OSX的client-auth时,Safari进程实际上从未看到私钥,操作系统提供的底层SSL库直接与安全守护进程通信,提示用户使用密钥链中的密钥。虽然这都是在软件中完成的,但如果将签名委托给可以更好地卸载或清除内存的其他实体(甚至是基于软件),这样的类似分离可能会有所帮助。
      2012-01-21 14:29:30Z
    4. @Bruno:有趣的想法是,一个负责清理内存的额外间接层确实可以透明地解决这个问题。为软件密钥库编写PKCS#11包装器可能已经成功了吗?
      2012-01-21 14:45:09Z
    5. 醇>

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

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

        
    47
    2015-07-08 09:34:03Z
    1. 最好还做些什么来阻止我们从'***********'打印密码的长度。
      2015-08-27 19:21:55Z
    2. @ chux你可以使用零宽度字符,虽然这可能比使用更令人困惑。如果不使用Unsafe,就无法更改char数组的长度。 ;)
      2015-08-27 20:16:36Z
    3. 由于Java 8的字符串重复数据删除,我认为做这样的事情可能会非常具有破坏性......你最终可能会清除程序中的其他字符串密码字符串的值相同。不太可能,但可能......
      2016-02-08 14:48:49Z
    4. @ jamp Java 9应该具有此功能以及紧凑字符串(即使用byte []])。我不认为这发生在Java 8中。
      2016-02-08 15:33:27Z
    5. @PeterLawrey必须使用JVM参数启用它,但它就在那里。可以在这里阅读: blog.codecentric.de/en/2014/08/...
      2016-02-08 16:20:53Z
    6. 醇>

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

    1。由于字符串在Java中是不可变的,如果将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它,并且因为字符串在字符串池中用于可重用性很可能会长时间保留在内存中,从而构成安全威胁。

    由于任何有权访问内存转储的人都可以以明文形式找到密码,因此您应该始终使用加密密码而不是纯文本。由于字符串是不可变的,因此无法更改字符串的内容,因为任何更改都会产生新的字符串,而如果使用char [],您仍然可以将所有元素设置为空或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。

    2。 Java本身建议使用JPasswordField的getPassword()方法,该方法返回char [],而不是弃用的getText()method以明文形式返回密码,说明安全原因。遵循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
    

    参考此博客 。 我希望这会有所帮助。

        
    39
    2018-11-19 14:34:14Z
    1. 这是多余的。这个答案是@SrujanKumarGulla stackoverflow.com/a/14060804/1793718 所写答案的精确版本。请不要复制粘贴或复制相同的答案两次。
      2016-11-03 07:57:15Z
    2. 1.)System.out.println(“字符密码:”+ charPassword)之间的区别是什么?和2.)System.out.println(charPassword);因为它提供与输出相同的“未知”。
      2017-07-28 11:13:36Z
    3. 醇>

    编辑:在经过一年的安全研究后回到这个答案,我意识到这实际上是一个相当不幸的暗示,你实际上会比较明文密码。请不要。 使用带有盐和合理迭代次数的安全单向哈希。考虑使用一个库:这个东西很难做对!

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

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

    38
    2017-05-23 12:26:29Z
    1. 但是在char []比较中也可以存在,我们在密码验证中也会做同样的事情。那么char []怎么比字符串更好?
      2015-07-01 05:41:56Z
    2. 你是绝对正确的,错误可以两种方式进行。知道问题是最重要的,因为在Java中没有基于字符串或基于char []的密码的明确密码比较方法。我会说使用compare()对字符串的诱惑是使用char []的一个很好的理由。这样你至少可以控制比较的方式(没有扩展String,这是一个痛苦的事情)。
      2015-07-02 21:03:09Z
    3. 除了比较明文密码不是正确的事情,使用Arrays.equals char[]的诱惑高达String.equals。如果有人关心,有一个专用的密钥类封装了实际密码并处理问题 - 哦等等,真正的安全包专用密钥类,这个Q& A只是一个习惯在他们之外,比方说,例如JPasswordField,使用char[]而不是String(其中实际算法无论如何都使用byte[])。
      2017-03-09 10:22:44Z
    4. 安全相关的软件应该在拒绝登录尝试之前执行类似sleep(secureRandom.nextInt())的操作,这不仅可以消除定时攻击的可能性,还可以抵御暴力攻击。
      2017-03-09 10:24:40Z
    5. 醇>

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

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

    然而,Java一直在变化,并且有一些可怕的功能,例如字符串重复数据删除功能Java 8 可能在您不知情的情况下实习String对象。但这是不同的对话。

        
    33
    2017-05-23 12:18:26Z
    1. 为什么String-deduplication可怕?它只适用于至少有两个字符串具有相同内容的情况,那么让这两个已经相同的字符串共享同一个数组会产生什么危险呢?或者让我们反过来说:如果没有字符串重复数据删除,那么两个字符串都有一个不同的数组(内容相同)会产生什么好处呢?在任何一种情况下,至少只要该内容的最长生命字符串是活着的,就会有一个内容的数组存活...
      2017-03-09 17:45:27Z
    2. @ Holger任何不受控制的东西都是潜在的风险......例如,如果两个用户拥有相同的密码,这个精彩的功能会将它们存储在单个字符中[]显然他们是相同的,不确定这是一个巨大的风险,但仍然
      2017-03-09 18:28:56Z
    3. 如果您可以访问堆内存和两个字符串实例,那么字符串是指向同一个数组还是指向相同内容的两个数组无关紧要,每个都很容易找到。特别是,无论如何它都是无关紧要的。如果你在这一点,你抓住两个密码,无论是否相同。实际的错误在于使用明文密码而不是盐渍哈希。
      2017-03-09 18:35:04Z
    4. @ Holger验证密码它必须在内存中以明文形式存在一段时间,即10ms,即使只是为了创建盐渍哈希。然后,如果发生内存中存在两个相同的密码,即使是10毫秒,重复数据删除也可能发挥作用。如果它真正实现字符串,它们会在内存中保存更长时间。几个月没有重启的系统会收集很多这些。只是理论化。
      2017-03-10 21:40:43Z
    5. 看来,你对String Deduplication有一个基本的误解。它不是“实习字符串”,它只是让具有相同内容的字符串指向同一个数组,实际上减少包含明文密码的数组实例的数量,除了一个数组实例可以立即回收并被其他对象覆盖。仍然像任何其他字符串一样收集这些字符串。如果您了解重复数据删除实际上是由垃圾收集器完成的,那么对于仅在多个GC周期中存活的字符串,可能会有所帮助。
      2017-03-13 11:30:41Z
    6. 醇>

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

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

        
    29
    2015-07-28 10:46:40Z
    1. 如果有问题的JVM是>它们只会被GC。 1.6。在1.7之前,所有字符串都存储在permgen中。
      2016-02-05 15:15:30Z
    2. @ avgvstvs:“所有字符串都存储在permgen中”只是错误的。只有实习字符串存储在那里,如果它们不是源自代码引用的字符串文字,它们仍然是垃圾收集的。考虑一下。如果在1.7之前的JVM中字符串通常从不GCed,那么任何Java应用程序如何能够存活超过几分钟?
      2017-03-09 10:28:15Z
    3. @ Holger这是假的。实习stringsString池(先前使用的字符串池)在1.7之前存储在Permgen中。另请参阅第5.1节: docs.oracle。 com /javase /specs /jvms /se6 /html /... JVM始终检查Strings以查看它们是否是相同的参考值,并将为您拨打String.intern()。结果是,每次JVM在constant_pool或堆中检测到相同的字符串时,它都会将它们移动到permgen中。我使用“蠕动的permgen”进行了几次应用,直到1.7。这是一个真正的问题。
      2017-03-09 16:36:36Z
    4. 回顾一下:直到1.7,字符串在堆中开始,当它们被使用时,它们被放入位于IN permgen的constant_pool中,然后如果字符串是不止一次使用,它会被实习。
      2017-03-09 16:39:51Z
    5. @ avgvstvs:没有“先前使用的字符串池”。你把完全不同的东西扔在一起。有一个运行时字符串池包含字符串文字和明确的实习字符串,但没有其他字符串。每个类都有它的常量池,包含编译时常量。这些字符串会自动添加到运行时池中,但只有这些,而不是每个字符串。
      2017-03-09 17:34:37Z
    6. 醇>

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

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

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

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

        
    16
    2018-01-05 23:40:12Z
    1. 这只是对Skeet答案毫无意义的重复。
      2017-01-31 10:22:04Z
    2. @ fallenidol完全没有。仔细阅读,你会发现差异。
      2017-09-11 06:02:41Z
    3. 醇>

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

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

        
    16
    2018-01-08 20:42:18Z
    1. 2019-04-14 09:03:47Z
    2. 醇>

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

    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
    2018-12-02 23:13:16Z
    1. ch = null;你不能这样做
      2018-12-02 22:56:37Z
    2. @ Yugerten好点。它只是伪代码,但是是
      2018-12-02 23:13:04Z
    3. 醇>

    java中的字符串是不可变的。因此,每当创建一个字符串时,它将保留在内存中,直到它被垃圾收集。所以任何有权访问内存的人都可以读取字符串的值。点击 如果修改了字符串的值,那么它将最终创建一个新字符串。因此,原始值和修改后的值都会保留在内存中,直到它被垃圾回收。搜索结果 使用字符数组,一旦提供密码的目的,就可以修改或删除数组的内容。修改后甚至在垃圾收集开始之前,内存中将找不到数组的原始内容。

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

        
    12
    2017-07-28 06:33:06Z

    关于你是否应该使用String或使用Char [] fo,这是值得商榷的这个目的因为两者都有其优点和缺点。这取决于用户的需求。

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

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

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

        
    1
    2019-05-09 11:23:32Z
来源放置 这里