为什么 char[] 比 String 更适合存储密码?带代码的指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么这个问题至今仍然重要?



想象一个场景:您正在编写一个登录系统的应用程序。用户输入密码,您需要验证它。似乎最简单的方法是用字符串(String)来存储它。但经验丰富的 Java 开发者会告诉您:"永远不要使用 String 来存储密码!请使用 char[]!"。为什么呢?



这个问题是 Stack Overflow 上最热门的问题之一(超过 100,000 次浏览)。问题不在于 String 不能存储字符。更深层次的原因在于:它涉及安全性、内存管理和数据泄漏。在本文中,我们将分析为什么使用 char[] 存储密码是安全标准,并通过代码示例展示如何在 Java 中正确实现这一实践。



如果您刚开始学习 Java,或者希望提高应用程序的安全性,那么本文适合您。



问题 #1:String 的不可变性



Java 中的 String 类是不可变的。这意味着一旦创建了 String 对象,其值就无法更改。任何看似"更改"字符串的操作(例如 toUpperCase() 或 replace()),实际上都会在内存中创建一个新的 String 对象。



为什么这对密码不好?让我们看一个例子:



String password = "MySecret123";// ... 使用密码进行验证 ...// 我们完成了密码的使用password = null; // "清除"引用


您认为已经清除了内存?不!对象 "MySecret123" 仍然存在于内存中,位于所谓的 String 池中。垃圾回收器不会立即删除它。它会一直保留在那里,直到下一次垃圾回收,更准确地说——直到没有引用指向它。即使在 password = null 之后,该对象本身也可能在内存中保留不确定的时间。



如果攻击者获取了您应用程序的内存转储(heap dump)呢?他们就能以明文形式读取密码。String 的不可变性使得无法立即从内存中删除密码。



问题 #2:String 池和密码泄漏



Java 通过 String 池优化字符串操作。当您使用字面量(例如 "password")创建字符串时,它会被放入池中。如果程序的另一部分使用相同的字符串,它只会引用现有对象。



现在想象一下,您的密码是 "admin123"。这个相同的密码可能在系统中的其他地方被意外使用(例如,在日志中、文件名中或配置中)。字符串 "admin123" 在池中永久存在(至少直到 JVM 终止)。这增加了攻击面。



// 可能导致泄漏的代码String password = "admin123";System.out.println("密码: " + password); // 密码进入日志// 即使之后 password = null,字符串 "admin123" 仍然留在日志和池中


使用 char[] 数组则没有这个问题:您可以在使用后显式覆盖数组的每个元素。



解决方案:char[] 数组和手动内存管理



char[] 字符数组是一个可变对象。您可以随时更改其内容。主要优势在于:您可以在密码不再需要后立即清除数组。



以下是实际应用中的样子:



import java.util.Arrays;

public class PasswordExample { public static void main(String[] args) { // 错误做法:String String passwordString = "MySecret123"; // ... 验证 ... passwordString = null; // 密码可能仍在内存中 // 正确做法:char[] char[] passwordChars = { 'M', 'y', 'S', 'e', 'c', 'r', 'e',

博客

书籍推荐