我如何确保在Java中销毁String对象?

我公司的Empoyee需要通过我编写的程序来修改SQL Server数据库中的数据。 该程序首先使用Windows身份validation,并要求数据库pipe理员给这个特定的用户写入访问数据库。

他们不愿意这样做,而是给我的 Windows用户帐户写入访问权限。

由于我相信这个人,但是还不足以让他在我的会话打开90分钟的情况下工作,我只需在我的程序中添加一个login提示,询问用户名和密码组合,然后使用它login到SQL Server。 我会login,并相信我的应用程序,让他只做他需要的东西。

但是,这会带来很小的安全风险。 Sun Oracle站点上的密码字段教程指出密码应该保持在内存中所需的最less时间,为此, getPassword方法返回一个char[]数组,您可以在完成该操作后将其置零。

但是,Java的DriverManager类只接受String对象作为密码,所以一旦完成,我将无法处理密码。 而且,由于我的应用程序在分配和内存要求方面相当低,谁知道在内存中能够存活多久呢? 如上所述,该计划将运行相当长的时间。

当然,我无法控制SQL Server JDBC类用我的密码做什么,但是我希望我能控制我的密码。

有没有一种可靠的方法来销毁/清零Java的String对象? 我知道两者都是反对语言的(对象销毁是非确定性的,而且String对象是不可变的), System.gc()也是不可预知的,但仍然是这样。 任何想法?

所以,这是坏消息。 我很惊讶没有人提到它呢。 与现代垃圾收集器,甚至整个char []概念被打破。 无论你使用String还是char [],数据最终都可以存储在内存中,以便知道多久。 这是为什么? 因为现代jvms使用世代垃圾收集器,简而言之,就是将所有的东西都复制到这个地方 。 因此,即使使用char [],它使用的实际内存也可能会被复制到堆中的不同位置,从而在所有位置都保留密码副本(并且没有高性能的gc会将旧内存清零)。 所以,当你最终清除你的实例时,你只会清除内存中的最新版本。

长话短说,没有防弹的方法来处理它。 你几乎不得不相信这个人。

我只能想到使用reflection的解决scheme。 您可以使用reflection来调用使用共享字符数组的私有构造函数:

  char[] chars = {'a', 'b', 'c'}; Constructor<String> con = String.class.getDeclaredConstructor(int.class, int.class, char[].class); con.setAccessible(true); String password = con.newInstance(0, chars.length, chars); System.out.println(password); //erase it Arrays.fill(chars, '\0'); System.out.println(password); 

编辑

对于任何人认为这是一个失败或甚至有用的预防措施,我鼓励你阅读jtahlborn的答案至less有一个警告。

如果你绝对必须的话,保持一个WeakReference的string,并保持吞噬内存,直到你强制垃圾收集的string,你可以通过testing,如果弱引用已成为null。 这可能仍然留下进程地址空间中的字节。 可能是更多的垃圾收集器的搅动会给你安慰吗? 所以在你的原始string弱引用被清空之后,再创build一个弱引用并搅动到零,这意味着完成了一个垃圾回收循环。

不知何故,我必须添加LOL到这个,即使我上面的答案是完全严重:)

您可以使用reflection更改内部char[]的值。

您必须小心使用相同长度的数组来更改它,或者更新count字段。 如果您希望能够将其用作集合中的条目或映射中的值,则需要重新计算哈希代码并设置hashCode字段的值。

这就是说,实现这个最小的代码是

  String password = "password"; Field valueField = String.class.getDeclaredField("value"); valueField.setAccessible(true); char[] chars = (char[]) valueField.get(password); chars[0] = Character.valueOf('h'); System.out.println(password); 

我不确定在DriverManager类。
一般来说,你是对的,推荐是将密码存储在字符数组中,并在使用后明确地清除内存。
最常见的例子是:

 KeyStore store = KeyStore. getInstance(KeyStore, getDefaultType()) ; char[] password = new char[] {'s','e','c','r','e','t'}; store .load(is, password ); //After finished clear password!!! Arrays. fill(password, '\u0000' ) ; 

在JSSE和JCA中,devise正是考虑到了这一点。 这就是为什么API期望一个char[]而不是一个string。
因为,因为您非常清楚string是不可变的,所以密码有资格用于将来的垃圾回收,并且之后不能重置密码。 这可能会导致窥探内存区域的恶意程序的安全风险。
我不认为在这种情况下,你正在寻找有一个工作。
其实这里也有类似的问题:
为什么Driver Manager不使用char数组?
但没有明确的答案。
看来这个概念是密码已经存储在一个属性文件(已经有一个DriverManager的构造函数接受属性),所以文件本身已经比实际加载密码从一个文件到一个string更大的风险。
或者API的devise者对访问数据库的机器的安全性有一些假设。
我认为最安全的方法就是试着研究一下DriverManager如何使用密码,例如是否保存内部引用等。

所以一旦我完成了,我将无法处理密码。

为什么不?

如果你正在通过连接

 Driver.getConnection 

你只需要通过密码,让它被gc'ed。

 void loginScreen() { ... connect( new String( passwordField.getPassword() ) ); ... } ... void connect( String withPassword ) { connection = DriverManager.getConnection( url, user, withPassword ); ... }//<-- in this line there won't be any reference to your password anymore. 

当控制从connect方法返回时,不再有密码的参考。 没有人可以使用它了。 如果您需要创build新的会话,则必须使用新密码再次调用“连接”。 保存您的信息的对象的引用将丢失。

如果string没有被JDBC驱动程序pipe理员(一个大的)保留,我不会担心强行破坏。 一个现代的JVM,即使有足够的可用内存,也能够及时运行垃圾收集。 问题是垃圾回收是否有效的“安全擦除”。 我怀疑它是。 我猜测它只是忘记了那个内存位置的引用,并没有把任何东西都清零。

没有固定的垃圾收集方式。 通过本地调用是可能的,但我不知道它是否适用于string,因为它们汇集。

也许另一种方法会有所帮助? 我不熟悉SQL Server,但在Oracle中,实践是让用户A拥有数据库对象和一组存储过程,而用户B只拥有对这些过程的运行权限。 这样密码就不再是一个问题了。

在你的情况下,你的用户拥有所有的数据库对象和存储过程,你的员工将需要运行权限的存储过程,DBA希望不愿意给予。

有趣的问题。 一些googeling透露这个: http ://securesoftware.blogspot.com/2009/01/java-security-why-not-to-use-string.html。 根据评论,这不会有所作为。

会发生什么,如果你不把string存储在一个variables,但通过新的string(char [])传递?