通过使用reflection在垃圾回收之前擦除内容,安全地使用String作为密码

使用reflection来擦除String是否使用String作为使用char[]进行密码安全?

从安全方面来说,通常认为使用char[]存储/传递密码是最佳做法,因为可以在代码中尽快将其内容清零,这可能在垃圾收集清理它和内存之前显着重用(擦除所有跟踪),限制内存攻击的时间窗口。

但是, char[]并不像String那么方便,所以如果需要的话可以“擦除”一个String ,这样就很方便了,这样就可以使Stringchar[]一样安全。

下面是一个使用reflection来清除String字段的方法。

这个方法是否“OK”,它是否实现了使Stringchar[]一样安全的目标?

 public static void scrub(String str) throws NoSuchFieldException, IllegalAccessException { Field valueField = String.class.getDeclaredField("value"); Field offsetField = String.class.getDeclaredField("offset"); Field countField = String.class.getDeclaredField("count"); Field hashField = String.class.getDeclaredField("hash"); valueField.setAccessible(true); offsetField.setAccessible(true); countField.setAccessible(true); hashField.setAccessible(true); char[] value = (char[]) valueField.get(str); // overwrite the relevant array contents with null chars Arrays.fill(value, offsetField.getInt(str), countField.getInt(str), '\0'); countField.set(str, 0); // scrub password length too hashField.set(str, 0); // the hash could be used to crack a password valueField.setAccessible(false); offsetField.setAccessible(false); countField.setAccessible(false); hashField.setAccessible(false); } 

这是一个简单的测试:

 String str = "password"; scrub(str); System.out.println('"' + str + '"'); 

输出:

 "" 

注意:您可能认为密码不是String常量,因此调用此方法对interned Strings没有不利影响。

另外,为了简单起见,我离开了这个方法是一个相当“原始”的状态。 如果我要使用它,我不会声明抛出exception(尝试/捕获/忽略它们)并重构重复代码。

有两个潜在的安全问题:

  1. String可以与其他字符串共享其后备数组; 例如,如果通过在较大的String上调用substring来创建String 。 因此,当您将整个value数组归零时,您可能会覆盖其他字符串的状态…不包含密码。

    解决方法是仅将密码字符串使用的后备arrays部分归零。

  2. JLS( 17.5.3 )警告说,使用reflection来改变final变量的效果是不确定的。

    但是,上下文是Java内存模型,以及允许编译器积极地缓存final变量的事实。 在这种情况下:

    • 你会期望String被线程限制,并且

    • 你不应该再使用任何这些变量。

我不希望其中任何一个成为真正的问题……模数固定过度积极的value归零。


但真正令人担忧的是迅猛龙 。 🙂


我很困惑你真的很想打扰这样的密码。 当你考虑它时,你所保护的是有人可以读取进程内存……或核心转储或交换文件……来检索密码。 但是,如果有人能做到这一点,那么你的系统安全性必须已经受到损害……因为那些东西很可能需要root访问权限(或等效的)。 如果他们具有root访问权限,他们可以“调试”您的程序并应用程序删除之前捕获密码。

我对String的一个论点是,无意中制作副本太容易了。 理论上可以安全地使用字符串,但是整个库生态系统都基于这样的假设:复制字符串是完全可以的。 最后,考虑到所有限制,字符串可能不像通常那样方便这个用例。