Java安全性:如何清除/清零与对象相关的内存?(和/或确保这是特定变量的唯一实例/副本)

2022-09-01 04:00:46

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

(顺便说一句,memset() 的使用受到了质疑,因为已知一些编译器会根据以下假设来优化其使用结果二进制文件:由于以后不会使用内存,因此无需首先将其归零。这个 blurb 是那些谷歌“memset”和“clear memory”等的人的免责声明)。

现在,我们手上有一个 Java 项目正在面临这一要求的压力。

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

  • 空引用仅更改引用的值;对象堆上的内存仍包含数据
  • 像 String 这样的不可变对象将无法修改其数据(或者至少不容易,在具有适当启用的安全管理器的 VM 范围内)
  • 代际垃圾回收器可以到处复制对象(如此所述))

对于原语,我的理解是:

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

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

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

我试图验证JLS或其他“官方”文档中是否有关于JVM在内存管理方面与原语相关的所需行为的语言。我能找到的最接近的是Oracle网站上的“Java编程语言安全编码指南”,其中提到使用后清除char数组。

当我的同事称原语不可变时,我会对定义提出质疑,但我非常确定他的意思是“内存不能被适当地归零” - 让我们不要担心这一点。我们没有讨论他是否意味着最终变量 - 从上下文中我们一般地谈论。

对此是否有任何明确的答案或参考?我很感激任何能告诉我哪里错了或确认我是对的东西。

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

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


答案 1

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

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

第二个是使用sun.misc.unsafe软件包 - 顾名思义,它有一些相当明显的问题,但至少这几乎与使用的VM无关 - 要么它受支持(并且它有效)要么不受支持(并且你会得到链接错误)。问题是,使用这些东西的代码很快就会变得非常复杂(单独获得一个不安全的变量是非同小可的)。

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

l-XX:PretenureSizeThreshold=,可以设置为限制年轻一代的分配规模。任何大于此值的分配都不会尝试在年轻一代中,因此将从老一代中分配出来。

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

..

无论如何,这里的旧答案是:

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

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

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

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

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

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


答案 2

告诉你的同事,这是一个无望的事业。那么内核套接字缓冲区呢,只是一个开始。

如果无法阻止有害程序监视计算机上的内存,则密码会受到威胁。时期。