通过在垃圾收集之前使用反射清理内容来安全地使用字符串作为密码

使用反射擦洗 String 是否使使用 String 与使用 char[] 密码一样安全?

Does using reflection to scrub a String make using String as safe as using char[] for passwords?

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

From a security aspect, it is generally considered best practice to use char[] for storing/passing passwords, because one can zero-out its contents as soon as possible in code, which may be significantly before garbage collection cleans it up and the memory is reused (wiping all trace), limiting the window of time for a memory attack.

但是,char[] 不如 String 方便,所以如果可以擦洗"一个 String需要,从而使 Stringchar[] 一样安全.

However, char[] is not as convenient as String, so it would be handy if one could "scrub" a String if needed, thus making String as safe as char[].

以下是使用反射将 String 的字段清零的方法.

Below is a method that uses reflection to zero-out the fields of String.

这个方法OK"吗,它是否达到了使 Stringchar[] 一样安全的目标?

Is this method "OK", and does it achieve the goal of making String as safe as char[] for passwords?

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), '');
    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 常量,因此调用此方法不会对实习字符串产生不利影响.

Note: You may assume that passwords are not String constants and thus calling this method will have no adverse effect on interned Strings.

另外,为了简单起见,我将方法保留为相当原始"的状态.如果我要使用它,我不会声明抛出的异常(尝试/捕获/忽略它们)并重构重复的代码.

Also, I have left the method is a fairly "raw" state for simplicity's sake. If I were to use it, I would not declare exceptions thrown (try/catch/ignoring them) and refactor repeated code.

推荐答案

有两个潜在的安全问题:

There are two potential safety concerns:

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

  1. The String may share its backing array with other Strings; e.g. if the String was created by calling substring on a larger String. So when you zero the entire value array you could be overwriting the state of other strings ... that don't contain passwords.

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

The cure is to only zero the part of the backing array that is used by the password string.

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

The JLS (17.5.3) warns that the effects of using reflection to change final variables is undefined.

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

However, the context for this is the Java Memory Model, and the fact that the compiler is allowed to aggressively cache final variables. In this case:

  • 你会期望 String 是线程受限的,而

  • you would expect the String to be thread-confined, and

您不应该再次使用这些变量.

you shouldn't be using any of those variables again.

我不希望这些都是真正的问题...模修正 value 的过度归零.

I wouldn't expect either of these to be real problems ... modulo fixing the over-aggressive zeroing of value.

但真正令人担忧的是 Velociraptors.:-)

But the real concern is Velociraptors. :-)

我很困惑,你居然会费心像这样破解密码.当您考虑它时,您要防止的是有人可以读取进程内存......或核心转储或交换文件......以检索密码的可能性.但是,如果有人可以做到这一点,那么您的系统安全性肯定已经受到威胁……因为这些东西很可能需要 root 访问权限(或同等权限).如果他们有 root 访问权限,他们可以调试"您的程序并在您的应用程序破坏它们之前捕获密码.

I'm puzzled that you would actually bothering to zap passwords like this. When you think about it, what you are protecting against is the possibility that someone can read process memory ... or a core dump or swap file ... to retrieve passwords. But if someone can do that, your system security has to have already been compromised ... cos' those things most likely require root access (or equivalent). And if they have root access they can "debug" your program and catch the passwords before your application zaps them.

相关文章