通过使用reflection在垃圾回收之前擦除内容,安全地使用String作为密码
使用reflection来擦除String
是否使用String
作为使用char[]
进行密码安全?
从安全方面来说,通常认为使用char[]
存储/传递密码是最佳做法,因为可以在代码中尽快将其内容清零,这可能在垃圾收集清理它和内存之前显着重用(擦除所有跟踪),限制内存攻击的时间窗口。
但是, char[]
并不像String
那么方便,所以如果需要的话可以“擦除”一个String
,这样就很方便了,这样就可以使String
像char[]
一样安全。
下面是一个使用reflection来清除String
字段的方法。
这个方法是否“OK”,它是否实现了使String
像char[]
一样安全的目标?
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(尝试/捕获/忽略它们)并重构重复代码。
有两个潜在的安全问题:
-
String可以与其他字符串共享其后备数组; 例如,如果通过在较大的
String
上调用substring
来创建String
。 因此,当您将整个value
数组归零时,您可能会覆盖其他字符串的状态…不包含密码。解决方法是仅将密码字符串使用的后备arrays部分归零。
-
JLS( 17.5.3 )警告说,使用reflection来改变
final
变量的效果是不确定的。但是,上下文是Java内存模型,以及允许编译器积极地缓存
final
变量的事实。 在这种情况下:-
你会期望String被线程限制,并且
-
你不应该再使用任何这些变量。
-
我不希望其中任何一个成为真正的问题……模数固定过度积极的value
归零。
但真正令人担忧的是迅猛龙 。 🙂
我很困惑你真的很想打扰这样的密码。 当你考虑它时,你所保护的是有人可以读取进程内存……或核心转储或交换文件……来检索密码。 但是,如果有人能做到这一点,那么你的系统安全性必须已经受到损害……因为那些东西很可能需要root
访问权限(或等效的)。 如果他们具有root
访问权限,他们可以“调试”您的程序并在应用程序删除之前捕获密码。
我对String的一个论点是,无意中制作副本太容易了。 理论上可以安全地使用字符串,但是整个库生态系统都基于这样的假设:复制字符串是完全可以的。 最后,考虑到所有限制,字符串可能不像通常那样方便这个用例。