内存中的敏感信息使用完毕后应立即清0
为什么推荐优先使用char[]/byte[]存储敏感信息
- 字符串变量是不可变的。一旦分配了字符串变量,就无法更改或删除其值。因此,这些字符串可能在可能在多个位置的存储器中保持不变,持续一段时间,直到垃圾收集器正好将其移除,且由于字符串是不可变的,所以没有任何方式可以修改字符串的值,因为每次修改都将产生新的字符串。敏感数据(如密码)将作为明文在内存中暴露,无法控制其生命周期因为任何能够访问内存(memory dump内存转储)的人都能清晰的看到文本中的密码,这也是为什么你应该总是使用加密的形式而不是明文来保存密码。因为String使用常量池存储字符串,即使jdk7之后常量池移到了堆,也要在触发gc之后才能彻底清除掉内存中的敏感信息。
- Java官方文档《Java加密体系结构指南》 (https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx) 建议:在对象java.lang.String 中收集和存储密码看似符合逻辑,但是需要注意的是,字符串对象是不可变的,没有任何方法可以改变(重写)或清空内容。这一特性使得字符串对象不适合存储安全敏感信息,比如用户密码。你应该使用字符数组收集和存储安全敏感信息。
- 建议使用专用类存储明文的敏感信息,或者,将明文敏感信息临时存储在可变数据类型(如:char[]/byte[])中,且用完后立即擦除。如果没有及时清空而由GC来清除的话,暴露窗口大约是秒这个数量级,如果能够在计算HASH后立即清除,暴露窗口大约是微秒数量级。如此简单的设计就可以降低如此多的被攻击概率,性价比是非常高的。
- 其他问题:通过reflection机制可以查看String的内部的内存成员,从而可以直接修改其中的数据区。但是这样的做法会有问题,内部化的String为了提高HASH速度,节省空间,值相同的字符串通常只有一个实例。
你自己的char[],修改它是没有副作用的。但是String里的char[],很可能是多个String所共享的,你改掉它就会殃及别的String。举个例子,有一个密码是"Password",而你密码框提示密码输入的文字也是"Password",改掉第一个"Password"会把后面那个也改掉。
推荐的写法
方法一 使用String(主动将String清0)
// 这段代码在jdk8运行正常
void doSomething() {
...
String user = request.getParameter("username");
String password = request.getParameter("pwd");
verifyLoginInfo(user, password);
// 清除password
try {
// 获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
// 改变value属性的访问权限
valueFieldOfString.setAccessible(true);
// 这里是填充的值,不是地址,否则等于掩耳盗铃,扫描内存还是可以获取密码
char[] value = (char[]) valueFieldOfString.get(password);
Arrays.fill(value, (char) 0x00);
...
}
...
}
上述示例中,从web容器中获取客户端提交的用户名和密码,进行登录验证,登录验证后主动将String
中的密码信息清0。该示例以Java 8为例,高版本的Java中String的实现可能存在差别,此案例仅用做参
考。
方法二 使用char[]处理敏感信息
void doSomething() {
char[] password = getPassword();
verifyPassword(password);
// 清除password
Arrays.fill(password, (char) 0x00);
}
boolean verifyPassword(char[] pwd) {
...
}
上述示例中,使用char[] 来保存密码信息,使用结束后主动将char数组清0。与前一示例相比,对于
char[]的清理更加方便,所以要优先使用char[]/byte[]处理敏感信息。
参考
- 字符数组char[] 存储解密密码,String不适用 http://3ms.huawei.com/km/blogs/details/11563231
- 【java suprise系列】关于string的冷知识 http://3ms.huawei.com/km/blogs/details/10203327
- 在Java中,优先使用char[]存储密码,而不是String https://blog.csdn.net/Forward__/article/details/78356814