通过在垃圾回收之前使用反射来清理内容,安全地使用 String 作为密码

使用反射来擦除是否与使用密码一样安全?StringStringchar[]

从安全方面来看,通常用于存储/传递密码被认为是最佳实践,因为人们可以在代码中尽快将其内容清零,这可能在垃圾回收清理它并重用内存(擦除所有痕迹)之前,限制了内存攻击的时间窗口。char[]

但是,并不像 ,所以如果需要,如果可以“擦洗”一个,那就很方便了,从而像 .char[]StringStringStringchar[]

下面是一个使用反射将 的字段清零的方法。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

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


答案 1

有两个潜在的安全问题:

  1. 字符串可以与其他字符串共享其支持数组;例如,如果 是通过调用更大的 .因此,当您将整个数组归零时,您可能会覆盖其他字符串的状态...不包含密码。StringsubstringStringvalue

    解决方法是仅将密码字符串使用的支持数组部分清零。

  2. JLS (17.5.3) 警告使用反射更改变量的效果未定义。final

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

    • 你会期望字符串是线程受限的,并且

    • 您不应该再次使用这些变量中的任何一个。

我不会期望这两个都是真正的问题...模固定 的过度激进归零。value


但真正令人担忧的是迅猛龙。:-)


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


答案 2

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