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

Aha*_*med 3298 java security string passwords char

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

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

Jon*_*eet 4164

字符串是不可变的.这意味着一旦你创建了String,如果另一个进程可以转储内存,那么除了反射之外没有办法可以在垃圾收集开始之前摆脱数据.

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

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

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

  • 我的理解是,通过运行时可以完成的内存重组量,甚至`char []`的副本可能会留在内存中,并且永远不会被清除,直到该内存被重用.我不知道有哪个来源证实了这一点.无论哪种方式,我都怀疑String会在GC时间被清除,而是在对象重新分配到该内存时. (122认同)
  • @ Xeon06:在.NET中有[`SecureString`](http://msdn.microsoft.com/en-us/library/system.security.securestring.aspx)哪个更好 - 但使用起来比较痛苦. (117认同)
  • 我们是否掩盖了这样一个事实:可以读取此数据的恶意实体已经读取了对您系统的内存访问权限?看起来更令人担忧的是,做这个char []而不是String只是看起来像在海洋里撒尿. (90认同)
  • @Mark Peters:如果GC启动,在清空char数组之前,你有另一个副本.但是这个副本至少在内存的一个繁重的使用区域,所以很快被覆盖的机会很高. (34认同)
  • @corsika:正如我在答案中提到的,它正在减少一个特定的攻击向量,就是这样.它使攻击者更难. (21认同)
  • 如果操作系统在将内存交给另一个进程之前不清除内存,则操作系统存在重大安全问题!然而,从技术上讲,清除通常是通过保护模式技巧完成的,如果 CPU 损坏(例如 Intel Meldown),仍然可以读取旧的内存内容。 (10认同)
  • @PrateekPande:如果它存在于源代码中或显式保留,它只会出现在文字池中。一般来说,这些都是坏主意...... (9认同)
  • 答案涵盖了所有内容,但只是为了补充:Java字符串需要存储在池中(对于指定的情况,请参阅String的intern()方法,但也可能存在依赖于实现的池).池通常是一个固定大小的缓存,只有当它完整时才会驱逐条目,可能使用一些LRU方案.所有这些意味着Strings最终可以持续相当长的时间,而不会受到垃圾收集器的影响. (7认同)
  • 一种常见的攻击方法是运行一个分配大量内存的进程,然后扫描它以获取剩余的有用数据,如密码.该过程不需要任何神奇的访问另一个进程的内存空间; 它只是依赖于其他进程死亡而没有先清除敏感数据,操作系统也不会清除内存(或页面缓冲区),然后再将其用于新进程.清除存储在`char []`位置的密码会切断该攻击线,这在使用`String`时是不可能的. (7认同)
  • 有关在Java中使用字符串存储密码的更多信息,请访问Security Stackexchange:https://security.stackexchange.com/questions/6753/deleting-a-java-object-securely (5认同)
  • 只是一个小旁注:即使可以清除 char[] ,它仍然会泄漏密码的长度或存储在其中的任何内容,直到它被垃圾收集为止,因为数组无法调整大小。(这可以用来大大减少暴力破解密码所需的时间) (4认同)
  • @Radon8472:这不会覆盖现有字符串对象的内容。这将对新字符串的引用分配给“myPasswordString”变量。旧的字符串对象仍在内存中,并且在垃圾收集之前进行的内存转储将显示数据。 (4认同)
  • @Radon8472 显然,您没有阅读您评论的答案。它在第一句话中用粗体表示:“*字符串是不可变的*”。 (4认同)
  • 如果GC导致`char []`重新定位,旧的可能存在一段时间,但通常会在下一个GC之前"自然"被覆盖的地方."String"的问题是对事物的引用有时会比预期更长时间(例如清除`List <T>`的某些实现可能会重置计数而不删除后备存储).在所有"有用的"引用被放弃之后,这样的事情可能会导致对字符串的引用长时间保持不变. (3认同)
  • @Andy:有一个问题是,如果你不小心的话,这些价值观会被透明地复制 - 所以它仍然很难真正抹去.但我应该强调,我不是安全专家. (3认同)
  • @CodeFighter:第一个显而易见的方法:转储整个进程的内存,然后寻找任何"类似字符串"的东西.(一旦你弄清楚构成*one*字符串的内容,剩下的就很简单,因为每个字符串对象中都会引用字符串类型.) (3认同)
  • @Yeti:是的,但它不像是黑白两色.如果他们只能获取内存的快照,那么您希望减少快照可以造成的损害,或者减少可以拍摄真正严重快照的窗口. (3认同)
  • @Ted Hopp:我不能代表Linux,但Windows*会清除内存页面.目前尚不清楚为什么这应该很慢,每次分配的页面都会发生一次,甚至不需要在分配上发生,而是第一次实际使用,尽管在CPU时还预先清除了未使用的页面周期是免费的.原因在于安全性.这不会影响`malloc`和`calloc`之间的区别,因为它们不是操作系统,C库函数.所以`malloc`可能会返回同一程序早先释放的非零内存...... (3认同)
  • @JonSkeet对于那些可能迟到聚会或者只是像我这样闲暇读书的人,请注意[SecureString](http://msdn.microsoft.com/zh-cn/library/system.security.securestring.aspx)类[不应再使用](https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md)。 (3认同)
  • @Prateek Pande 字符串无论如何都无法被清除,所以它是否被字符串池引用并不重要。由于被池引用并不会阻止其垃圾回收(这只会将其从池中删除),因此只要代码处于活动状态,只有在代码中作为编译时常量存在的字符串才会保持活动状态,这应该是即使不考虑池的工作原理,这也是一个明显的问题。正如乔恩·斯基特(Jon Skeet)所说,总的来说这是一个坏主意。 (3认同)
  • 如果不变性是问题,我们可以使用`StringBuilder`或`StringBuffer`吗? (2认同)
  • 如果某个进程可以访问您的应用程序的内存,那么这已经是一个安全漏洞,对吧? (2认同)
  • @Milixyron:好吧`CharSequence`只是一个接口,`String`实现了.它没有提供任何清除方法,导致同样的问题. (2认同)
  • 如果使用字符串,密码也可能存在于文字池中并且无法手动清除。 (2认同)
  • @Trejkaz:我觉得我没有资格在那里发表评论,恐怕:( (2认同)
  • @Radon8472:除了使用不安全的代码或反射之外,您究竟如何建议用随机字符覆盖字符串的内容? (2认同)
  • @Radon8472:“错误:需要数组,但找到了字符串”。这在某些语言中可能有效,但在 Java 中无效。 (2认同)

Kon*_*rus 1187

虽然这里的其他建议似乎有效,但还有另一个好的理由.简单地说,String不小心将密码打印到日志,监视器或其他一些不安全的地方.char[]不那么脆弱了.

考虑一下:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}
Run Code Online (Sandbox Code Playgroud)

打印:

String: Password
Array: [C@5829428e
Run Code Online (Sandbox Code Playgroud)

  • @ Thr4wn`toString`的默认实现是`classname @ hashcode`.`[C`表示`char []`,其余是十六进制哈希码. (36认同)
  • 我会为此编写一个`Password`类类型.它不那么模糊,更难以意外地传递到某个地方. (32认同)
  • @voo,但我怀疑你是通过直接写入流和连接来记录的.日志框架会将char []转换为良好的输出 (31认同)
  • 有趣的想法.我想指出,这不会转换为Scala,它对数组有一个有意义的toString. (11认同)
  • 为什么有人会假设char数组将被转换为Object?我不确定为什么每个人都喜欢这个答案.假设你这样做:System.out.println("Password".toCharArray()); (7认同)
  • @ g.rocket用于阵列不依赖于所述阵列的内容的散列码,它实际上是随机的:[在线示例](https://tio.run/##TYxBDoIwFET3nmLCphCUgC7RI7hiaVxUQCxCS9oPxhDOXttIjLOYmfw/eS2f@E4NtWyrp7XDeOtEibLjxuDMhcS8gdN6N8TJxaREhd59w4K0kM3lCq4bE61jr7vSCIUkCJyQ5i6OyFJf4vh/51W8DdV9okZKBoejToZBgBiyfqF8cO3wM8vYFmzv7cCWKP8Bls3XF2s/) (7认同)
  • 鉴于Java的数组的哈希码实现不是安全哈希,在日志文件中发现`[C @ 5829428e`"的攻击者可能会强制破坏它来自的密码,因此这提供了可忽略的安全优势. (3认同)

Bru*_*uno 668

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

在类型的对象中收集和存储密码似乎是合乎逻辑的java.lang.String.但是,这里有一点需要注意:Object类型的s String是不可变的,即没有定义的方法允许你改变(覆盖)或清零String 后使用的内容.此功能使String对象不适合存储安全敏感信息,如用户密码.您应该始终在char数组中收集和存储安全敏感信息 .

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

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

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

本指南还对低级库的实现和使用有影响,这些低级库没有他们正在处理的数据的语义知识.例如,低级字符串解析库可以记录它所处理的文本.应用程序可以使用库解析SSN.这会产生一种情况,即管理员可以使用SSN访问日志文件.

  • @bestass你还能引用一下参考吗? (36认同)
  • @bestass抱歉,但是`String`非常清楚,它在JVM中的表现如何...当以安全的方式处理密码时,有充分的理由使用`char []`来代替`String`. (9认同)
  • 这正是我在Jon的答案下面谈到的有缺陷/伪造的参考,这是一个众所周知的来源,有很多批评. (3认同)
  • 再次将密码作为字符串从浏览器作为“字符串”传递给请求,而不是char?因此,无论您做什么,它都是一个字符串,在什么时候应该对其进行操作并丢弃,永远不要存储在内存中? (2认同)
  • @Dawesi-“在那一点上”-这是特定于应用程序的,但是通常的规则是在您掌握应该是密码的内容(明文或其他)后立即这样做。例如,您从浏览器获取它作为HTTP请求的一部分。您无法控制交付,但可以控制自己的存储,因此,一旦获取它,将其放入char []中,执行所需的操作,然后将其全部设置为'0'并让gc回收它。 (2认同)

ale*_*phx 343

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

  • 默认情况下不安全.如果我们正在讨论Web应用程序,大多数Web容器都会将密码传递给纯文本中的`HttpServletRequest`对象.如果JVM版本是1.6或更低,它将在permgen空间.如果它在1.7中,它将在收集之前仍然可读.(无论何时.) (11认同)
  • @Holger 参见 https://docs.oracle.com/javase/specs/jvms/se6/html/ConstantPool.doc.html#67960 “否则,会创建一个新的 String 类实例,其中包含由CONSTANT_String_info 结构体;该类实例是字符串字面量派生的结果。最后,调用新的 String 实例的 intern 方法。” 在 1.6 中,JVM 会在检测到相同序列时为您调用 intern。 (3认同)
  • @Holger,你是对的,我将常量池和字符串池混为一谈,但永久空间*仅*应用于实习字符串也是错误的。在 1.7 之前,constant_pool 和 string_pool 都驻留在 permgen 空间中。这意味着分配给堆的唯一字符串类是你所说的,`new String()` 或 `StringBuilder.toString()` 我用很多字符串常量管理应用程序,我们有很多 permgen 蠕变作为结果。直到 1.7。 (3认同)
  • @avgvstvs:嗯,字符串常量是,作为JLS的命令,总是被实现,因此实现字符串的语句最终在permgen空间中,隐式应用于字符串常量.唯一的区别是字符串常量首先在permgen空间中创建,而在任意字符串上调用`intern()`可能会导致在permgen空间中分配等效字符串.后者可以获得GC,如果没有相同内容的文字字符串共享该对象... (3认同)
  • @avgvstvs:字符串不会自动移动到permgen空间,仅适用于实习字符串。除此之外,还需要以较低的速率对permgen空间进行垃圾回收。permgen空间的真正问题是它的固定大小,这正是为什么没有人不注意对任意字符串调用`intern()`的原因。但是您是对的,因为String实例首先存在(直到收集到),之后将它们转换为char []数组不会改变它。 (2认同)

jos*_*efx 214

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

更新

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

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

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

  • 一旦你完成它就从内存中擦除未加密的数据被认为是最好的做法,不是因为它是万无一失的(它不是); 但因为它降低了你的威胁暴露水平.这样做不会阻止实时攻击; 但是因为它通过显着减少对内存快照的追溯性攻击中暴露的数据量(例如,写入交换文件的应用程序内存副本,或者已从内存中读取的数据)来提供损害缓解工具从正在运行的服务器并在其状态失败之前移动到另一个服务器). (42认同)
  • 我将"完全没用"替换为"只是一个小小的安全改进".例如,如果您碰巧具有对tmp目录的读访问权,配置错误的计算机以及应用程序崩溃,则可以访问内存转储.在这种情况下,您将无法安装键盘记录器,但您可以*分析核心转储. (32认同)
  • 在Heartbleed渗透服务器内存,泄露密码之后,我会将字符串"只是一个小的安全性改进"替换为"绝对必须不使用字符串作为密码,而是使用char []代替." (9认同)
  • 我倾向于同意这种回应的态度.我冒昧地提出大多数安全漏洞都发生在比内存中的位高得多的抽象层次.当然,在超安全防御系统中可能存在这样的情况,这可能是相当令人担忧的,但是在这个级别上认真思考对于99%的利用.NET或Java的应用程序来说是过度的(因为它与垃圾收集有关). (8认同)
  • @PetervdL heartbleed只允许读取特定的重用缓冲区集合(用于安全关键数据和网络I/O而不清除它们之间 - 出于性能原因),您不能将它与Java String结合使用,因为它们在设计上是不可重用的.您也不能使用Java读取随机内存以获取String的内容.Java Strings无法实现导致出现问题的语言和设计问题. (8认同)
  • @PetervdL那么请不要将"Heartbleed bug与Java Strings结合使用",我相信我提到的关于heartbleed的大多数都是正确的,libreSSL开发人员已经详细说明了为什么OpenSSL是一个安全噩梦."可能"是一种使用JIT中的错误来绕过范围检查中的Javas构建或对象内存的自动清零的方法,并且"可能"可能有人从远程位置触发此操作而不会使JVM崩溃,但是与heartbleed没有任何共同之处,JVM上的任何错误都不会如此简单或针对漏洞进行优化. (5认同)
  • @PetervdL它不仅不是Heartbleed bug的"特定"; 它完全不适用.我建议您不要将Heartbleed作为安全措施的理由,直到您更好地掌握其性质.感谢josefx揭示了这个经常被误解的问题. (3认同)
  • 我会说有人了解char vs sting背后的想法,然后他们更有可能在配置服务器时考虑安全性.不思考通常会引入安全漏洞. (2认同)
  • @PetervdL发生了心跳,因为有人知道重用内存比分配新的(清除的)内存便宜,你不能在Java中重用一个String,只希望没有人能够把你的char []放入重用列表中更好的性能. (2认同)
  • 你完全没有抓住要点,约瑟夫斯。关键是,heartbleed 允许远程读取进程内存,而迄今为止每个人都认为这不太可能。当与 Java 字符串结合使用时,密码很容易泄露。Char[]允许开发者清空密码,String甚至不给这个机会。如果编码器搞砸了,Char [] + heartbleed 可能会有害。字符串 + 心血 == 绝对总是有害的。 (2认同)

Sea*_*wen 85

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

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

但仅此一点并不是一个好的答案; 为什么不只是确保参考char[]String不逃避?然后没有安全问题.但事实是String物体可以intern()在理论上编辑并在恒定池中保持活力.我想使用char[]禁止这种可能性.

  • 我不会说问题是你的引用会或者不会"逃避".只是字符串在内存中保持未修改一段时间,而`char []`可以被修改,然后它是否被收集无关紧要.因为字符串实习需要为非文字明确地完成,所以它就像告诉静态字段可以引用`char []`一样. (3认同)
  • 内存中的密码不是表单帖子中的字符串吗? (3认同)

emb*_*oss 65

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

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

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

这是一个糟糕的情况,因为本文描述了这种情况如何可能被利用.

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


Pet*_*rey 50

正如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);
    }
}
Run Code Online (Sandbox Code Playgroud)

什么时候跑

please enter a password
hello world
password: '***********'
Run Code Online (Sandbox Code Playgroud)

注意:如果String的char []已被复制为GC循环的一部分,则前一个副本可能在内存中的某个位置.

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

  • 由于Java 8的String Deduplication,我认为做类似的事情可能会非常具有破坏性......你最终可能会清除程序中的其他字符串,这些字符串偶然具有相同的密码字符串值.不太可能,但可能...... (5认同)
  • 最好还做一些事情来防止打印密码的长度,我们从''***********'`获得. (2认同)
  • 密码很有可能仍然位于“Scanner”的内部缓冲区中,并且由于您没有使用“System.console().readPassword()”,密码在控制台窗口中以可读形式存在。但对于大多数实际用例,“usePassword”执行的持续时间才是实际问题。例如,当与另一台机器建立连接时,需要花费大量时间并告诉攻击者现在是在堆中搜索密码的正确时间。唯一的解决方案是防止攻击者读取堆内存...... (2认同)

Hum*_*ing 43

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

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
Run Code Online (Sandbox Code Playgroud)

参考此博客.我希望这有帮助.

  • 这是多余的.这个答案是@SrujanKumarGulla http://stackoverflow.com/a/14060804/1793718所写答案的精确版本.请不要复制粘贴或复制相同的答案两次. (9认同)
  • @Lucky不幸的是,您链接到的较早答案与该答案来自同一博客的窃,现已删除。请参阅https://meta.stackoverflow.com/questions/389144/review-a-declined-flag-for-plagiarism-based-on-additional-information。这个答案只是从同一个博客中剪切和粘贴而来,没有添加任何内容,因此它应该只是链接到原始来源的评论。 (2认同)

Gra*_*ory 41

编辑:经过一年的安全研究后回到这个答案,我意识到它实际上比较明确地比较了明文密码.请不要.使用带有salt和合理迭代次数的安全单向散列.考虑使用一个库:这个东西很难做对!

原始答案: 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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 你是绝对正确的,错误可以两种方式.知道问题是最重要的,因为在Java中没有基于字符串或基于char []的密码的明确密码比较方法.我会说使用compare()对字符串的诱惑是使用char []的一个很好的理由.这样你至少可以控制比较是如何完成的(没有扩展String,这是一个痛苦的imo). (3认同)

Ole*_*eev 34

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

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

但是,Java一直在变化,Java 8的字符串重复数据删除功能等一些可怕的功能可能会在您不知情的情况下实际执行String对象.但这是不同的对话.

  • 为什么字符串重复数据删除很可怕?它仅适用于至少有两个字符串具有相同内容的情况,那么让这两个已经相同的字符串共享同一个数组会产生什么危险呢?或者让我们反过来问:如果没有字符串重复数据删除,那么两个字符串都有一个不同的数组(内容相同)这一事实会产生什么优势?无论哪种情况,至少只要该内容的最长存活字符串还活着,就会有一个该内容的数组处于存活状态...... (3认同)
  • 看来,您对字符串重复数据删除存在根本性的误解。它不是“实习字符串”,它所做的只是让具有相同内容的字符串指向同一个数组,这实际上*减少了*包含明文密码的数组实例的数量,因为除了一个数组实例之外的所有实例都可以回收并立即被其他对象覆盖。这些字符串仍然像任何其他字符串一样被收集。如果您了解重复数据删除实际上是由垃圾收集器完成的,那么它可能会有所帮助,仅适用于在多个 GC 周期中幸存下来的字符串。 (2认同)
  • “Spring Security 人员无能吗”:在这种情况下这是一个重要的问题。我之前在研究“BCrypt”和“BCryptPasswordEncoder”之间的差异时自己也想知道这一点。即使在[同时初始提交](https://github.com/spring-projects/spring-security/commit/8565116f203b9c6186b044bc655c9d5d5b2e6450)中,他们也采取了不一致的方法:采用“String”作为“Bcrypt”并采用[“CharSequence”] (/sf/ask/926420911/)“BCryptPasswordEncoder”(调用“Bcrypt”)。 (2认同)

Gee*_*eek 30

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

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

  • @avgvstvs:“所有字符串都存储在 permgen 中”是完全错误的。只有驻留字符串存储在那里,如果它们不是源自代码引用的字符串文字,它们仍然会被垃圾收集。考虑一下。如果 1.7 之前的 JVM 中的字符串通常不会被 GC,那么 Java 应用程序如何能够存活超过几分钟呢? (2认同)
  • @avgvstvs:没有“以前使用过的字符串池”。你把完全不同的东西放在一起。有一个运行时字符串池包含字符串文字和显式驻留字符串,但没有其他。每个类都有其包含*编译时*常量的常量池。这些字符串会自动添加到运行时池中,但*仅这些*,而不是每个字符串。 (2认同)

Pri*_*jee 17

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

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

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

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

  • 这只是Skeet答案毫无意义的重演. (21认同)

ACV*_*ACV 17

字符串是不可变的,它将转到字符串池.一旦编写,就无法覆盖.

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';
 }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 但是 request.getPassword() 不是已经创建了字符串并将其添加到池中吗? (3认同)
  • 您可以简单地使用“Arrays.fill(pass, '0');” (3认同)
  • ch = 空;你不能这样做 (2认同)
  • `ch = '0'` 更改局部变量 `ch`;它对阵列没有影响。无论如何,您的示例毫无意义,您从调用“toCharArray()”的字符串实例开始,创建一个新数组,即使您正确覆盖新数组,它也不会更改字符串实例,因此它没有优势不仅仅是使用字符串实例。 (2认同)

Saa*_*vik 12

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

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

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


Adi*_*ari 6

案例字符串:

    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
Run Code Online (Sandbox Code Playgroud)

案例字符数组:

    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
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

378097 次

最近记录:

6 年,5 月 前