KC *_*ong 72 java password-protection
我char[]早在Usenet时代就学会了如何使用comp.lang.java.*.
搜索 Stack Overflow,你还可以轻松找到像这样高票数的问题: Why is char[] Preferred over String for passwords? 这与我很久很久以前学到的一致。
我仍然编写 API 以用于char[]密码。但现在这只是空洞的理想吗?
例如,查看Atlassian Jira的 Java API: LoginManager.authenticate,它将您的密码作为字符串。
或者Thales的 Luna Java API:
LunaSlotManager 中的 login() 方法。其中,HSM供应商使用StringHSM 插槽密码。
我想我还在某处读到 URLConnection (和许多其他类)的内部结构String在内部使用来处理数据。因此,如果您发送密码(尽管密码通过网络通过TLS加密),它将以字符串形式保存在服务器内存中。
访问服务器内存是否是一个很难实现的攻击因素,以至于可以像String现在一样存储密码?或者泰勒斯这样做是因为你的密码最终会String由于其他人编写的类而最终被泄露?
Kon*_*lph 49
首先,让\xe2\x80\x99s 回顾一下建议使用而不是 的原因:s 是不可变的,因此一旦创建了字符串,对字符串内容的控制就受到限制,直到(可能很久之后)内存被占用。垃圾收集。因此,可以转储进程内存的攻击者有可能读取密码数据。同时,对象的内容在创建后可以被覆盖。假设这已完成,并且 GC 在此期间没有将对象移动到另一个物理内存位置,这意味着密码内容在使用后可以(在某种程度上)确定性地销毁。此后读取进程内存的攻击者将无法获取密码。char[]StringStringchar[]
因此,使用char[]代替String可以防止非常特定的攻击场景,其中攻击者可以完全访问进程内存,1但只能在特定时间点而不是连续访问。即使在这种情况下,使用char[]和覆盖其内容也不会阻止攻击,它只是降低了成功的机会(如果攻击者碰巧在创建和删除密码之间读取了进程内存,他们就可以读取它)。
我不知道有任何证据表明(a)这种情况有多频繁,也没有(b)这种缓解措施在这种情况下降低了成功概率。据我所知,这纯粹是猜测。
\n事实上,在大多数系统上,这种情况可能根本不存在:能够访问另一个进程\xe2\x80\x99内存的攻击者也可以获得完全跟踪访问权限。例如,在 Linux 和 Windows 上,任何可以读取另一个进程\xe2\x80\x99 内存的进程也可以将任意逻辑注入该进程(例如,通过LD_PRELOAD和类似的机制2)。所以我想说,这种缓解措施最多只能带来有限的好处,甚至可能根本没有好处。
\xe2\x80\xa6 实际上我可以想到一个具体的反例:加载不受信任的插件库的应用程序。一旦通过传统方式加载该库(即在同一内存空间中),它就可以访问父应用程序。在这种情况下,如果在加载插件之前处理密码,则使用char[]代替并在完成后覆盖其内容可能是有意义的。但更好的解决方案是不要将不受信任的插件加载到同一内存空间中。一种常见的替代方案是在单独的进程中启动它并通过 IPC 进行通信。String
(有关更脆弱的场景,请参阅 Gilles 的答案。我仍然认为好处相对有限,但它 \xe2\x80\x99s 显然不是零。)
\n1如 Gilles\xe2\x80\x99 答案所示,这是不正确的:没有完整的内存访问即可发起成功的攻击。
\n2虽然LD_PRELOAD特别要求攻击者不仅有权访问另一个进程,而且还可以启动该进程,或者有权访问其父进程。
Gil*_*il' 37
(注:我是安全专家,但不是 Java 专家。)
是的,使用char[]密码而不是字符串作为密码具有显着的安全优势。这在某种程度上也适用于其他高度机密的数据,尽管大多数高度机密的数据(例如加密密钥)往往是字节而不是字符。
旧的但仍然有效的使用理由char[]是在使用内存后立即清理内存,而这是不可能的String。这是一种非常牢固的安全实践。例如,在著名的FIPS 140加密处理要求中(通常被认为是安全要求),实际上 1 级(最简单的级别)的安全要求极少。事实上,只有两个:一是只能使用经过批准的加密算法,二是密钥、密码和其他敏感数据在使用后必须擦除。
这种做法是加密原语的生产实现通常采用具有手动内存管理的语言(例如 C、C++ 或 Rust)来实现的原因之一:加密实现者希望保留对敏感数据去向的控制,并确保擦除所有副本敏感材料。
作为可能出错的示例,请考虑(臭名昭著的)Heartbleed bug。它允许互联网上连接到易受攻击的服务器的任何人在不被发现的情况下转储服务器的一些内存。攻击者无法控制内存的哪一部分,但可以一次又一次地尝试。攻击者可能发出会导致可转储部分在堆中移动的请求,从而可能转储整个内存。
这种错误常见吗?不。这个引起了很大的关注,因为它是在一个非常流行的软件中,而且后果很糟糕。但此类错误确实存在,最好加以防范。
另外,从Java 8开始,还有一个原因,就是避免字符串去重。字符串去重意味着如果两个String对象有相同的内容,它们可能会被合并。如果攻击者在尝试重复数据删除时可以发起侧通道攻击,那么字符串重复数据删除就会出现问题。该攻击不需要对密码进行重复数据删除(尽管在这种情况下更容易):只要某些代码将密码与另一个字符串进行比较,就会出现问题。
比较字符串是否相等的常用方法是:
这有一个计时侧通道:中间步骤的时间取决于字符串开头相同字符的数量。假设攻击者可以测量这个时间,并且可以上传一些字符串进行比较(例如,通过向服务器发出合法请求)。攻击者注意到比较 比sssssssss比较花费的时间稍长aaaaaaaaa,因此密码必须以 开头s。然后攻击者尝试改变第二个字符,并发现与 进行比较swwwwwwww再次需要稍长的时间。这样,攻击者就可以在相对较短的时间内逐个字符地重建密码。
在字符串重复数据删除的上下文中,攻击更加困难,因为(据我所知)重复数据删除代码首先对要比较的字符串进行哈希处理。这可能意味着攻击者必须首先猜测哈希值。但是给定哈希表中的哈希值总数(即哈希桶的数量,而不是方法的全部范围hash)足够小,因此可以进行枚举。
可以肯定的是,这不是一次容易的攻击。但我绝对不会排除这种可能性,尤其是对于本地攻击者,但即使对于远程攻击者也是如此。远程定时攻击是实用的(仍然)。
总之,是的,您不应该使用String密码。将它们读为char[],仔细跟踪任何副本,如果您正在验证它们,请尽快对它们进行哈希处理,然后擦除所有副本。
如果您需要存储第三方服务的密码,最好以加密形式存储它,即使加密密钥没有单独的访问控制。加密密码的副本比密码本身的副本更不容易通过侧通道泄漏,密码本身是一个低熵的可打印字符串。
我想我还在某处读到 URLConnection (和许多其他类)的内部使用 String 内部来处理数据。因此,如果您发送密码(尽管密码通过网络通过 TLS 加密),它将以字符串形式保存在服务器内存中。
我不是 Java 专家,但这听起来不对:连接的明文(TLS 或其他)是字节流,而不是字符流。它应该是 8 位字节的数组,而不是 Unicode 代码点的数组。
或者,由于其他人编写的类,您的密码无论如何都会以字符串形式结束,这就是泰勒斯这样做的原因。
可能吧。或者可能是因为他们不是 Java 专家,或者因为编写高层的人通常不是最重要的安全专家。
Jim*_*mes 14
答案中有很多细节,但这里有一个缺点:是的,理论上,将密码放入数组中并擦除它可以提供安全优势。实际上,只有当您能够避免将密码存储在字符串中时,这才有帮助。也就是说,如果您获取存储在字符串中的密码并将该字符串的内容放入 char[] 中,它不会神奇地使该字符串从堆中消失。必要的要求是密码永远不会放在字符串中。我有兴趣看到它在真正的 Java 应用程序中成功实现。
小智 11
几乎其他人的答案加上一点:交换存储介质上的空间。
如果 JVM 堆曾经被分页到磁盘并且密码仍然作为字符串(不可变且未被 GC 处理)存在于内存中,则它将被写入交换文件。然后可以扫描此交换文件中的密码值,因此,本质上是另一个基于时间的攻击向量,并且仍然相当难以利用,但显然并不那么困难,因为我们在这里:D。
擦除可变数组至少可以减少密码在内存中的时间。
我听到的故事是,如果攻击者可以攻击您的进程(如 DDOS)并触发进程换出,那么攻击交换空间比攻击内存更容易,并且交换空间在启动/崩溃等过程中都会保留。这允许另一种攻击媒介,攻击者拉出交换驱动器来扫描交换空间。
小智 5
这不是通过网络传输的时刻的想法。实际上,您确实最好使用字符串,因为它更方便用于通过网络发送,当然要确保它已正确加密。
对于在应用程序中使用密码,由于堆栈转储和逆向工程以及字符串不可变的问题,情况有所不同:如果输入了密码,即使对变量的引用更改为另一个字符串,也没有确定性关于垃圾收集器何时实际从堆中删除字符串。因此,能够看到转储的黑客也将能够看到密码。使用 char 数组可以防止这种情况,因为您可以直接更改数组中的数据,而无需依赖垃圾收集器。
现在您可能会说:那么当将其作为字符串通过网络发送时,它仍然可见,不是吗?是的,但这就是为什么在发送之前对其进行加密很重要。尽可能不要通过网络发送纯文本密码。
| 归档时间: |
|
| 查看次数: |
6206 次 |
| 最近记录: |