使用 C 或 C++,在我将文件解密到磁盘后 - 如果应用程序崩溃或系统关闭并且无法正确清理它,我如何保证它被删除?在 Windows 和 Linux 上使用 C 或 C++?
13 回答
不幸的是,没有 100% 万无一失的方法来确保在整个系统崩溃的情况下文件将被删除。想想如果用户只是在文件在磁盘上时拔掉插头会发生什么。再多的异常处理都无法保护您免受这种(最坏的)情况的影响。
您可以做的最好的事情是首先不要将解密的文件写入磁盘。如果文件以加密和解密的形式存在,那么这就是您的安全性的一个弱点。
您可以做的下一个最好的事情是使用 Brian 的结构化异常处理建议来确保清理临时文件。这不会保护你免受所有可能性的影响,但它会有很长的路要走。
最后,我建议您在应用程序启动时检查临时解密文件。这将允许您在应用程序完全崩溃的情况下进行清理。将这些文件保留一段时间并不理想,但至少这可以让您尽快摆脱它们。
根本不要将解密的文件写入磁盘。
如果系统断电,文件仍在磁盘上,则可以访问磁盘和文件。
例外情况是使用加密的文件系统,但这是您的程序无法控制的。
尽量避免它:
如果文件是敏感文件,最好的办法是首先不要将其以解密格式写入磁盘。
防止崩溃:结构化异常处理:
但是,您可以添加结构化异常处理来捕获任何崩溃。
如果他们拔掉插头怎么办?:
有一种方法可以防止这种情况...
如果您在 Windows 上,您可以使用 MoveFileEx 和选项 MOVEFILE_DELAY_UNTIL_REBOOT 与 NULL 的目的地在下次启动时删除文件。这将防止计算机因未删除的文件而意外关机。您还可以确保您拥有该文件的独占打开句柄(指定不共享权限,例如 FILE_SHARE_READ 并使用 CreateFile 打开它)。这样一来,任何人都无法从中读取。
避免该问题的其他方法: 所有这些都不是在磁盘上拥有解密文件的借口,而是:
您还可以考虑通过 \\?\ 的文件语法写入大于 MAX_PATH 的文件。这将确保文件不能被 Windows 资源管理器浏览。
您应该将文件设置为具有临时属性
您应该将文件设置为具有隐藏属性
我不知道这是否适用于Windows,但在Linux上,假设您只需要一个进程来访问解密文件,您可以打开该文件,然后调用unlink()删除该文件。只要进程保持打开,文件就会继续存在,但当它关闭或进程终止时,文件将不再可访问。
当然文件的内容仍然在磁盘上,所以你需要的不仅仅是删除它,而是清零内容。解密文件是否需要在磁盘上(大小?)。最好将解密的版本保存在内存中,最好标记为不可交换,这样它就不会碰到磁盘。
在 C 中(因此,我假设在 C++ 中也是如此),只要您的程序没有崩溃,您就可以注册一个atexit()
处理程序来进行清理。只是避免使用_exit()
or_Exit()
因为那些绕过atexit()
处理程序。
但是,正如其他人指出的那样,最好避免将解密的数据写入磁盘。仅仅使用unlink()
(或等效)是不够的;您需要在原始数据上重写一些其他数据。日志文件系统使这变得非常困难。
一个进程不能保护或监视自己。您唯一的可能是启动第二个进程作为一种看门狗,它会定期检查解密其他进程的健康状况。如果其他进程崩溃,看门狗会注意到并删除文件本身。
您可以使用 hearth-beats(定期轮询其他进程以查看它是否还活着)或使用从其他进程本身发送的中断来做到这一点,如果它已经崩溃,这将触发超时。
例如,您可以使用套接字来使看门狗和您的应用程序之间的连接正常工作。
很明显,您需要一些锁定机制来防止交换到页面文件/交换分区。在 Posix 系统上,这可以通过m(un)lock* family of functions
.
查看 tmpfile()。
它是 BSD UNIX 的一部分,不确定它是否是标准的。
但它会创建一个临时文件并自动取消链接,以便在关闭时将其删除。
写入文件系统(即使是暂时的)是不安全的。
只有当你真的必须这样做时才这样做。
或者,您可以创建一个内存文件系统。
我自己从来没有使用过,所以没有推荐,但快速谷歌发现了一些。
删除文件有问题。它并没有真正消失。当您从硬盘驱动器中删除文件(不包括回收站)时,该文件并没有真正消失。只是删除了指向文件的指针。
曾经看过那些他们覆盖硬盘驱动器 6、8,24 次的间谍电影,这就是他们如何知道它是干净的.. 他们这样做是有原因的。我会尽一切努力不存储文件的解密数据。或者,如果必须,使其成为少量数据。甚至,脱节的数据。
如果你必须,那么他们尝试抓住应该保护你一点。虽然没有什么可以保护你免受停电的影响。
祝你好运。
在 C++ 中,您应该使用RAII策略:
class Clean_Up_File {
std::string filename_;
public Clean_Up_File(std::string filename) { ... } //open/create file
public ~Clean_Up_File() { ... } //delete file
}
int main()
{
Clean_Up_File file_will_be_deleted_on_program_exit("my_file.txt");
}
RAII 有助于自动化大量清理工作。您只需在堆栈上创建一个对象,并让该对象在其生命周期结束时进行清理(在对象超出范围时将调用的析构函数中)。 ScopeGuard甚至使它更容易一些。
但是,正如其他人所提到的,这只适用于“正常”情况。如果用户拔下计算机,您不能保证该文件将被删除。并且可以取消删除文件(即使在 UNIX 上也可以“ grep the harddrive ”)。
此外,正如评论中所指出的,在某些情况下对象不会超出范围(例如,std::exit(int)
函数退出程序而不离开当前范围),因此 RAII 在这些情况下不起作用。就个人而言,我从不调用std::exit(int)
,而是抛出异常(这将展开堆栈并调用析构函数;我认为这是“异常退出”)或从中返回错误代码main()
(这将调用析构函数,我也认为这是“异常出口”)。IIRC,发送 aSIGKILL
也不会调用析构函数,SIGKILL
也不会被捕获,所以你也不走运。
这是一个棘手的话题。通常,如果可以避免,您不想将解密文件写入磁盘。但是将它们保存在内存中并不总是保证它们不会作为页面文件的一部分或其他方式写入磁盘。
我很久以前读过有关这方面的文章,我记得 Windows 和 Linux 之间存在一些区别,即一个可以保证内存页面不会写入磁盘而一个不能。但我记不太清楚了。
如果您想进行尽职调查,可以查找该主题并阅读相关内容。这完全取决于您的威胁模型以及您愿意防范的内容。毕竟,您可以使用压缩空气来冷却 RAM 并从中取出加密密钥(这实际上是在新的 Christian Slater 间谍节目 My Own Worst Enemy 中 - 我认为这是对尖端、准确、计算机的最佳使用媒体中的安全技术)
在 Linux/Unix 上,创建文件后立即使用 unlink。一旦您的程序关闭文件描述符或退出,该文件将被删除。
更好的是,即使整个系统崩溃,该文件也会被删除 - 因为一旦您取消链接,它就会被删除。
当然,这些数据不会从磁盘上被物理删除,因此它仍然可以被黑客攻击。
请记住,计算机可能随时关闭。然后,您不喜欢的人可以使用 Linux live CD 启动,并以所需的任何详细程度检查您的磁盘,而无需更改任何内容。将明文写入磁盘的任何系统都无法抵御此类攻击,而且它们并不难做到。
您可以设置一个函数,用 1 和 0 重复覆盖文件,最好注入一些随机性,并将其设置为在程序结束或退出时运行。如果没有硬件或软件故障、电源故障或其他中断,并且文件系统只写入它声称正在使用的扇区(例如,日志文件系统可能会将部分文件留在其他地方),这将起作用)。
因此,如果你想要安全,你需要确保没有明文被写出,这也意味着它不能被写入交换空间或等价物。了解如何在您编写的所有平台上将内存标记为不可交换。确保解密密钥等以与明文相同的方式处理:在任何情况下都不要写入磁盘,并保存在不可交换的内存中。
然后,您的系统应该是安全的,不会受到敌方入侵、中断和在断电前冻结您的 RAM 芯片的攻击,因此它们在被转移进行检查之前不会丢失其内容。或者当局合法地(在此处查看当地法律)或非法地要求您提供密钥。
故事的寓意:真正的安全是很难的。
我将要实现的方法是流式解密——以便内存中唯一的部分是在读取期间在使用数据时解密的部分。下面是管道示意图:
这将是一个流式实现,因此内存中的唯一数据是我在任何给定点在应用程序中使用的数据。这使得一些事情变得棘手 - 考虑到许多传统的文件技巧不再可用,但由于实现将基于流,我仍然能够寻找文件的不同点,这些点将被转换为加密流以解密不同的部分。
基本上,它将一次加密文件的块-因此,如果我尝试寻找到某个点,它将解密该块以进行读取。当我读过一个块时,它会解密下一个块并释放前一个块(在加密流中)。
此实现不需要我解密到文件或内存,并且与其他流消费者和提供者 (fstream) 兼容。
这是我的“计划”。我以前没有用 fstream 做过这种类型的工作,一旦我准备好处理这个问题,我很可能会发布一个问题。
感谢所有其他答案 - 它非常有用。