Java 安全 : how to clear/zero-out memory associated with an object?(和/或确保这是特定变量的唯一实例/副本)

标签 java security memory

我正在讨论如何保护存储在 Java 程序中的敏感信息(例如密码)。根据安全要求,清除包含敏感信息的内存,例如通过将字节的值设置为全零。问题是攻击者可以观察到与应用程序进程关联的内存,因此我们希望尽可能地限制此类敏感信息存在的时间窗口。以前项目涉及到 C++,所以一个 memset() 就足够了。

(顺便说一句,memset() 的使用受到了质疑,因为已知一些编译器会优化它的使用,基于这样的假设,即由于以后不使用内存,因此无需首先将它归零。对于那些在谷歌上搜索“memset”和“clear memory”等的人来说,这个简介是免责声明。

现在我们手头有一个 Java 项目正在满足这个要求。

对于Java对象,我的理解是:

  • 空引用只会改变引用的值;对象的堆内存仍然包含数据
  • 像 String 这样的不可变对象(immutable对象)将无法修改其数据(或者至少在具有适当启用的安全管理器的 VM 范围内不容易修改)
  • 分代垃圾收集器可能会在各处复制对象(如 here 所述)

对于原语,我的理解是:

  • 本地方法中的原始类型变量将在堆栈上分配,并且:
  • 当你改变它的值时,你直接在内存中修改它(而不是使用引用来处理堆上的对象)。
  • 在某些情况下,可以/将在“幕后”制作副本,例如将其作为参数传递给方法或装箱(自动或不)创建包含另一个具有相同值的原始变量的包装器实例。

我的同事声称 Java 原语是不可变的,并且有来自 NSA 和 Oracle 的文档表明 Java 缺乏对此要求的支持。

我的立场是,可以(至少在某些情况下)通过将值设置为零(或将 boolean 值设置为 false)来将基元归零,并通过这种方式清除内存。

我正在尝试验证 JLS 或其他“官方”文档中是否有关于 JVM 在与原语相关的内存管理方面所需行为的语言。我能找到的最接近的是 "Secure Coding Guidelines for the Java Programming Language"在 Oracle 的网站上提到使用后清除 char 数组。

当我的同事称原语不可变时,我会对定义提出质疑,但我很确定他的意思是“内存不能适本地归零”——我们不用担心。我们没有讨论他是否指的是最终变量——从我们一般谈论的上下文来看。

对此是否有任何明确的答案或引用资料?如果有任何东西可以告诉我我错在哪里或确认我是对的,我将不胜感激。

编辑:经过进一步讨论,我已经能够澄清我的同事正在考虑原始包装器,而不是原始包装器本身。所以我们留下了如何安全地清除内存的原始问题,最好是对象。此外,需要澄清的是,敏感信息不仅是密码,还包括 IP 地址或加密 key 等信息。

是否有任何商业 JVM 提供诸如优先处理某些对象的功能? (我想这实际上会违反 Java 规范,但我想我会问一下,以防我错了。)

最佳答案

编辑:实际上我只有三个可能确实有效的想法——至少对于不同的“工作”值(value)。

第一个或多或少记录的将是 ByteBuffer.allocateDirect!据我了解,allocateDirect 在通常的 java 堆之外分配缓冲区,因此不会被复制。我找不到任何关于它不会在所有情况下都被复制的硬性保证——但对于当前的 Hotspot VM 来说,情况确实如此(即它被分配在一个额外的堆中),我认为这将保持这种状态。

第二个是使用 sun.misc.unsafe 包 - 顾名思义,它有一些相当明显的问题,但至少它几乎独立于使用的 VM - 要么它受支持(并且它工作)要么它是不是(并且您会收到链接错误)。问题是,使用这些东西的代码会很快变得非常复杂(仅获得一个不安全的变量并不容易)。

第三个是分配一个比实际需要的大很多很多的大小,以便对象在老年代堆开始分配:

l-XX:PretenureSizeThreshold= that can be set to limit the size of allocations in the young generation. Any allocation larger than this will not be attempted in the young generation and so will be allocated out of the old generation.

我认为该解决方案的缺点很明显(默认大小似乎约为 64kb)。

。 .

无论如何这里是旧答案:

是的,正如我所见,您几乎不能保证存储在堆上的数据在不留下副本的情况下被 100% 删除(如果您不想要一个通用的解决方案,但可以使用的解决方案说当前的 Hotspot VM 及其默认的垃圾收集器)。

正如您在链接的帖子 (here) 中所说,垃圾收集器几乎无法保证这一点。实际上与帖子所说的相反,这里的问题不是分代 GC,而是 Hotspot VM(现在我们是特定于实现的)默认情况下为其年轻代使用某种 Stop & Copy gc。

这意味着一旦在将密码存储在 char 数组中和将其清零之间发生垃圾收集,您将获得数据的副本,该副本仅在下一次 GC 发生时才会被覆盖。请注意,对对象进行永久化将具有完全相同的效果,但不是将其复制到空间,而是将其复制到旧代堆中——我们最终会从空间中获得未被覆盖的数据副本。

为了避免这个问题,我们几乎需要某种方法来保证在存储密码和将密码归零之间不会发生垃圾收集,或者从一开始就将 char 数组存储在老年代堆中。另请注意,这依赖于 Hotspot VM 的内部结构,可能会发生很大变化(实际上有不同的垃圾收集器可以生成更多副本;iirc Hotspot VM 支持使用训练算法的并发 GC)。 “幸运的是”不可能保证其中任何一个(afaik 每个方法调用/返回都会引入一个安全点!),所以你甚至不会尝试尝试(特别是考虑到我看不到任何方法来确保JIT 不会优化归零);)

似乎保证数据仅存储在一个位置的唯一方法是使用 JNI。

PS:请注意,虽然上述内容仅适用于堆,但您不能保证堆栈的更多内容(JIT 可能会优化写入而无需读取堆栈,因此当您从函数返回数据时仍将在堆栈上)

关于Java 安全 : how to clear/zero-out memory associated with an object?(和/或确保这是特定变量的唯一实例/副本),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6473352/

相关文章:

java - 如何将带数组的字符串变量转换为带数组的整数变量

sql - 如何避免注入(inject)按运行时值排序查询

php - 如何检测 PHP 中的 X-Accel-Redirect (Nginx)/X-Sendfile (Apache) 支持?

java - 在java中存储 map 哪个内存效率最高?

java - Libgdx - Actor 的位置不正确

java.lang.NoSuchMethodError : jodd. Jodd.init(Ljava/lang/Class;)V

java - 如何在返回 Futures 的 Java ExecutorService 中调试 Callable

java - java.security.cert.Certificate 中的验证方法是线程安全的吗?

javascript - 在 Javascript Web 应用程序中保护私有(private) API key

c++ - 从偏移创建一个 DWORD 指针