DoD 5220.22-M 是美国国防部旧版手册提出的3次覆写数据擦除建议,但已被 NIST SP 800-88 取代;它仅对传统 HDD 上未被覆盖的扇区有效,对 SSD、加密卷等无效,实际安全性有限。

什么是 DoD 5220.22-M 覆写,它真能防恢复吗?
DoD 5220.22-M(常被简称为 “DoD 5220”)并不是一个强制标准,而是美国国防部旧版手册中提出的一种数据擦除建议:用随机值或固定模式(如 0x00、0xFF、随机字节)覆写 3 次,中间穿插验证。现实中它已被 NIST SP 800-88 Rev.1 取代,且现代 SSD、SMR 硬盘、日志文件系统(如 ext4 的 journal、NTFS 的 USN journal)会让这种覆写完全失效——你根本无法控制物理块是否被真正覆盖。
所以,在普通磁盘上对文件做 DoD 5220 风格覆写有意义;在 SSD、加密卷、快照/备份活跃的系统上,它只是心理安慰。
- 覆写只对传统 HDD 上的
unlink()后未被新数据覆盖的扇区有效 - SSD 的 FTL 层会重映射逻辑地址,
write()到同一路径 ≠ 写入同一物理页 - Windows 的
MoveFileEx(..., MOVEFILE_DELAY_UNTIL_REBOOT)或 macOS 的setfile -a V隐藏+删除,不解决底层残留
C++ 中如何安全覆写并删除单个文件(HDD 场景)
核心是:先打开文件(open() 或 fopen()),用 lseek()/fseek() 定位到开头,循环覆写全尺寸,再 fsync() 强刷缓存,最后 unlink()。不能依赖 std::ofstream 默认行为——它可能缓冲、截断、甚至因异常提前退出。
关键实操点:
立即学习“C++免费学习笔记(深入)”;
- 必须用
O_WRONLY | O_SYNC(Linux/macOS)或_O_WRONLY | _O_NOINHERIT+_commit()(Windows MSVC)绕过内核/运行时缓冲 - 覆写次数按需设为 1(随机)、3(DoD 风格)、7(Gutmann,已过时但有人坚持)——更多次不提升安全性,只拖慢速度
- 每次覆写前调用
lseek(fd, 0, SEEK_SET),避免因文件指针偏移导致尾部未覆写 - 务必检查
write()返回值,部分设备(如只读挂载)会返回-1或短写
示例片段(Linux):
int fd = open("sensitive.txt", O_WRONLY | O_SYNC);
if (fd == -1) return;
off_t size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
std::vector<uint8_t> buf(4096, 0xFF); // 全 0xFF 第一遍
for (off_t written = 0; written < size; ) {
ssize_t r = write(fd, buf.data(), std::min(buf.size(), (size_t)(size - written)));
if (r <= 0) break;
written += r;
}
fsync(fd);
close(fd);
unlink("sensitive.txt");批量处理时最常踩的三个坑
批量不是简单 for 循环套上面逻辑——并发、路径、权限会立刻暴露问题。
-
std::filesystem::directory_iterator在遍历时若目标被删,某些实现(如 libstdc++ 11.2)会抛std::filesystem::filesystem_error,需用try/catch包裹单次迭代,而非整个循环 - 相对路径和符号链接:直接
unlink("link")只删链接本身,要删目标得用std::filesystem::weakly_canonical()解析真实路径 - 权限不足时
open()失败很常见,但错误码EACCES和EPERM含义不同:EACCES是权限不够,EPERM是文件被锁定(如 Windows 正被记事本打开),后者应跳过而非报错退出
为什么别自己实现“安全删除工具”,而该用现成方案
因为边界情况太多:稀疏文件、只读属性(chmod 444)、ACL、WORM 存储、FUSE 文件系统……你写的 overwrite_and_unlink() 函数在 ext4 上跑通,到 XFS 或 NTFS 上可能静默失败。
更务实的做法:
- Linux:用
shred -u -n 3 file(注意:对 ext4 日志文件系统,加--remove=wipe才清 journal 缓存) - macOS:
srm -s file(-s表示 single-pass,比默认 7 次快且足够) - 跨平台 C++ 项目:调用
std::system("shred -u -n 1 " + quoted_path),比手写覆写逻辑更可靠
真正难的从来不是“怎么覆写三次”,而是“怎么确保这三次真的落到了磁盘上、且没被文件系统/固件/缓存吃掉”。一旦涉及 SSD、全盘加密、云存储,覆写本身就成了伪命题——这时候,cryptsetup luksKillSlot 或密钥轮转,比覆写文件有效得多。



















