使用Java.io.Console读取密码后内存Dump仍泄露密码的原因排查
为什么覆盖密码字符数组后内存Dump仍能看到密码?
我基于Java官方教程的代码改编了如下程序,目的是通过char[]存储密码并使用Arrays.fill覆盖来避免内存中残留明文,但执行后用jmap生成内存Dump,仍能找到输入的密码明文。具体代码及场景如下:
public class RedirectOutputStreamExample { public static void main(String[] args) throws InterruptedException { Console c = System.console(); if (c == null) { System.err.println("No console."); System.exit(1); } String login = ((Console) c).readLine("Enter your login: "); char [] oldPassword = c.readPassword("Enter your old password: "); if (verify(login, oldPassword)) { boolean noMatch; do { char [] newPassword1 = c.readPassword("Enter your new password: "); char [] newPassword2 = c.readPassword("Enter new password again: "); noMatch = ! Arrays.equals(newPassword1, newPassword2); if (noMatch) { c.format("Passwords don't match. Try again.%n"); } else { c.format("Password for %s changed.%n", login); } Arrays.fill(newPassword1, ' '); Arrays.fill(newPassword2, ' '); System.out.println("password is: "+newPassword1); System.out.println("password is: "+newPassword2); } while (noMatch); } Arrays.fill(oldPassword, ' '); System.out.println(oldPassword); Thread.sleep(60000); } }
程序中三个密码字符数组被覆盖后,打印结果为空行(符合预期),操作在原生控制台完成(IntelliJ终端因安全限制报错"No Console")。但生成内存Dump后,仍能在其中找到输入的密码明文。
核心原因:Console内部实现可能将密码暂存为String
这既不是操作错误,也不是JVM安全漏洞,核心问题在于**Console.readPassword()的内部实现可能会将用户输入的密码临时存储为String对象**。
虽然你在代码里用char[]存储并主动覆盖,但Console类的底层输入处理逻辑中,部分JDK实现会先把输入内容转成String缓存。而String是Java中的不可变对象,一旦创建就无法修改其内部字符数组,即便你自己维护的char[]被覆盖,这些临时生成的String实例仍会留在内存中,直到被垃圾回收机制清理。
内存Dump是对JVM内存状态的快照,若生成Dump时这些临时String还未被GC回收,就能在快照中找到密码明文。
额外注意事项
- 代码中
System.out.println("password is: "+newPassword1)会触发char[]的toString()方法,生成类似[C@xxxx的标识字符串,不会泄露密码,但要绝对避免主动将char[]转换为String(比如new String(oldPassword)),否则会直接创建不可变的密码字符串实例。 - 即便使用
char[],也无法完全消除内存中密码残留的可能性——JVM的即时编译、内存拷贝等底层操作可能会在内存中留下数据副本,但这类残留比String导致的问题更难追踪和处理。
内容的提问来源于stack exchange,提问作者Yoo oo
相关产品推荐
相关产品推荐

