在Swing中,password字段有一个getPassword()(返回char[])方法,而不是通常的getText()(返回String)方法。类似地,我也遇到过不使用String来处理密码的建议。
为什么String对密码安全构成威胁?使用char[]感觉不方便。
- 你能举出这个建议的出处吗?除了最业余的威胁外,我实在想不出比这更安全的理由。
- 看看javadocs。JPasswordField的getText()方法因"安全理由"而被反对,而赞成getPassword()。
- @dogbane我只是对"安全原因"持怀疑态度;Jon Skeet的回答澄清了一些事情。
- 如果您将ActionListener添加到JPasswordField中(可能是通常的情况),您将获得作为操作"command"的密码String。它还可能被卡在底层的Document中。但是,在你的雇主毫无意义的清单上,这是一个毫无意义的标记。
- char[]吗?为什么不byte[]吗?在任何线程转储/堆分析器中,您都可以看到字符串,并且很容易注意到其中是否包含密码。对于char[]来说比较困难,但是,如果您的堆分析器将逐个显示char,那么它将与String相同。对于byte[],不太可能。
- 另外,如果您看到控制台类(Jdk 6),请访问——> docs.oracle.com/javase/7/docs/api/java/io/…,你会发现readPassword方法总是返回一个char[],,
- 我不是专家,但它可能与字符串是对象,并通过引用传递,从而在内存中创建多个实例有关吗?
- 为了提供一个有效的场景,这可能很重要,这是取证领域的一个常见实践,以确保没有需要调查的计算机在内存被转储之前被关闭,以防它们正在使用软件加密。即使是最好的加密系统也需要维护能够解密的状态。
- @scottlafoy char[]和String对象都将通过引用传递;单独的char值将不会。只有当对象通过值而不是引用传递时,才会创建多个副本。一般来说,这不是问题,因为这类对象通常设计得很小。
- 这个建议是不可能实现的,而且对于标准Java servlet的实用工具也存在问题:stackoverflow.com/questions/15016250/…
- 在我看来,关键不在于字符数组本质上更安全,而在于一旦使用了它们,就可以删除它们(覆盖字符),这样它们就不会留在内存中。你不能对字符串做同样的事情,它们是不可变的。
- 对于现代密码消费API,我将使用CharSequence,然后您可以使用String或StringBuilder。后者可以被覆盖。不幸的是char[]没有实现CharSequence。虽然有一个char数组很好,但是您可以经常清除高级API只提供给您字符串,并且在使用string.toCharArray()只满足API之前,您可以直接传递字符串。
- 老实说,如果您是一名开发人员,并且对敏感数据持有"不太可能,所以我不会防范它"的态度,那么您可能需要寻找一个新的职业。我们不能防范每一个所谓的安全漏洞,但是……来吧……在一些地方使用char[]而不是字符串不会损害您的统计数据,我相信您的管理人员和客户不会在意。对于任何从事开发工作一段时间的人来说,他们都知道安全是一个不断变化的场景,不可能做到"完美"。至少遵循最佳实践。哦,不要因为你没有期望就变得懒惰。
字符串是不可变的。这意味着,一旦创建了String,如果另一个进程可以转储内存,那么(除了反射之外)就没有办法在垃圾收集开始之前删除数据。
使用数组,您可以在处理完数据之后显式地擦除数据。您可以使用任何您喜欢的方法覆盖数组,并且密码不会出现在系统中的任何位置,甚至在垃圾收集之前也不会出现。
因此,是的,这是一个安全问题——但是即使使用char[]也只会减少攻击者的机会窗口,而且只针对这种特定类型的攻击。
正如注释中所指出的,垃圾收集器移动的数组可能会在内存中留下零散的数据副本。我相信这是特定于实现的——垃圾收集器可能会清除所有内存,以避免这类事情。即使是这样,在char[]包含实际字符作为攻击窗口的时间内仍然存在。
- 我的理解是,由于运行时可以进行大量的内存重组,甚至char[]的副本也可能留在内存中,并且在重用该内存之前永远不会被清除。但我不知道有什么资料能证实这一点。无论如何,我怀疑字符串在GC时不会被清除,而是在对象重新分配到内存时才会被清除。
- @Mark Peters:如果GC开始工作,在清空字符数组之前,您还有另一个副本。但是,这个副本至少在内存的大量使用区域中,所以它很可能很快被覆盖。
- @Xeon06: . net中有一个SecureString,它更好,但是使用起来比较麻烦。
- 如果一个GC导致一个char[]被重新定位,那么旧的一个可能会存在一段时间,但是通常会在下一次GC之前被"自然"覆盖。String的一个问题是,对事物的引用有时会停留得比预期的长得多(例如,清除List的一些实现可能会重置计数,而不会擦除备份存储)。这样做可能会导致在放弃所有"有用的"引用之后,对字符串的引用仍然存在很长时间。
- 如果不变性是问题所在,我们可以使用StringBuilder或StringBuffer来代替吗?
- 有一个问题是,如果你不小心的话,这些值会被透明地复制,所以仍然很难完全删除。但我要强调的是,我不是安全专家。
- 答案涵盖了所有内容,但只是为了补充:Java字符串必须存储在池中(对于指定的情况,请参阅String的intern()方法,但也可能存在实现相关的池)。池通常是一个固定大小的缓存,它只在填满时才清除条目,可能使用一些LRU方案。所有这些都意味着字符串最终可以持续相当长的时间,而不会受到垃圾收集器的影响。
- 我们是否忽略了这样一个事实,即能够读取此数据的恶意实体已经读取了对系统的内存访问?这似乎是一个更大的问题,做这个char[]而不是String就像是在大海里撒尿。
- 正如我在答案中提到的,它减少了一个特定的攻击向量,仅此而已。这让攻击者更难对付。
- 存储在stringpool中的密码(或任何字符串)如何检索?我尝试了很多论坛,但是没有找到一个方法。
- @CodeFighter:第一个显而易见的方法:转储整个进程的内存,然后寻找任何"类似字符串"的东西。(一旦确定了一个字符串的组成,剩下的部分就很简单了,因为每个string对象中都有一个对string类型的引用。)
- 有关在Java中存储字符串密码的更多信息,请访问Security Stackexchange: Security .stackexchange.com/questions/6753/…
- 如果进程访问了应用程序的内存,那么这已经是安全漏洞了,对吗?
- 是的,但又不是黑白分明。如果它们只能获得内存快照,那么您希望减少快照可能造成的损害,或者减少在此期间可以获取真正严重快照的窗口。
- CharSequence呢?它几乎和String一样吗?我刚刚看到,CharSequence使用的方法更少。
- CharSequence只是一个接口,String实现了它。它没有提供任何清除它的方法,导致了同样的问题。
- 一种常见的攻击方法是运行一个进程,该进程分配大量内存,然后扫描它,寻找遗留的有用数据,比如密码。进程不需要对另一个进程的内存空间进行任何神奇的访问;它只是依赖于其他进程在没有首先清除敏感数据的情况下死亡,而操作系统也没有在新进程可用内存(或页面缓冲区)之前清除内存(或页面缓冲区)。清除存储在char[]位置的密码可以切断这条攻击线,这在使用String时是不可能的。
- 如何从垃圾收集中抓取字符串?
- 使用工具转储进程的内存。它们存在于几乎每个平台。是的,首先您需要有相当多的访问权限,但是它仍然增加了您可以进行的攻击。
- @Ted Hopp:所以唯一的障碍是找到一个在新进程可用之前不清除内存的操作系统……
- @Holger -你是说像Windows或Linux?我们不要忘记硬盘上的分页区域。除非有特殊的硬件支持,否则任何为每个进程清除所有这些的操作系统都会慢得像爬行一样。这取决于获取内存来清除它的过程,如果这是它所需要的。(例如,malloc和calloc之间的区别是,您得到的是未初始化的内存块,还是已清除的内存块。数据嗅探程序可能不会使用calloc。)
- @Ted Hopp:我不能代表Linux,但Windows确实清除内存页面。现在还不清楚为什么这个过程会很慢,每个分配的页面只执行一次,甚至不需要在分配时执行,而是第一次实际使用,不过在CPU周期空闲时,还会提前清除未使用的页面。原因就是安全。这并不影响malloc和calloc之间的区别,因为它们不是操作系统C库函数。所以malloc可能会返回非零内存,这是相同的程序早些时候释放的…
- @Holger——有趣。看来Windows已经加强了不少安全措施。CVE数据库包含大量Windows XP和Windows 7的内存漏洞。还有一篇关于如何在Windows和Linux上读写其他进程内存的有趣博客文章。
- @Ted Hopp:这个博客使用对其他进程的进程内存的显式访问,如果您试图读取特权进程的内存,那么这个进程应该(当然是)接受访问检查,并且不会工作。这与试图在新分配的内存中意外地找到遗留的工件不同。据我所知,安全漏洞也与分配中的构件无关,然而,Windows中还有足够多的其他漏洞……
- 嗨,@JonSkeet,当你谈到(除了反射)没有办法在垃圾收集开始之前删除数据时。,你说的原因是字符串是不可变的,它与run-time constant pool有关吗?或者你有医生对此做深入的解释吗?
- @JIEWANG:不是特别——我们这里说的不是常数……我们讨论的字符串可能是由用户输入创建的。
- @JonSkeet—在这个问题stackoverflow.com/questions/22397861/…中,大多数答案都说字符串的不变性对安全性非常重要,这与您的答案正好相反。它们的"推理"——字符串通常用于存储连接、密码等,而不变性可以防止它们被更改。他们没有提到可变字符串值如何被更改可能是一个安全问题。这里没有提到可以用来更改可变字符串的技术。你能澄清一下吗?谢谢。
- @testerjoe2:我想说的是不同的担忧。字符串的不变性是伟大的,以确保在一个受约束的沙箱(禁止反射等),您可以传递字符串到任意代码,而不用担心他们被突变。char[]很适合擦除内存内容,以防止以后未加保护的代码对其进行"监视"。
- 然后,您将如何处理一个密码,然后在请求范围内从浏览器以字符串的形式提交给黑客(也就是说,它在内存中是以字符串的形式提交的)?这难道不能说明这一点吗?
- 是的,不可能总是把它当作一个char[]。
- 已经有很多评论了,但是还有一个安全要求,即不应该记录敏感数据。例如,在服务器节点崩溃的情况下,通常会得到一个堆转储。您可能能够在该堆转储中找到密码字符串,请参见Eclipse MAT。但是,如果使用char[]并在读取之后覆盖所有元素,则可以有效地将密码从内存中删除,从而从堆转储中删除。堆转储通常存储在公司的NAS中,所以……这也需要对其他敏感数据进行处理,比如信用卡号码等。
- @JonSkeet,对于那些可能会迟到的人,或者像我一样只是休闲阅读的人,请注意,SecureString类不应该再使用了。
虽然这里的其他建议似乎是有效的,但还有一个很好的理由。使用普通的String,您很有可能不小心将密码打印到日志、监视器或其他不安全的地方。char[]不那么脆弱。
考虑一下:
1 2 3 4 5 6 7
| public static void main (String[] args ) {
Object pw ="Password";
System. out. println("String:" + pw );
pw ="Password". toCharArray();
System. out. println("Array:" + pw );
} |
打印:
- @voo,但是我怀疑你是否会通过直接写日志来进行流和连接。日志框架将把char[]转换成良好的输出
- toString的默认实现是classname@hashcode。[C表示char[],其余为十六进制哈希码。
- 有趣的想法。我想指出的是,这不是转置到Scala,它有一个有意义的数组toString。
- 我将为此编写一个Password类类型。它不那么模糊,而且很难意外地通过某个地方。
- 我同意bestsss的观点。日志框架将决定如何打印char数组。我认为在大多数情况下都会打印一个char数组。
- 为什么有人会假设char数组将被转换为对象?我不知道为什么每个人都喜欢这个答案。假设您这样做了:System.out.println("Password".toCharArray());
- 考虑到Java的数组hashcode实现不是一个安全散列,在日志文件中发现[C@5829428e的攻击者可能会强行输入它来自的密码,所以这提供了一个微不足道的安全好处。
- @g。rocket的hashcode对于一个数组不依赖于数组的内容,它实际上是随机的:在线例子
- 这并不是推荐使用char[]而不是String的原因
- 为什么一开始存储的密码是未加密的?它是在内存中从一个请求接收的(存储在内存中作为字符串??),然后应该立即进行散列、比较和丢弃,或者,如果另一个系统应该从已经加密的外部保险库获得,然后使用旋转密钥解密?我是不是漏掉了什么?
- 密码可能不会未经加密而存储——事实上,所存储的是一个安全加密并哈希的字节序列,这些字节是由一个经过1)非加密、2)加盐和3)加长的密码生成的。让我们把这个字节序列称为c。然而,在这一行的某个地方,一个明文可能被处理为salt/nonce/length然后加密,以便与c进行比较。
引用一份官方文件,Java Cryptography Architecture guide说这是关于char[]和String的密码(关于基于密码的加密,但这当然是关于密码的更普遍的情况):
It would seem logical to collect and store the password in an object
of type java.lang.String. However, here's the caveat: Objects of
type String are immutable, i.e., there are no methods defined that
allow you to change (overwrite) or zero out the contents of a String
after usage. This feature makes String objects unsuitable for
storing security sensitive information such as user passwords. You
should always collect and store security sensitive information in a
char array instead.
Java编程语言安全编码指南的指南2-2,4.0版本也说了类似的话(尽管它最初是在日志的上下文中):
Guideline 2-2: Do not log highly sensitive information
Some information, such as Social Security numbers (SSNs) and
passwords, is highly sensitive. This information should not be kept
for longer than necessary nor where it may be seen, even by
administrators. For instance, it should not be sent to log files and
its presence should not be detectable through searches. Some transient
data may be kept in mutable data structures, such as char arrays, and
cleared immediately after use. Clearing data structures has reduced
effectiveness on typical Java runtime systems as objects are moved in
memory transparently to the programmer.
This guideline also has implications for implementation and use of
lower-level libraries that do not have semantic knowledge of the data
they are dealing with. As an example, a low-level string parsing
library may log the text it works on. An application may parse an SSN
with the library. This creates a situation where the SSNs are
available to administrators with access to the log files.
- 这正是我在Jon的回答下面提到的有缺陷的/虚假的参考,这是一个众所周知的有很多批评的来源。
- @bestass,你能不能也引用一个参考文献?
- @bestass对不起,但是String已经被很好地理解了,并且它在JVM中的行为……在以安全的方式处理密码时,有很好的理由使用char[]来代替String。
- 再次,虽然密码是作为字符串从浏览器传递到请求的"字符串"而不是字符?所以无论你做什么,它都是一个字符串,在什么时候它应该被执行并被丢弃,而不是存储在内存中?
- @Dawesi - At which point—这是特定于应用程序的,但一般规则是,一旦您获得了应该是密码的东西(明文或其他形式),就立即这样做。例如,您可以从浏览器作为HTTP请求的一部分获得它。您不能控制交付,但是您可以控制自己的存储,所以一旦您得到了它,就将它放入char[]中,对它执行您需要执行的操作,然后将所有设置为'0',让gc回收它。
字符数组(char[])在使用后可以通过将每个字符设置为零而不设置字符串来清除。如果有人能以某种方式看到内存图像,那么如果使用字符串,他们就能看到纯文本的密码,但是如果使用char[],在清除了带有0的数据之后,密码是安全的。
- 默认情况下不安全。如果我们讨论的是web应用程序,那么大多数web容器都会将密码以明文形式传递给HttpServletRequest对象。如果JVM版本是1.6或更低,那么它将位于permgen空间中。如果是在1.7中,在收集之前仍然是可读的。(当)。
- 字符串不会自动移动到permgen空间,这只适用于实习生的字符串。除此之外,permgen空间也受到垃圾回收的影响,只是速率较低。permgen空间的真正问题是它的大小是固定的,这正是为什么没有人应该盲目地对任意字符串调用intern()的原因。但是您是对的,因为String实例首先存在(直到收集到),然后将它们转换成char[]数组不会改变它。
- @Holger看到docs.oracle.com/javase/specs/jvms/se6/html/…否则,将创建一个新的类String实例,其中包含CONSTANT_String_info结构给出的Unicode字符序列;该类实例是字符串字面派生的结果。最后,调用新String实例的intern方法。"在1.6中,当JVM检测到相同的序列时,它会为您调用intern。
- @avgvstvs:这与类的常量池条目相关,只描述字符串文本。顾名思义,类的常量池只包含常量字符串,显然必须在编译时知道这些字符串(否则它们怎么会出现在类文件中)。运行时创建的字符串,即通过Stringbuilder或String的一个构造函数不受影响。
- @Holger,你是对的,我把常量池和字符串池合并了,但是permgen空间只应用于中间字符串也是错误的。在1.7之前,constant_pool和string_pool都驻留在permgen空间中。这意味着分配给堆的惟一一类字符串是您所说的,new String()或StringBuilder.toString() I管理的应用程序有很多字符串常量,因此我们有很多permgen爬行。直到1.7。
- @avgvstvs:正如JLS所要求的那样,字符串常量总是被挂起的,因此说挂起的字符串最终会出现在permgen空间中,这句话隐式地应用于字符串常量。唯一的区别是字符串常量首先是在permgen空间中创建的,而对任意字符串调用intern()可能会导致在permgen空间中分配等效的字符串。后者可以得到GC 'ed,如果没有相同内容的文字字符串共享该对象…
- 简单的话说,+ 1。
- 不过要注意的是,关于字符串的一些信息仍然会泄漏(如果可以检查内存的话):密码的长度。对于C风格的字符串,我们可以有一个内存缓冲区,一旦它为空,我们就不知道原来的字符串有多长。使用Java中的char[],我们可以这样做。如果攻击者可以看到内存,并且看到getPassword()返回一个char[1],这表明一个比返回char[16]更容易的暴力目标。
- 密码不是以字符串的形式从浏览器提交的吗?又名在内存中作为字符串开始?
有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存。这减少了攻击者从系统读取密码的时间窗口,并且完全忽略了这样一个事实:攻击者已经需要足够的访问来劫持JVM内存来完成这一任务。具有如此多访问权限的攻击者可以捕获您的关键事件,使其完全无用(AFAIK,所以如果我错了,请纠正我)。
更新
感谢这些评论,我不得不更新我的答案。显然,在两种情况下,这可以增加(非常)小的安全性改进,因为它减少了密码在硬盘上停留的时间。不过,我仍然认为对于大多数用例来说,这样做是多余的。
您的目标系统可能配置得很差,或者您必须假设配置得很差,并且您必须对核心转储非常担心(如果系统不是由管理员管理的,那么它可能是有效的)。您的软件必须过于偏执,以防止数据泄漏与攻击者获得访问硬件-使用诸如TrueCrypt(中断),VeraCrypt,或密码学。
如果可能,禁用核心转储和交换文件可以解决这两个问题。但是,它们需要管理员权限,并且可能会减少功能(使用的内存更少),从运行的系统中取出RAM仍然是一个有效的关注点。
- 我将把"完全无用"替换为"只是一个小小的安全改进"。例如,如果您碰巧对tmp目录、配置不良的机器和应用程序中的崩溃具有读访问权,那么您可以访问内存转储。在这种情况下,您将无法安装键盘记录程序,但您可以分析核心转储。
- 从内存中删除未加密的数据被认为是最佳实践,而不是因为它是万无一失的(它不是);而是因为它降低了你的威胁暴露水平。这样做并不能阻止实时攻击;但因为它是一个缓解工具的损害显著减少的数据量暴露在追溯攻击一个内存快照(例如应用程序内存的副本写入交换文件,或者是拽从正在运行的服务器的内存读出和前搬到另一个状态失败)。
- 我倾向于同意这种反应的态度。我冒昧地提出,大多数严重的安全性破坏都发生在比内存中的位更高的抽象级别。当然,在超安全防御系统中可能存在这样的场景,这可能会引起相当大的关注,但是在这个级别上认真考虑一下,对于99%使用. net或Java的应用程序(因为它与垃圾收集相关)来说,这样做有些过头了。
- 也许localhost不是你想要保护的。也许您已经创建了一个在不安全的客户机上运行的程序,并且需要访问数据库的密码。如果您不信任客户机并试图保护数据库,那么从内存中删除密码是一个好建议。
- @RolfRander更糟糕的是,你不应该在不可信的客户端输入密码——使用一次性密钥(不确定这是不是正确的术语,这个密码只工作一次,在使用之后就会被锁定)。
- 大多数网站使用密码登录。你的信用卡号码和安全数字也是一种密码。
- @RolfRander网站不需要不受信任的客户端,如果用户选择使用受攻击的系统,那么你就无法保护他免受后果。
- 正如我所说,重点可能是保护服务器,而不是客户机。我对web站点示例的观点是,有许多服务使用密码(出于不同的原因,尽管其他机制显然更安全)。底线是,如果您希望保护服务器,不相信客户端,并且使用密码,那么将密码存储在char[]中是一个明智的选择,而且这并不是一个牵强附会的场景。
- @RolfRander给我的感觉是,您对所有客户端使用单一登录,没有任何安全/验证服务器端。您有责任确保用户不能破坏数据库(访问限制和验证服务器端),而用户有责任确保他的帐户不会被破坏(他的密码)。
- 绝对的。但是为了确保用户的密码不被泄露,处理密码的软件必须小心减少恶意软件(在客户端)从客户端应用程序内存中获取密码的机会。为了实现这一点,我认为将密码存储在char[]中,然后重写它是一个很好的建议。
- 我想说的是,有人理解char vs . sting背后的思想,那么他们在配置服务器时更可能考虑安全性。不思考通常会导致安全漏洞。
- 在Heartbleed渗透服务器内存并泄露密码之后,我将用"绝对必要的,而不是使用字符串作为密码,而是使用char[]"替换字符串"只是一个小小的安全改进"。
- @PetervdL heartbleed之所以发生,是因为有人想到重用内存比分配新的(清除的)内存更便宜,所以不能在Java中重用字符串,只希望没有人想到将char[]放到重用列表中以获得更好的性能。
- 你完全没抓住重点,约瑟夫。关键是,heartbleed允许远程读取进程内存,这是迄今为止所有人都认为不太可能的。当结合使用Java字符串时,密码很容易暴露。Char[]允许开发人员清空密码,而String甚至没有这样的机会。如果编码器出错,Char[] +心脏出血可能是有害的。String + heartbleed ==肯定总是有害的。
- @PetervdL heartbleed只允许读取特定的重用缓冲区集合(用于安全关键数据和网络I/O,而不需要在两者之间清除——出于性能原因),您不能将其与Java字符串组合,因为它们在设计上是不可重用的。也不能使用Java读入随机内存来获取字符串的内容。导致心脏出血的语言和设计问题在Java字符串中是不可能的。
- @PetervdL那么请不要提及"与Java字符串结合的心脏出血bug",我相信我提到的大多数与心脏出血有关的bug都是正确的,libreSSL开发人员已经详细了解了为什么OpenSSL是一个安全噩梦。"可能"有一种使用JIT绕过java咖啡中的缺陷构建范围内检查或对象内存的自动归零,对于一些人来说,"可能"有可能触发这个从远程位置没有JVM崩溃,然而,无关与heartbleed一样,JVM上的任何错误也不会那么简单或优化利用。
- @PetervdL这不仅仅是"特定"的心脏出血漏洞;这完全是不适用的。我建议您在更好地理解心脏出血的本质之前,不要将心脏出血作为安全措施的理由。感谢josefx揭示了这个经常被误解的问题。
- 还有另一种情况——JVM崩溃并产生内存转储。
- 为什么要将密码存储在内存中?外部系统?web或api请求提交一个字符串(具有讽刺意味的是),因此无论如何它都在内存中。我不会因为任何原因将登录密码存储在另一个未加密的变量中(除了发送到服务器的只读变量)。web请求是否应该将字母转换为它们的char number equiv来提交,这样请求变量就是一个数字?因为浏览器中没有char数据类型?
- 为了完整起见,我们不要忘记最近的meltdown和spectre攻击:meltdownattack.com,它可以通过web浏览器或共享主机环境进行利用。
用Java字符串是不可变的,如果你将密码存储为纯文本,可以在内存中,直到垃圾收集器清除它,因为字符串中使用字符串池可重用性有很高的机会,它会一直保存在内存中,很长一段时间,构成安全威胁。因为任何访问内存转储的人都可以用明文找到密码Java推荐使用JPasswordField的
getPassword()方法,该方法返回char[];不推荐使用的
getText()方法,该方法以明文形式返回密码,说明安全性原因。
toString()总是存在在日志文件或控制台中打印纯文本的风险,但是如果使用Array,则不会打印数组的内容,而是打印数组的内存位置。
1 2 3 4
| 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 ); |
String password: passwd
Character password: [C@110b2345
最后一点:尽管使用char[]还不够,您还需要删除内容以确保更安全。我还建议使用散列或加密的密码,而不是纯文本,并在完成身份验证后立即从内存中清除它。
- 因为任何访问内存转储的人都可以用明文找到密码,"我如何才能访问内存转储并在那里找到密码?"
- @MohitKanwar通过jmap或jvisualvm触发内存转储。如果设置了heapdumponoutofmemoryerror,则可以通过导致内存不足条件触发远程堆转储。有了转储之后,就可以使用jvisualvm或eclipse memory analyzer工具或命令行工具对其进行遍历,并检查所有对象及其值。
我不认为这是一个有效的建议,但我至少能猜出原因。
我认为这样做的动机是想确保在密码被使用后,你可以迅速而确定地在内存中删除密码的所有痕迹。使用char[],您可以使用空白或其他内容覆盖数组中的每个元素。您不能以这种方式编辑String的内部值。
但这并不是一个好的答案;为什么不确保对char[]或String的引用不转义呢?那么就没有安全问题了。但问题是,String对象在理论上可以被intern()创建,并在常量池中保持活动状态。我认为使用char[]禁止这种可能性。
- 我不认为问题在于你的推荐人是否会"转义"。只是字符串将在内存中保留一段额外的时间不被修改,而char[]可以被修改,然后它是否被收集就无关紧要了。由于字符串插入需要显式地为非字面量执行,这就像告诉静态字段可以引用char[]一样。
- 内存中的密码不是表单post中的字符串吗?
答案已经给出了,但是我想与您分享我最近在Java标准库中发现的一个问题。虽然他们现在非常小心地在任何地方使用char[]替换密码字符串(当然这是一件好事),但是当涉及到从内存中清除密码字符串时,其他安全关键数据似乎被忽略了。
我考虑的是像PrivateKey这样的课程。考虑这样一个场景:您将从PKCS#12文件加载一个私有RSA密钥,并使用它执行一些操作。在本例中,只要对密钥文件的物理访问受到适当限制,仅嗅探密码对您就没有多大帮助。作为攻击者,如果您直接获得密钥而不是密码,情况会好得多。所需的信息可能会泄漏,例如歧管、核心转储、调试器会话或交换文件。
事实证明,没有什么可以让您从内存中清除PrivateKey的私有信息,因为没有API可以让您擦除构成相应信息的字节。
这是一个糟糕的情况,因为本文描述了如何潜在地利用这种情况。
例如,OpenSSL库会在释放私钥之前覆盖关键内存段。由于Java是垃圾收集的,所以我们需要显式的方法来擦除和使Java密钥的私有信息无效,这些私有信息将在使用密钥后立即应用。
- 解决这个问题的一种方法是使用PrivateKey的实现,该实现实际上不会在内存中加载其私有内容:例如,通过PKCS#11硬件令牌。也许PKCS#11的软件实现可以手工清理内存。也许使用NSS store(它的大部分实现与Java中的PKCS11 store类型共享)这样的东西更好。KeychainStore (OSX keystore)将私钥的全部内容加载到它的PrivateKey实例中,但是它不应该这样做。(不确定WINDOWS-MY密钥存储在Windows上做什么。)
- 当然,基于硬件的令牌不会受此影响,但如果您或多或少被迫使用软件密钥,情况又会如何呢?并不是每个部署都有足够的预算来负担高速公路。软件密钥存储在某个时候必须把密钥加载到内存中,所以我认为至少应该给我们一个选项来清除内存。
- 当然,我只是想知道一些相当于HSM的软件实现在清理内存方面是否会表现得更好。例如,当在Safari/OSX中使用client-auth时,Safari进程实际上从未看到私钥,OS提供的底层SSL库直接与安全守护进程对话,后者会提示用户使用密钥链中的密钥。虽然这都是在软件中完成的,但是如果将签名委托给能够更好地卸载或清除内存的不同实体(甚至是基于软件的实体),类似的分离可能会有所帮助。
- 有趣的想法,一个额外的负责清理内存的间接层确实可以透明地解决这个问题。为软件密钥存储库编写PKCS#11包装器已经可以做到这一点了吗?
正如Jon Skeet所说,除了使用反射,没有其他方法。
但是,如果反射是一种选择,您可以这样做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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 );
}
} |
运行时
1 2 3
| please enter a password
hello world
password: '***********' |
注意:如果字符串的char[]作为GC循环的一部分被复制,那么前一个副本有可能在内存中的某个位置。
这个旧副本不会出现在堆转储中,但是如果您可以直接访问进程的原始内存,则可以看到它。一般来说,您应该避免任何人有这样的访问权限。
- 最好还做一些事情来防止打印我们从'***********'得到的密码的长度。
- @chux可以使用零宽度的字符,尽管这可能更容易混淆而不是有用。在不使用不安全的情况下,不可能更改char数组的长度。,)
- 由于Java 8的字符串重复数据删除,我认为这样做是非常有害的……您可能最终会清除程序中碰巧具有相同密码字符串值的其他字符串。可能不太可能,但是……
- @jamp Java 9应该有这个特性和紧凑的字符串(即使用字节[]])。我不相信这在Java 8中会发生。
- @PeterLawrey必须使用JVM参数启用它,但它确实存在。可以在这里阅读:blog. codistributed .de/en/2014/08/…
这些都是原因,应该选择char[]数组而不是字符串作为密码。
1. 因为在Java字符串是不可变的,如果你的密码存储为纯文本将在内存中,直到垃圾收集器清除它,因为字符串在字符串池中用于可重用性有很高的可能性,它会一直保存在内存中,很长一段时间,构成安全威胁。
因为任何访问内存转储的人都可以用明文找到密码,所以应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以无法更改字符串的内容,因为任何更改都会生成一个新的字符串,而如果使用char[],仍然可以将所有元素设置为空或零。因此,将密码存储在字符数组中可以明显降低窃取密码的安全风险。
2. Java本身推荐使用JPasswordField的getPassword()方法,该方法返回一个char[],而不建议使用getText()方法,该方法以明文返回说明安全原因的密码。听从Java团队的建议,坚持标准,而不是违背标准,这是很好的。
3.使用String总是有在日志文件或控制台中打印纯文本的风险,但是如果使用数组,则不会打印数组的内容,而是打印其内存位置。虽然这不是一个真正的原因,但仍然有意义。
1 2 3 4 5 6 7
| 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 |
引用自本博客。我希望这能有所帮助。
- 这是多余的。这个答案与@SrujanKumarGulla stackoverflow.com/a/14060804/1793718给出的答案完全相同。请不要复制粘贴或重复相同的答案两次。
- 两者的区别是什么?system . out。println("字符密码:"+ charPassword);和2)。System.out.println (charPassword);因为它给出了与输出相同的"未知"。
编辑:经过一年的安全研究之后,回到这个问题上,我意识到它带来了一个相当不幸的暗示,即你永远不会真正比较明文密码。请不要。使用带salt的安全单向散列和合理次数的迭代。考虑使用图书馆:这些东西很难正确使用!
原始答案:那么String.equals()使用短路求值,因此很容易受到定时攻击,这一事实又如何呢?这可能不太可能,但是理论上您可以对密码比较进行计时,以确定正确的字符序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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[]如何优于string呢?
- 你是绝对正确的,这两种方法都有可能出错。了解这个问题是最重要的,因为Java中没有显式的密码比较方法来比较基于字符串或基于char[]的密码。我想说,对字符串使用compare()的诱惑是使用char[]的一个很好的理由。这样,您至少可以控制如何进行比较(不扩展字符串,这在我看来是一种痛苦)。
- 此外,比较纯文本密码并不是正确的做法,对char[]使用Arrays.equals和对String.equals使用Arrays.equals的诱惑同样大。如果有人关心,有一个专门的键类封装的实际密码和照顾issues-oh等等,真正的安全包有专门的关键类,这Q&外只有一个习惯的说,例如JPasswordField, char[]而不是String(实际的算法使用byte[])。
- 无论如何,安全相关软件应该在拒绝登录尝试之前执行类似sleep(secureRandom.nextInt())的操作,这不仅消除了定时攻击的可能性,而且还可以抵消蛮力尝试。
char数组没有给你vs字符串任何东西,除非你在使用后手动清理它,我还没有看到任何人这样做。所以在我看来,char[]与String之间的偏爱有点夸张。
看看这里广泛使用的 Spring安全库,问问自己——Spring安全人员是否无能,或者char[]密码没有多大意义。当某个可恶的黑客窃取了你的内存转储文件时,即使你使用复杂的方法来隐藏它们,也要确保她会得到所有的密码。
然而,Java一直在变化,一些可怕的特性,比如Java 8的字符串重复数据删除特性,可能会在您不知情的情况下实习字符串对象。但那是不同的谈话。
- 为什么字符串重复数据删除令人恐惧?它只适用于至少有两个字符串具有相同的内容,所以让这两个已经相同的字符串共享同一个数组会产生什么危险?或者反过来问:如果没有字符串重复数据删除,那么两个字符串都有一个不同的数组(内容相同)有什么好处呢?在任何一种情况下,都将有一个数组的内容是活的,至少只要最长的活字符串的内容是活的…
- @Holger:任何你无法控制的事情都是潜在的风险。例如,如果两个用户拥有相同的密码,那么这个出色的特性将把它们都存储在一个char[]中,这表明它们是相同的,虽然不确定这是不是一个巨大的风险,但仍然如此
- 如果您能够访问堆内存和两个字符串实例,那么无论字符串指向同一个数组还是指向两个内容相同的数组,都很容易找到它们。尤其是,反正这无关紧要。如果您在这一点上,您将获取两个密码,不管它们是否相同。实际的错误在于使用纯文本密码而不是咸散列。
- @Holger要验证密码,它必须在内存中以明文形式存在一段时间,10ms,即使只是创建一个咸散列。然后,如果碰巧有两个相同的密码保存在内存中,即使是10毫秒,重复数据删除也可能发挥作用。如果它真的是实习生字符串,它们会在内存中保存更长的时间。几个月不重启的系统会收集很多这样的信息。只是理论。
- 看起来,你对字符串重复数据删除有一个基本的误解。它没有"实习字符串",它所做的只是让具有相同内容的字符串指向相同的数组,这实际上减少了包含明文密码的数组实例的数量,因为除了一个数组实例外,所有的数组实例都可以立即被其他对象回收和覆盖。这些字符串仍然像其他字符串一样被收集。如果您了解重复数据删除实际上是由垃圾收集器完成的,对于仅在多个GC周期中存活下来的字符串,这可能会有所帮助。
字符串是不可变的,一旦创建就不能更改。将密码创建为字符串将在堆或字符串池中留下对密码的零星引用。现在,如果有人对Java进程进行堆转储并仔细扫描,他可能能够猜出密码。当然,这些未使用的字符串将被垃圾收集,但这取决于GC何时开始工作。
另一方面,一旦身份验证完成,char[]是可变的,您可以用任何字符(比如所有M或反斜杠)覆盖它们。现在,即使有人进行堆转储,他也可能无法获得当前未使用的密码。在某种意义上,这给了您更多的控制,比如自己清除对象内容,而不是等待GC执行此操作。
- 只有当涉及的JVM是> 1.6时,它们才会被GC'。在1.7之前,所有字符串都存储在permgen中。
- @avgvstvs:"所有字符串都存储在permgen"是完全错误的。只有中间字符串存储在那里,如果它们不是来自代码引用的字符串文本,那么它们仍然是垃圾收集。想想看。如果在1.7之前jvm中通常从未对字符串进行GCed,那么任何Java应用程序如何能够存活超过几分钟?
- 这是假的。在1.7之前,Interned strings和String池(以前使用的字符串池)都存储在Permgen中。另外,请参见第5.1节:docs.oracle.com/javase/specs/jvms/se6/html/…JVM总是检查Strings以查看它们是否是相同的引用值,并为您调用String.intern()。结果是,每次JVM在constant_pool或堆中检测到相同的字符串时,都会将它们移动到permgen中。我用"爬行permgen"开发了几个应用程序,直到1.7。这确实是个问题。
- 总结一下:直到1.7,字符串开始在堆中,当它们被使用时,它们被放入位于permgen的constant_pool中,然后如果一个字符串被使用了不止一次,它将被保存。
- 没有"以前使用过的字符串池"。你把完全不同的东西放在一起。有一个运行时字符串池包含字符串字面量和显式连接的字符串,但没有其他的。每个类都有一个包含编译时常量的常量池。这些字符串将自动添加到运行时池中,但只添加这些字符串,而不是每个字符串。
简单而直接的答案是,char[]是可变的,而String对象不是可变的。
Java中的Strings是不可变的对象。这就是为什么一旦创建它们就不能修改它们,因此将它们的内容从内存中删除的唯一方法就是对它们进行垃圾收集。只有当对象释放的内存被覆盖时,数据才会消失。
现在Java中的垃圾收集不会在任何保证的时间间隔发生。因此,String可以在内存中保存很长时间,如果进程在此期间崩溃,字符串的内容可能最终会出现在内存转储或某些日志中。
使用字符数组,您可以读取密码,尽快完成处理,然后立即更改内容。
- 这只是一个毫无意义的双向飞碟的答案。
- @fallenidol完全不是。仔细阅读,你会发现不同之处。
1)自Java字符串是不可变的,如果你的密码存储为纯文本,它可以在内存中,直到垃圾收集器清除它,因为字符串在字符串池用于可重用性,有很高的机会,它将长时间保持在内存中,构成安全威胁。因为任何访问内存转储的人都可以用明文找到密码,这也是应该使用加密密码而不是纯文本密码的另一个原因。由于字符串是不可变的,所以无法更改字符串的内容,因为任何更改都会生成新的字符串,而如果char[],仍然可以将其所有元素设置为空或零。因此,将密码存储在字符数组中可以降低窃取密码的安全风险。
2) Java本身推荐使用JPasswordField的getPassword()方法,该方法返回char[],不推荐使用getText()方法,该方法以明文形式返回密码,说明安全性原因。听从Java团队的建议,坚持标准,而不是违背标准,这是很好的。
String是不可变的,它进入字符串池。一旦写好了,就不能再写了。
char[]是一个数组,一旦你使用了密码,你就应该重写它。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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';
}
} |
攻击者可以使用它的一个场景是crashdump——当JVM崩溃并生成内存转储时——您将能够看到密码。
这并不一定是恶意的外部攻击者。这可能是一个支持用户,可以访问服务器进行监视。他可以偷看一个垃圾场,找到密码。
- ch =零;你不能这么做
- @Yugerten好点。这只是伪代码,但是
java中的字符串是不可变的。因此,无论何时创建字符串,它都将保留在内存中,直到被垃圾收集为止。因此,任何访问内存的人都可以读取字符串的值。
如果修改了字符串的值,那么它最终将创建一个新字符串。因此,原始值和修改后的值都保留在内存中,直到垃圾回收为止。
使用字符数组,一旦达到密码的目的,就可以修改或删除数组的内容。数组的原始内容在修改后甚至在垃圾收集开始之前都不会在内存中找到。出于安全考虑,最好将密码存储为字符数组。
将密码使用到String或Char[]中总是有争议的,因为这两种方法都有各自的实用价值和缺点。它取决于用户期望实现的需求。以下几行可能有助于更好地理解何时使用哪个容器:由于java中的String是不可变的,所以每当有人试图操纵您的String时,它就会创建一个新对象,而现有的对象则保持不变。从另一方面来说,这可以看作是将密码存储为字符串的优势。即使在使用之后,String对象仍然保存在内存中。因此,如果有人获得了内存位置,就可以很容易地跟踪存储在该位置的密码。虽然Char[]是可变的,但它的优势在于,在使用之后,程序员可以明确地清除数组或覆盖值。因此,在不使用之后,它就会被清除,没有人能够知道您存储的信息。
基于上面的情况,我们可以知道应该使用String还是Char[]来满足他们的需求。
谢谢。
简而言之,这是因为在您使用完存储的密码之后,它可能会发生什么变化。
一个字符串可以(潜在地)在您停止使用它之后在内存中保留很长时间,但是一个字符数组可以被清空,因此它不再包含数据。
这都与垃圾收集器和不可变对象的工作方式有关。