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

时间:2011-03-08 20:46:29

标签: java security

我公司的员工需要通过我制作的程序修改SQL Server数据库中的数据。该程序首先使用Windows身份验证,我要求DBA为该特定用户提供对所述数据库的写访问权。

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

由于我相信这个人但是不足以让他在我的会话打开的情况下工作90分钟,我只会在我的程序中添加一个登录提示,要求输入用户名和密码,然后用它登录到SQL Server 。我会登录,并相信我的申请让他只做他需要的东西。

然而,这会带来很小的安全风险。 password fields tutorial over Sun Oracle的站点声明密码应保持在内存中所需的最短时间,为此,getPassword方法返回{{1}一旦你完成它就可以归零的数组。

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

当然,我无法控制SQL Server JDBC类对我的密码所做的任何事情,但我希望我可以控制使用我的密码做什么。

是否有一种使用Java销毁/清空String对象的万无一失的方法?我知道两者都有点违背语言(对象破坏是非确定性的,String对象是不可变的),而String也是不可预测的,但仍然存在;任何想法?

9 个答案:

答案 0 :(得分:19)

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

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

答案 1 :(得分:11)

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

  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's answer

答案 2 :(得分:3)

如果你绝对必须,保持WeakReference到字符串,并继续吞噬内存,直到你强制垃圾收集字符串,你可以通过测试弱引用是否为空来检测。这可能仍然留在进程地址空间中的字节。可能是垃圾收集器的几个搅拌器会给你带来安慰吗?所以在你的原始字符串弱引用被取消之后,创建另一个弱引用并进行流失,直到它被归零,这意味着完成了一个完整的垃圾收集循环。

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

答案 3 :(得分:1)

您可以使用反射更改内部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);

答案 4 :(得分:1)

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

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中,设计正是考虑到了这一点。这就是API期望char[]而不是字符串的原因 因为您非常清楚Strings是不可变的,所以密码符合 future 垃圾收集的条件,之后您无法重置。这可能会导致窥探内存区域的恶意程序带来安全风险 我不认为在这种情况下你正在调查有一个解决方法 实际上这里有一个类似的问题:
Why Driver Manager not use char arrays?
但没有明确的答案 似乎这个概念是密码已经存储在属性文件中(已经有一个DriverManager构造函数接受属性),因此文件本身已经比实际将密码从文件加载到字符串带来更大的风险。登记/> 或者API的设计者对访问数据库的机器的安全性有一些假设 我认为最安全的选择是尝试查看DriverManager如何使用密码,即它是否保留内部参考等。

答案 5 :(得分:0)

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

如果您不将String存储在变量中但通过new String(char [])传递,会发生什么?

答案 6 :(得分:0)

没有强制垃圾收集的常规方法。可以通过本机调用,但我不知道它是否适用于字符串,因为它们被合并。

也许替代方法会有所帮助?我对SQL Server并不熟悉,但在Oracle中,实践是让用户A拥有数据库对象和一组存储过程,而用户B除了给出了程序的运行权限之外什么都没有。这样,密码就不再是问题了。

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

答案 7 :(得分:0)

因此,一旦我完成密码,我将无法处理密码。

为什么不呢?

如果您通过

获得连接
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方法返回时,不再有您的密码参考。没有人可以再使用它了。如果需要创建新会话,则必须使用新密码再次调用“connect”。保存您信息的对象的引用将丢失。

答案 8 :(得分:0)

如果JDBC驱动程序管理器(一个很大的if)没有保留字符串,我不会担心强制它被破坏。一个现代的JVM,即使有足够的可用内存,仍能很快地运行垃圾收集。问题在于垃圾收集是否有效“安全擦除”。我怀疑它是。我猜它只是忘记了对该内存位置的引用,并没有将任何内容归零。