为什么在密码问题上char[]优先于String?
本文由 ImportNew - 范琦琦 翻译自 stackoverflow。欢迎加入Java小组。转载请参见文章末尾的要求。
问题
在Swing中,密码区域会用一个getPassword()
函数(用来返回char[]
)取代getText()
函数(返回字符串)。我偶然得到的一个劝告是不使用字符串去解决密码,为什么当涉及到密码问题的时候字符串会对安全构成威胁。我感觉使用char[]
很不方便。
回答1
字符串是不可变的。这就意味着一旦你创建了一个字符串,如果另一个进程可以转储内存,那就没有办法(除了反射)在GC生效之前摆脱数据了。
用一个数组,你可以处理它之后再显示地擦掉数据:你可以以你喜欢的任何方式重写数组,而且密码不会出现在系统的任何地方,甚至是之前的垃圾收集。
所以呢,这是一个安全问题——即使使用char[]
只能降低攻击者的机会之窗,而这只是为了攻击的特定形式。
编辑:如评论所说,数组移动时,垃圾回收器可能会将流动的数据副本留在内存中。我相信这是可以特定实现的-GC会在工作时清除掉所有内存,以避免此类事情的发生。即便如此,仍旧会有人在此期间将包含实际字符的char[]作为攻击之窗。
回答2
但是其他的建议似乎有效,这有一个更好的理由。若是普通的字符串,你登陆时就有更高的机率不小心将密码输出,或者是显示器和其他不安全的地方。Charp[]
这是就显得尤为脆弱。
参考一下这段代码:
public static void main(String[] args) { Object pw = "Password"; System.out.println("String: " + pw); pw = "Password".toCharArray(); System.out.println("Array: " + pw); }
输出结果:
String: PasswordArray: [C@5829428e
回答3
引用官方的文件,Java加密体系结构指南说这场有关char[]和String的密码之争(关于密码加密基础,但这更为基于课程密码)
在对象java.lang.String
中收集和存储密码看似合法,然而,看这里的警告:对象S型的字符串是不可变的,也就是说,在字符串使用之后没有已定义的方法可以让你改变(重写)或者溢出的内容。这一特性使得字符串对象不适合存储安全敏感信息,比如用户密码。你应当使用一个字符数组来代替,以便收集和存储安全敏感的信息。
指引的安全编码指南为Java编程语言版本4.0的2-2也表示类似的东西(虽然它最初是在登录的情况下):
指引2-2:不记录高度敏感信息
有些信息是高度敏感的,例如社会安全号(SSN)和密码。即便是管理员,也不能保存这些信息超过所需的时间,也没有地方可以看到。例如,它不应当被发送到日志文件,它的存在不应该通过搜索被检测出。一些瞬态数据可以被保存在可变的数据结构中,如字符数组,并且使用之后立马清除。对程序员而言,清除数据结构已经减少典型的Java运行时系统的有效性,就如同对象在透明的内存中移动。
对于一些低级别的库,当他们不包含所处理的数据应有的语法知识时,该指南也会对执行和使用这些库产生影响。作为一个例子,一个低水平的字符串解析库可以登录它所处理的文本。在库的帮助下,一个应用程序或许可以解析SSN,这就造成了这种状况:SSN可用于管理员访问日志文件。
回答4
有些人认为,如果你长期未登录你的密码,你必须重写内存用来存储密码。这就减少了攻击者从你的系统中读取密码的时间窗,而且也完全忽略了一个事实,攻击者需要足够的访问劫持JVM内存才能做到这一点。一个攻击者如果有如此多帮助能获取你的重要事件,这就显得完全无用。(据我所知,如果我错了请纠正我)
更新
- 谢谢这条评论使我可以更新我的问题。很显然,有两种情况,这可以增加一个非常小的安全改进,因为它减少了一个密码降落在硬盘驱动器上的时间,所以我认为在大多数情况下这是有点矫枉过正的。
- 你的目标系统可能配置不当,或者你需要假定它是这样,你必须妄想核心存储(如果系统不是由管理员管理的话就是有效的)。你的软件需高度防护以阻止攻击者获得路径访问你的硬盘造成数据泄漏(像TrueCrypt的东西)。
如果可以,禁用核心转储和交换文件会对所有问题有所帮助。然而,它们需要管理员权限,并且可能会使功能降低(较少使用内存),而且,将一个正在运行的系统RAM拉动仍是一个有效的问题。
回答5
字符数组(char[]
)在使用之后可以被清除而且每个字符可以初始化,字符串并不如此。如果有人能以某种方式看到内存映像,如果字符串被使用了,它们可以以纯文本看到一个密码。但是如果char[]
被使用,数据置零后,密码就是安全的。
回答6
我不认为这是一个有效的建议,但是,至少,我可以猜测原因。
我认为动机是你想确保在你用完密码后,可以及时并非常肯定地删除所有在内存中的密码的踪迹。你可以确保用空白或其他东西来重写char[]
数组的每个元素。但你不能用那种方式来编辑字符串的内部值。
但是,它本身并不是一个好的答案,为什么不只是确保一个char[]
的参考或者字符串怎么不消失?那么,这有没有安全隐患呢?但是事情本质是在理论上字符串对象可以使用intern()
方法,还可继续存放在常量池中。我想,使用char[]
可以避免这种可能性。
回答7
答案已经揭晓,但是我还想同大家分享我最近发现的与Java标准库有关的问题。虽然他们现在更换密码字符串非常小心谨慎地处处使用char[]
(这当然是好现象),当涉及到清除内存时其他安全关键型数据似乎被忽视了。
例如PrivateKey
类。想像一下一个场景,你从PKCS#12文件加载一个RSA私钥,用它来进行一些操作。现在,在这种情况下,只要密钥文件的物理访问是适当限制的,独自一人试探破解密码纯粹是在做无用功。作为一个攻击者,如果你得到密钥,要比直接的密码好得多。
所需信息会被泄漏歧管,例如核心转储,调试器回话和交换文件。
事实证明,没有什么PrivateKey可以让你从内存中清除你的私人信息,因为没有API让你擦掉相应信息的字节。
这是一个糟糕的情况,因为这篇文章介绍了在这种情况下可能会被潜在地利用。
例如OpenSSL库会在私钥释放之前重写关键的部分内存。由于Java有垃圾回收机制,我们需要明确的方法来擦掉无效的私人信息的Java密钥,这些在使用密钥后需要立即生效。
- 字符串是不可变的。在Java中,如果你存储的密码是纯文本形式,那么直到垃圾回收器清除他,才会在内存中可用。因为字符串池中的字符串有可重复使用性,它就有相当高的机会长时间持续保留在内存中,从而对安全构成威胁。这样一来,任何一个有权访问内存转储的人都可以以明文形式找到密码。
- Java的建议。使用
JPasswordField
中的getPassword()
方法将会返回一个char[]
,一个废弃的getText()
方法会将密码以明文形式返回,这的确是个安全问题。 - toString()在日志文件和控制台输出纯文本总是存在危险,但如果使用数组,你不用输出数组内容,以代替输出内存中的位置。
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
最后的思考
虽然使用char[]
并不能让你足够删除内容以更安全,我也建议使用hash’d或者加密的密码,用以代替验证后立即从内存中清除的纯文本。
原文链接: stackoverflow 翻译: ImportNew.com - 范琦琦
译文链接: http://www.importnew.com/10281.html
[ 转载请保留原文出处、译者和译文链接。]