问答中心分类: STRING为什么 char[] 优于 String 的密码?
0
匿名用户 提问 1小时 前

在 Swing 中,密码字段有一个getPassword()(返回char[]) 方法而不是通常的方法getText()(返回String) 方法。同样,我遇到了一个建议不要使用String处理密码。
为什么String当涉及到密码时,会对安全构成威胁吗?使用起来感觉不方便char[].

17 Answers
0
Konrad Garus 回答 1小时 前

虽然这里的其他建议似乎有效,但还有一个很好的理由。与平原String你有更高的机会不小心将密码打印到日志中、监视器或其他一些不安全的地方。char[]不那么脆弱。
考虑一下:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

印刷:

String: Password
Array: [C@5829428e
bestsss 回复 1小时 前

@voo,但我怀疑您是否会通过直接写入流和连接来登录。日志框架会将 char[] 转换为良好的输出

Konrad Garus 回复 1小时 前

@Thr4wn 默认实现toStringclassname@hashcode.[C代表char[],其余为十六进制哈希码。

mauhiz 回复 1小时 前

有趣的想法。我想指出,这不会转置为对数组具有有意义的 toString 的 Scala。

user1804599 回复 1小时 前

我会写一个Password类类型。它不那么晦涩难懂,也更难意外通过某个地方。

GC_ 回复 1小时 前

为什么有人会假设 char 数组将被转换为对象?我不确定我明白为什么每个人都喜欢这个答案。假设你这样做了: System.out.println("Password".toCharArray());

Gavin S. Yancey 回复 1小时 前

鉴于 Java 的数组哈希码实现不是安全哈希,攻击者发现[C@5829428e无论如何,在日志文件中可能会暴力破解它来自的密码,因此这提供的安全优势可以忽略不计。

Octavia Togami 回复 1小时 前

@g.rocket 数组的哈希码不依赖于数组的内容,它实际上是随机的:在线示例

ACV 回复 1小时 前

这不是原因char[]建议代替String

Dawesi 回复 1小时 前

为什么首先存储的密码未加密?它是从请求中接收到内存中的(作为字符串存储在内存中??)然后应该立即进行散列、比较和丢弃,或者是否应该从已经加密的外部保险库中获取另一个系统,然后在使用时使用旋转密钥解密?我在这里错过了什么,试图解决这个问题吗?

luis.espinal 回复 1小时 前

密码可能不会在未加密的情况下存储 – 事实上,存储的是从密码生成的安全加密和散列的字节序列,该密码已 1) nonced、2) salted 和 3) 加长。我们称之为字节序列C.但是,在某个地方,明文可能会被处理为 salt/nonce/lengthen 然后加密以进行比较C.因此,最好的方法是始终假设我们将要持有的看起来像密码的东西是明文并尽可能短暂地存储它。

Mikko Rantalainen 回复 1小时 前

如果您的程序曾经使用函数参数记录堆栈跟踪,则任何参数都可能作为文本泄漏。

0
Bruno 回答 1小时 前

引用官方文件,Java 密码体系结构指南说这个char[]对比String密码(关于基于密码的加密,但这当然更普遍地与密码有关):

将密码收集并存储在类型的对象中似乎是合乎逻辑的java.lang.String.但是,这里有一个警告:Object类型String是不可变的,即没有定义允许您更改(覆盖)或将 a 的内容归零的方法String使用后。此功能使String不适合存储用户密码等安全敏感信息的对象。您应始终将安全敏感信息收集并存储在一个char代替数组。

Java 编程语言安全编码指南 4.0 版的指南 2-2也说了类似的话(尽管它最初是在日志记录的上下文中):

准则 2-2:不要记录高度敏感的信息
一些信息,例如社会安全号码 (SSN) 和密码,是高度敏感的。此信息不应保存超过必要的时间,也不应保存在可能被管理员看到的地方。例如,不应将其发送到日志文件,并且不应通过搜索检测到其存在。一些瞬态数据可能保存在可变数据结构中,例如 char 数组,并在使用后立即清除。清除数据结构在典型的 Java 运行时系统上降低了效率,因为对象在内存中对程序员透明地移动。
该指南还对不具备所处理数据的语义知识的低级库的实现和使用产生影响。例如,低级字符串解析库可能会记录它处理的文本。应用程序可以使用库解析 SSN。这会产生一种情况,即 SSN 可供有权访问日志文件的管理员使用。

bestsss 回复 1小时 前

这正是我在乔恩的回答下面谈到的有缺陷/虚假的参考资料,这是一个众所周知的来源,有很多批评。

user961954 回复 1小时 前

@bestass你能不能也引用一个参考?

SnakeDoc 回复 1小时 前

@bestass 对不起,但是String很好理解以及它在 JVM 中的行为方式……有充分的理由使用char[]代替String以安全的方式处理密码时。

Dawesi 回复 1小时 前

尽管密码作为字符串从浏览器传递给请求,但又是“字符串”而不是字符?所以无论你做什么,它都是一个字符串,此时它应该被处理并丢弃,而不是存储在内存中?

luis.espinal 回复 1小时 前

@Dawesi –At which point– 那是特定于应用程序的,但一般规则是一旦你拿到应该是密码的东西(明文或其他)就这样做。例如,您可以从浏览器获取它作为 HTTP 请求的一部分。你无法控制投递,但你可以控制自己的存储,所以一拿到它,就把它放在一个 char[] 中,用它做你需要做的事情,然后将所有设置为 '0' 并让 gc收回它。

0
alephx 回答 1小时 前

字符数组 (char[]) 可以在使用后通过将每个字符设置为零而不是字符串来清除。如果有人能以某种方式看到内存图像,如果使用字符串,他们可以看到纯文本密码,但如果char[]使用,用0清除数据后,密码是安全的。

avgvstvs 回复 1小时 前

默认情况下不安全。如果我们谈论的是 Web 应用程序,大多数 Web 容器会将密码传递到HttpServletRequest明文对象。如果 JVM 版本是 1.6 或更低,它将在 permgen 空间中。如果它在 1.7 中,它在被收集之前仍然是可读的。 (无论何时。)

Holger 回复 1小时 前

@avgvstvs:字符串不会自动移动到 permgen 空间,这只适用于实习字符串。除此之外,permgen 空间也受到垃圾收集的影响,只是速度较低。 permgen 空间的真正问题是它的固定大小,这正是没有人应该盲目调用的原因intern()在任意字符串上。但你是对的String实例首先存在(直到收集)并将它们变成char[]之后的数组不会改变它。

avgvstvs 回复 1小时 前

@Holger 见docs.oracle.com/javase/specs/jvms/se6/html/…“否则,将创建一个 String 类的新实例,其中包含 CONSTANT_String_info 结构给出的 Unicode 字符序列;该类实例是字符串文字推导的结果。最后,调用新 String 实例的 intern 方法。”在 1.6 中,当 JVM 检测到相同的序列时,它会为您调用 intern。

Holger 回复 1小时 前

@avgvstvs:这与常量池类的条目,仅描述字符串文字。一个类的常量池,顾名思义,只包含持续的字符串,显然必须在编译时知道(否则它们怎么会出现在类文件中)。在运行时创建的字符串,即通过Stringbuilder或其中一个构造函数String不受影响。

avgvstvs 回复 1小时 前

@Holger,您是对的,我将常量池和字符串池混为一谈,但是 permgen 空间也是错误的只要应用于实习字符串。在 1.7 之前,constant_pool 和 string_pool 都驻留在 permgen 空间中。这意味着分配给堆的唯一一类字符串就像你说的那样,new String()或者StringBuilder.toString()我用大量字符串常量管理应用程序,结果我们有很多 permgen 蠕变。直到 1.7。

Holger 回复 1小时 前

@avgvstvs:嗯,字符串常量,按照 JLS 的要求,总是被实习的,因此实习字符串最终在 permgen 空间中的语句,隐式应用于字符串常量。唯一的区别是字符串常量首先是在 permgen 空间中创建的,而调用intern()在任意字符串上可能会导致在 permgen 空间中分配等效字符串。如果没有共享该对象的相同内容的文字字符串,则后者可能会被 GC 处理……

Joshua Taylor 回复 1小时 前

不过需要注意的是,一些关于字符串的信息仍然泄漏(如果可以检查内存):长度的密码。使用 C 风格的字符串,我们可以只拥有一个内存缓冲区,一旦它被清空,我们不知道原始字符串有多长。随着char[]在 Java 中,我们做到了。如果攻击者可以看到内存,并且看到 getPassword() 返回一个char[1],这表明比返回 a 的目标更容易暴力破解目标char[16].

0
josefx 回答 1小时 前

有些人认为,一旦不再需要密码,就必须覆盖用于存储密码的内存。这减少了攻击者必须从您的系统读取密码的时间窗口,并且完全忽略了攻击者已经需要足够的访问权限来劫持 JVM 内存来执行此操作的事实。具有如此多访问权限的攻击者可以捕获您的关键事件,从而使其完全无用(AFAIK,如果我错了,请纠正我)。
更新
感谢评论,我必须更新我的答案。显然,在两种情况下,这可以增加(非常)小的安全性改进,因为它减少了密码可能落在硬盘上的时间。我仍然认为对于大多数用例来说这太过分了。

  • 您的目标系统可能配置不正确,或者您必须假设它是,并且您必须对核心转储持偏执态度(如果系统不是由管理员管理,则可能有效)。
  • 您的软件必须过于偏执,以防止攻击者获得对硬件的访问权限而导致数据泄漏 – 使用诸如TrueCrypt(停产),VeraCrypt, 或者密码棚.

如果可能,禁用核心转储和交换文件将解决这两个问题。但是,它们需要管理员权限,并且可能会减少功能(使用更少的内存)并且从正在运行的系统中提取 RAM 仍然是一个有效的问题。

Joachim Sauer 回复 1小时 前

我会用“只是轻微的安全改进”来代替“完全没用”。例如,如果您碰巧拥有对 tmp 目录的读取权限、一台配置错误的机器以及您的应用程序崩溃,那么您就可以访问内存转储。在这种情况下,您将无法安装键盘记录器,但是您可以分析核心转储。

Dan Is Fiddling By Firelight 回复 1小时 前

完成后立即从内存中擦除未加密的数据被认为是最佳实践,而不是因为它是万无一失的(它不是);但因为它降低了你的威胁暴露水平。这样做并不能防止实时攻击;但是因为它通过显着减少在对内存快照的追溯攻击中暴露的数据量(例如,写入交换文件或从内存中读取的应用程序内存的副本)来提供损害缓解工具从正在运行的服务器并在其状态失败之前移动到另一个服务器)。

kingdango 回复 1小时 前

我倾向于同意这种回应的态度。我冒昧地提出,大多数安全漏洞的后果都发生在比内存中的位更高的抽象级别上。当然,在超安全防御系统中可能存在这样的场景,这可能是相当令人担忧的,但在这个级别上认真思考对于 99% 正在利用 .NET 或 Java 的应用程序来说是多余的(因为它与垃圾收集有关)。

Rolf Rander 回复 1小时 前

也许 localhost 不是您想要保护的。也许您创建了一个在不安全的客户端上运行的程序,并且您需要密码才能访问数据库。如果您不信任客户端并试图保护数据库,那么从内存中擦除密码是一个不错的建议。

josefx 回复 1小时 前

@RolfRander 更糟糕的是,您不应该在不受信任的客户端上输入密码 – 使用一次性密钥(不确定这是正确的术语,密码只能使用一次并在使用后被锁定)。

Rolf Rander 回复 1小时 前

大多数网站使用密码登录。您的信用卡号码和安全数字也是一种密码。

josefx 回复 1小时 前

@RolfRander 没有任何网站需要不受信任的客户端,如果用户选择使用受感染的系统,那么您将无法保护他免受后果。

Rolf Rander 回复 1小时 前

正如我所说,重点可能是保护服务器,而不是客户端。我对网站示例的观点是,有很多服务使用密码(出于不同的原因,尽管其他机制显然会更安全。底线是您想要保护服务器的场景,不会信任客户端并且正在使用密码,将密码存储在 char[] 中是一个明智的选择,这并不是一个牵强的场景。

josefx 回复 1小时 前

@RolfRander 不知何故,我觉得您对所有客户都使用单一登录,并且没有任何安全/验证服务器端。您有责任确保用户不会破坏数据库(访问限制和验证服务器端),而用户有责任确保他的帐户不会被破坏(他的密码)。

Rolf Rander 回复 1小时 前

绝对地。但是为了确保用户的密码不被泄露,处理密码的软件必须注意减少恶意软件(在客户端上)从客户端应用程序内存中获取密码的机会。为了实现这一点,我相信将密码存储在 char[] 中并在之后覆盖它是个好建议。

Tinman 回复 1小时 前

我想说有人了解 char 与 sting 背后的想法,那么他们在配置服务器时更有可能考虑安全性。不思考通常会引入安全漏洞。

Peter vdL 回复 1小时 前

在 Heartbleed 穿透服务器内存并泄露密码之后,我会将字符串“只是一个小的安全改进”替换为“绝对不能使用字符串作为密码,而是使用 char []。”

josefx 回复 1小时 前

@PetervdL heartbleed 的发生是因为有人知道重用内存比分配新的(清除的)内存更便宜,你不能在 Java 中重用字符串,只是希望没有人想到将你的 char[] 放入重用列表中更好的性能。

Peter vdL 回复 1小时 前

你完全没有抓住重点,约瑟夫。关键是心脏出血允许远程读取进程内存,迄今为止每个人都认为这不太可能。当与 Java 字符串结合使用时,密码很容易被泄露。 Char [] 允许开发人员清除密码,String 甚至不给这个机会。如果编码器搞砸了,Char [] + heartbleed 可能是有害的。 String + heartbleed == 绝对总是有害的。

josefx 回复 1小时 前

@PetervdL heartbleed 仅允许读取特定的重用缓冲区集合(用于安全关键数据和网络 I/O,而不会在两者之间清除 – 出于性能原因),您不能将其与 Java 字符串结合使用,因为它们在设计上不可重用.您也不能使用 Java 读入随机内存来获取字符串的内容。导致心脏出血的语言和设计问题对于 Java 字符串是不可能的。

josefx 回复 1小时 前

@PetervdL 那么请不要提及“与 Java 字符串结合使用的心脏出血错误”,我确信我提到的与心脏出血有关的大部分内容都是正确的,libreSSL 开发人员非常详细地了解了为什么 OpenSSL 是一个安全噩梦。 “可能”有一种方法可以使用 JIT 中的错误来绕过 Java 内置范围检查或对象内存的自动归零,并且“可能”有人可以从远程位置触发它而不会导致 JVM 崩溃,但是这与 heartbleed 没有任何共同之处,JVM 上的任何错误也不会如此简单或针对漏洞利用进行优化。

DavidS 回复 1小时 前

@PetervdL 这不仅仅是 Heartbleed 错误的“特定”;这是完全不适用的。我建议您不要将 Heartbleed 作为安全措施的理由,除非您更好地了解它的性质。感谢 josefx 阐明了这个经常被误解的问题。

ACV 回复 1小时 前

还有另一种情况 – 当 JVM 崩溃并产生内存转储时。

Dawesi 回复 1小时 前

为什么要将密码存储在内存中?外部系统? web 或 api 请求提交一个字符串(具有讽刺意味),所以它无论如何都在内存中。我永远不会出于任何原因将登录密码存储在另一个未加密的变量中(除了它发送到服务器的只读变量)? Web 请求是否应该将字母转换为它们的 char 编号以等效于提交,以便请求变量是一个数字?因为浏览器没有 char 数据类型?

Stefan L 回复 1小时 前

为了完整起见,我们不要忘记最近的崩溃和幽灵攻击:熔断攻击.com可以通过网络浏览器或共享托管环境来利用它。

0
Sean Owen 回答 1小时 前

我不认为这是一个有效的建议,但是,我至少可以猜到原因。
我认为动机是希望确保您可以在使用后立即并确定地清除内存中的所有密码痕迹。带一个char[]您可以肯定地用空白或其他内容覆盖数组的每个元素。您不能编辑 a 的内部值String那样。
但这并不是一个好的答案。为什么不只是确保参考char[]或者String不逃?那么就没有安全问题了。但问题是String对象可以是intern()理论上 ed 并在常量池中保持活力。我想使用char[]禁止这种可能性。

Groo 回复 1小时 前

我不会说问题在于您的参考文献会或不会“逃脱”。只是字符串将在内存中保持未修改一段时间,而char[]可以修改,那么收不收就无关紧要了。而且由于需要为非文字显式地完成字符串实习,这就像告诉一个char[]可以被静态字段引用。

Dawesi 回复 1小时 前

内存中的密码不是作为表单帖子中的字符串吗?