问题简介
内存中的敏感信息使用结束后如果不及时清理,会存在敏感信息泄露的风险,应尽量减小敏感信息在内 存中的生命周期,使用结束后立即清0。Java中的 String 是不可变对象(创建后无法更改),使用 String 保存口令、秘钥等敏感信息时,这些敏感信息会一直在内存中直至被垃圾收集器回收(其生命 周期不可控),如果进程的内存被dump,会导致敏感信息泄露风险。
开发规范
内存中的敏感信息不能依赖垃圾回收机制的清理,而是在使用结束后主动将内存中的信息清0。为了方 便内存的清理,推荐优先使用 char[] / byte[] 存储敏感信息。对于必须使用String进行数据处理的场 景(如web系统获取请求数据、数据需要转为json字符串进行传递、接口中预定义使用String传递参数 等),不需要将String转为char[]这样的无效处理,但要对所有涉及敏感信息的String中的信息进行清 理,不要遗漏,例如将一个含敏感信息的对象转为json串,使用结束后要将对象中敏感信息及json串全 部清0。String的清理可以通过反射、调用JNI接口等方式实现
反例
void doSomething() {
String password = getPassword();
verifyPassword(password);
...
}
boolean verifyPassword(String pwd) {
...
}
正例(建议使用数组存储敏感信息,方便清理)
void doSomething() {
char[] password = getPassword();
verifyPassword(password);
// 清除password
Arrays.fill(password, (char) 0x00);
}
boolean verifyPassword(char[] pwd) {
...
}
正例(补救方案:三方件中的String清0)
void doSomething() {
...
String user = request.getParameter("username");
String password = request.getParameter("pwd");
verifyLoginInfo(user, password);
// 清除password
try {
Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfString.setAccessible(true);
char[] value = (char[]) valueFieldOfString.get(password);
Arrays.fill(value, (char) 0x00);
...
}
...
}
通用方法Demo
private static void erasureString(String sensitiveString) {
try {
Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfString.setAccessible(true);
char[] value = (char[]) valueFieldOfString.get(sensitiveString);
Arrays.fill(value, (char) 0x00);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private static <T> void erasureString(T model, Function<T, String> getSensitiveString) {
try {
Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfString.setAccessible(true);
char[] value = (char[]) valueFieldOfString.get(getSensitiveString.apply(model));
Arrays.fill(value, (char) 0x00);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private static <T> void erasureString(Collection<T> list, Function<T, String> getSensitiveString) {
try {
Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfString.setAccessible(true);
list.stream().forEach(model -> {
char[] value = new char[0];
try {
value = (char[]) valueFieldOfString.get(getSensitiveString.apply(model));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Arrays.fill(value, (char) 0x00);
});
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}