在本文中,我们将解释为什么我们应该使用 char[] 数组来表示密码,而不是 Java 中的字符串。
请注意,本教程重点介绍在内存中操作密码的方法,而不是存储密码的实际方法,后者通常在持久层中处理。
我们还假设我们无法控制密码的格式(例如,密码以字符串的形式来自第三方 API)。虽然使用 java.lang.String 类型的对象来操作密码似乎很明显,但 Java 团队自己建议使用 char[] 代替。
例如,如果我们看一下javax.swing的JPasswordField,我们可以看到返回String的方法getText()自Java 2以来被弃用,取而代之的是返回char[]的getPassword()方法。
字符串是不可变的
Java 中的字符串是不可变的,这意味着我们不能使用任何高级 API 更改它们。对 String 对象的任何更改都将生成一个新的字符串,并将旧字符串保留在内存中。
因此,存储在字符串中的密码将在内存中可用,直到垃圾回收器清除它。我们无法控制它何时发生,但这个时间段可能比常规对象长得多,因为字符串保存在字符串池中以实现可重用性。
因此,有权访问内存转储的任何人都可以从内存中检索密码。
使用 char[] 数组而不是 String,我们可以在完成预期工作后显式擦除数据。这样,我们将确保甚至在进行垃圾回收之前就从内存中删除密码。
现在让我们看一下代码片段,它们演示了我们刚刚讨论的内容。
第一个字符串:
System.out.print("Original String password value: ");
System.out.println(stringPassword);
System.out.println("Original String password hashCode: "
+ Integer.toHexString(stringPassword.hashCode()));
String newString = "********";
stringPassword.replace(stringPassword, newString);
System.out.print("String password value after trying to replace it: ");
System.out.println(stringPassword);
System.out.println(
"hashCode after trying to replace the original String: "
+ Integer.toHexString(stringPassword.hashCode()));
输出将是:
Original String password value: password
Original String password hashCode: 4889ba9b
String value after trying to replace it: password
hashCode after trying to replace the original String: 4889ba9b
现在对于字符[]:
char[] charPassword = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
System.out.print("Original char password value: ");
System.out.println(charPassword);
System.out.println(
"Original char password hashCode: "
+ Integer.toHexString(charPassword.hashCode()));
Arrays.fill(charPassword, '*');
System.out.print("Changed char password value: ");
System.out.println(charPassword);
System.out.println(
"Changed char password hashCode: "
+ Integer.toHexString(charPassword.hashCode()));
输出为:
Original char password value: password
Original char password hashCode: 7cc355be
Changed char password value: ********
Changed char password hashCode: 7cc355be
正如我们所看到的,在我们尝试替换原始 String 的内容后,值保持不变,hashCode() 方法在应用程序的相同执行中没有返回不同的值,这意味着原始字符串保持不变。
对于 char[] 数组,我们能够更改同一对象中的数据。
我们可能会意外打印密码
在 char[] 数组中使用密码的另一个好处是防止在控制台、显示器或其他或多或少不安全的地方意外记录密码。
让我们看看下一个代码:
String passwordString = "password";
char[] passwordArray = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
System.out.println("Printing String password -> " + passwordString);
System.out.println("Printing char[] password -> " + passwordArray);
使用输出:
Printing String password -> password
Printing char[] password -> [C@6e8cf4c6
我们看到内容本身在第一种情况下是打印的,而在第二种情况下,数据不是那么有用,这使得 char[] 不那么容易受到攻击。