在处理加密数据时,攻击者是否有可能恢复我在程序中使用的部分数据?我是否应该尝试删除使用的所有密钥和加密数据的每个位,并用零覆盖?

还有一个额外的问题,在具有垃圾回收的语言(如Python或Java)中,情况有何不同?

评论

您到底想防止什么?假定访问了机器内存的“攻击者”将因此有权获得机器已处理的数据。而且,如果您的模型是攻击者在处理数据时可以控制该计算机,则以后将其删除将无济于事。您是否要防止交换或核心转储中的敏感数据?

#1 楼

密码学工程这本书的一章专门介绍了该主题。用零覆盖敏感数据是一个不错的开始,但是还有许多其他注意事项。


如果您依靠某种语言的默认对象破坏行为来将内存清零,则可能由于意外错误而过早地暂停了程序的执行,而没有对其进行自我清理。在Java中,异常处理可以丢弃对象引用而无需清理对象本身。 [不确定是否仍然如此]
有时编译器会优化掉用于覆盖内存的代码,因为它作用于一块内存,而程序中没有其他用途。有几种方法可以诱使编译器不执行此“消除死存储”。
OS内存管理可以通过将其分页到系统硬盘来阻止删除数据的尝试。有多种方法可以防止这种情况,但是它们是特定于语言和平台的。
如果OS进行上下文切换,则寄存器中的值将存储在内存中的其他位置,直到恢复执行为止。这可能导致敏感数据最终出现在陌生的地方。现代处理器还具有多个可以保存敏感数据的缓存。
在自动内存管理的语言中,要知道分配的内存在系统中的确切位置(以及持续多长时间)更加困难。在Java中,所有对象都生活在堆中,并作为垃圾回收的一部分进行引用计数。如果不再有对该对象的引用,则可以在下一个垃圾收集器传递时将其删除。但是,缺少调用System.gc()的方法,无法知道何时进行垃圾回收。即使强制执行垃圾收集,它也可能不会立即执行。这个SO问题稍微讨论了System.gc行为。
当密钥始终写入计算机内存的同一物理区域时,就会发生冷启动攻击。随着时间的流逝,电磁力会导致电源关闭后存储器保持状态。

这可能不属于您应用程序的全部范围,但是它应该使您对具有物理系统访问权限的攻击者的功能有个很好的了解。您可以做的最重要的事情是非常非常了解您的语言和平台。为了进一步阅读,这篇Wikipedia文章应该为您提供一个良好的开端。

评论


$ \ begingroup $
2号尤为重要。例如,您可以查看OpenSSL的工作方式,方法是对数据进行奇怪的操作以覆盖,以诱使编译器将其随机化,即使以后再也不会使用(对于高级语言而言,这很可能无法实现)。确实没有通用解决方案。
$ \ endgroup $
–托马斯
13年8月27日在3:36

$ \ begingroup $
很好的答案,尽管从技术上讲,它回答的问题是“如何擦除在编程中使用的敏感数据”,而不是问题的“应该擦除在程序中使用的敏感数据”。
$ \ endgroup $
–亨里克·赫尔斯特伦
13年8月27日在13:59

$ \ begingroup $
哈哈,我想是的。我以为我会用一块石头杀死两只鸟。
$ \ endgroup $
–pg1989
13年8月27日在17:15

#2 楼

是的,这是个好主意。但是不幸的是,从内存中安全擦除数据并不是一件容易的事。现代的编译器,操作系统和CPU使其变得非常困难。例如,您永远不知道计算机在哪里存储了敏感数据。 CPU具有L1,L2和共享的L3缓存。 NUMA(甚至是ccNUMA)可以对您的数据进行处理,直到您强制执行内存屏障。操作系统也可能决定将内存页面交换到磁盘。 mlock()可能会有所帮助,但是它需要额外的功能并且很难使用,因为它可以在页面上运行。

对memset()的简单调用在大多数情况下也是错误的。每个最先进的编译器都将静默删除基于memset()的清理代码。 C11引入了memset_s(),但在大多数CRT中尚不可用。请参阅
https://www.securecoding.cert.org/confluence/display/seccode/MSC06-C。+要+意识到+ of +编译器+优化+何时+处理+使用+敏感+数据

您可以尝试以下代码,如

static void * (* const volatile __memset_vp)(void *, int, size_t) = (memset);
(*__memset_vp)(s, c, n);




memset(s, c, n);
asm volatile("" : : "r"(s) : "memory"); /* may need GCC */




SecureZeroMemory(s, n); /* Windows only */

也是。

对于CPython,您必须在C中实现自己的数据类型,并在其释放函数中擦除内存。 Python的核心类型不会很快获得安全擦除的标志(最像从来没有)。

评论


$ \ begingroup $
您的__memset_vp代码错误。编译器始终可以比较__memset_vp函数指针,如果它等于memset,则省略代码。
$ \ endgroup $
– Molossus Spondee
2014年12月21日在20:07

$ \ begingroup $
使用内联asm向GCC解释可以在以后读取数据的memset代码更好,但没有考虑多线程。例如,在内存实际归零之前,您的归零写入可能会在内核的存储缓冲区中停留一段时间。您还应该刷新操作系统和硬件缓存。
$ \ endgroup $
– Molossus Spondee
2014年12月21日在20:09

$ \ begingroup $
@ StevenStewart-Gallus是的,比较__memset_vp函数指针是合法的优化,但不会使代码“错误”。请记住,没有便携式方法可以做到这一点。如果您没有asm等,那么volatile fp方法实际上是可行的。到目前为止,没有编译器在进行此优化。
$ \ endgroup $
–实数或随机数
20年4月28日在8:37

#3 楼

这个问题实际上并不完全容易回答。通常,应该避免给密码方案或实现增加复杂性,除非为了满足特定要求而必须增加复杂性。将内存归零的软件的问题在于,很难想出一种可靠的方案,这种方案既有效又必要,以应对特定威胁。

假设您在磁盘上受密码保护的.pfx文件中具有客户端证书。您相信用于.pfx文件的密码短语足够强,至少在证书过期之前不会破坏加密。您可以在已知的程序中使用证书,该程序实现了将内部内存清零的最新方法。后来,您发现您在同一台计算机上存在恶意软件,或者该计算机实际上被盗。在任何一种情况下,您是否都信任将内部内存归零的方法,还是会撤销证书?可以说,理智的事情是撤销证书,而不管软件是否将内部内存清零。

#4 楼


在处理加密数据时,攻击者是否有可能恢复我在程序中使用的部分数据?


是的。请参阅其他答案。


我是否应该尝试删除使用的密钥和加密数据的每一位,并用零覆盖?


这取决于目标:


至少在某些安全检查/评估中获得更高的分数非常有用。因此,如果那是目标¹,那么肯定是肯定的。
很容易构造人为的案例来阻止攻击²,因此实践有一定的道理,并表示审查/评估的目的。因此,如果没有迹象表明需要解决的更严重的问题,那就是肯定的。
可靠且可移植的代码执行麻烦很多,使代码的可读性降低,速度变慢和变大,有助于自动识别代码操作敏感数据,所有这些保护措施都可以通过稍有不同的攻击来规避。因此,不,在进行其他对安全性很重要的重要工作(防止缓冲区溢出,定时攻击,其他旁路)之前进行很多认真的工作(通常不值得),
/>总结:归零?也许可以,但是不要指望它可以确保不安全的系统安全。从技术上讲,必须进行归零的唯一情况是处理可疑攻击的代码,这会破坏所有敏感资料,例如长期密钥。代码行是一个目标。

²通常,攻击依赖于编码错误,包括一些在已部署系统中确实发生的错误。

³需要许多现代语言(尤其是解释型)所没有的普通控制级别。对于已编译的语言,需要编译器知道的特殊语义,或者需要使优化器不明智(存在被下一发行版或其他编译器过度明智的风险),或者禁用优化(可能会花费大量性能,并且会被无意中还原,尤其是在端口期间)。