Java安全性:如何清除/清零与对象关联的内存? (和/或确保这是唯一的实例/特定variables的副本)
我正在讨论如何保护存储在Java程序中的敏感信息(例如密码)。 根据安全要求,将清除包含敏感信息的内存,例如将字节值设置为全零。 担心的是攻击者可以观察与应用程序进程相关的内存,所以我们要尽可能地限制这种敏感信息的时间窗口。 以前,项目涉及C ++,因此memset()已经足够了。
(顺便说一句,memset()的使用已经成为问题,因为已知某些编译器会根据假设来优化它的使用,因为以后不会使用内存,所以不需要将其归零第一个地方,这个blurb对于那些Google为“memset”和“清除内存”的人来说是一个免责声明)。
现在,我们已经掌握了一个Java项目,正在按这个要求。
对于Java对象,我的理解是:
- 一个空引用只改变引用的值; 对象的堆上的内存仍然包含数据
- 一个像String这样的不可变对象将无法修改数据(或者至less不是很容易的,在具有相应的安全pipe理器的虚拟机的范围内)
- 代代垃圾收集者可以在整个地方复制物体(如这里所述 )
而对于原始的,我的理解是:
- 本地方法中的原始typesvariables将被分配到堆栈上,并且:
- 当你改变它的值的时候,你直接在内存中修改它(而不是使用引用来处理堆上的对象)。
- 在某些情况下,副本可以/在“幕后”进行,例如将其作为parameter passing给方法或装箱(自动或不是)创build包含另一个具有相同值的原始variables的包装的实例。
我的同事声称,Java原语是不可改变的,NSA和Oracle都有文档说明在这种需求方面缺乏对Java的支持。
我的立场是,基元可以(至less在某些情况下)通过将值设置为零(或布尔值为false)来清零,并且内存以这种方式被清除。
我试图validationJLS或其他“官方”文档中是否存在关于JVM在涉及基元的内存pipe理时所需行为的语言。 我能find的最接近的是Oracle网站上的“Java编程语言安全编码指南” ,其中提到了在使用后清除字符数组。
当我的同事把原始的东西称为不可改变的时候,我会质疑定义,但我很确定他的意思是“记忆不能适当地调零” – 我们不用担心。 我们没有讨论他是否意味着最后的变数 – 从我们一般说的上下文来看。
有没有什么明确的答案或参考? 我会很感激任何可以告诉我我错在哪里或确认我是对的。
编辑 :经过进一步的讨论,我已经能够澄清,我的同事想到的是原始包装,而不是原始本身。 所以我们留下的是如何安全地清理内存,最好是对象的原始问题。 另外,为了说明,敏感信息不仅仅是密码,还包括IP地址或encryption密钥等。
有没有商业JVM提供像某些对象的优先处理function? (我想这实际上违反了Java规范,但我想我会问,以防万一我错了。)
编辑:其实我只是有三个想法,可能确实工作 – 至less不同的价值“工作”。
第一个或多或less的文档将是ByteBuffer.allocateDirect! 据我所知,allocateDirect分配在通常的Java堆外的缓冲区,所以不会被复制。 但是对于当前的Hotspot虚拟机来说,我无法find任何硬拷贝,但是对于实际情况(即它被分配在一个额外的堆中),我认为这将保持这种状态。
第二个是使用sun.misc.unsafe软件包 – 正如其名称所说,有一些相当明显的问题,但至less这将是非常独立于使用的虚拟机 – 无论是支持(和它的工作),或不是(和你会得到链接错误)。 问题是,使用这些东西的代码将非常快速地变得非常复杂(单独获取一个不安全的variables是不重要的)。
第三个是分配一个比实际需要的大得多的更大尺寸,以便将对象分配到旧一代堆中开始:
l-XX:PretenureSizeThreshold =可以设置为限制年轻一代分配的大小。 任何比这更大的分配在年轻一代中是不会尝试的,所以将分配给老一代。
那么解决scheme的缺点是显而易见的,我认为(默认大小似乎是64kb)。
。 。
无论如何,这里的旧答案:
是的,因为我看到它几乎不能保证存储在堆上的数据是100%删除而不留下副本(如果你不想要一个通用的解决scheme,甚至可以说当前的Hotspot VM和它的默认垃圾收集器)。
正如你的链接文章( 这里 )所说,垃圾收集器几乎使这不可能保证。 实际上与post所说的问题不同的是,这里的问题不是代GC,而是Hotspot虚拟机(现在我们是特定于实现的)在默认情况下为其年轻一代使用某种Stop&Copy GC。
这意味着只要在将密码存储在char数组中并进行清零之间发生垃圾回收,就会在下一个GC发生时立即获得将被覆盖的数据副本。 请注意,对象的持久性将具有完全相同的效果,但不是将其复制到空间中,而是将其复制到旧一代堆中 – 最后从空间中复制未被覆盖的数据。
为了避免这个问题,我们非常需要一些方法来保证在存储密码和调零之间没有发生垃圾回收,或者把char数组存储在老一代堆中。 还要注意,这依赖于热点虚拟机的内部,这可能会发生很大的变化(实际上有不同的垃圾收集器可以生成更多的副本;热点虚拟机iirc支持使用火车algorithm的并发GC)。 “幸运的是”不可能保证其中任何一个(afaik每个方法的调用/返回引入一个安全点!),所以你甚至不会试图去尝试(特别是考虑到我没有看到任何方法来确保JIT不会优化掉零);)
看起来像保证数据只存储在一个位置的唯一方法是使用JNI。
PS:请注意,虽然以上只适用于堆,你不能保证任何更多的堆栈(JIT可能会优化写入而不读取堆栈,所以当你从函数返回数据仍然是在堆栈上)
告诉你的同事,这是一个无望的事业。 关于内核套接字缓冲区,只是为了一个开始。
如果您无法防止有害程序窥探您计算机上的内存,则密码会受到影响。 期。
奇怪,从来没有想过这样的事情。
我的第一个想法是做一个char [100]来存储你的密码。把它放在那里,用它来做任何事情,然后做一个循环来设置每个char为空。
问题是,密码在某些时候会变成数据库驱动程序内部的一个string,它可能在内存中存在0到无穷大的秒数。
我的第二个想法是通过对C的某种JNI调用来完成所有的身份validation,但是如果您尝试使用类似JDBC的东西,这将非常困难….
我一直在努力解决一些与凭据类似的问题。
到目前为止,我唯一的答案是“不要使用string来保密”。 string很容易使用和存储,但是计算机可以和字节数组一起工作。 即使是encryption原语也可以使用byte []。
当你不再需要密码的时候,只需要用零填充数组,不要让GC发明新的方法来重用你的秘密。
在另一个线程( 为什么不能在Java和.NET中可变的string? ),他们假设它是非常短视的。 由于安全原因,string是不可变的; 没有devise的是,操作问题并不总是存在的唯一存在,安全有时需要一些灵活性和/或支持才能有效,在本地Java中不存在支持。
补充。 我们如何读取密码而不使用string? 那么…要有创意,不要使用inputtypes密码的Android EditText之类的东西,只是不够安全,需要你去string。