安全地擦除磁盘
擦除磁盘是通过向磁盘写入新数据,直至将旧数据完全覆盖完成的。
完全擦除磁盘最常见的应用是,在需要丢弃或出售设备时,清除磁盘上可能仍存在的未加密敏感数据。擦除数据后,可抵御使用简单文件恢复软件恢复敏感数据的尝试。
若追求擦除速度,可以向设备写入/dev/zero产生的全零数据,或其它通过简单模式生成数据。不过,在某些情况下,使用具有一定随机性的数据可能更好,详见#数据残留。
对于设备已经被擦除的部分,不可能使用常规的系统功能(例如,标准ATA/SCSI命令)和硬件接口获取到擦除前的数据。若文件恢复软件需要尝试从被擦除的部分恢复数据,则其必须使用依设备而异的专有功能。
对于机械硬盘,要从被擦除的部分恢复数据,至少需要使用非公开的设备指令,或修改设备控制器/固件,以从设备通常不可访问的部分读取信息。例如,可能可以从被重映射的扇区(记录在S.M.A.R.T.中的设备损坏的扇区)读取到擦除前的数据。
在擦除数据方面,不同的物理存储技术存在不同问题。其中最值得注意的是基于闪存的设备和老式磁存储设备(机械硬盘硬盘、软盘、磁带)。
若要将磁盘某部分用于存储块设备加密数据,建议使用密码学随机数生成器(下文中简称为RNG)生成的#随机数据进行擦除。
另见Wikipedia:Random number generation。
即使有意移除/擦除了磁盘上的数据,数据的表示可能仍然存在于设备中,详见Wikipedia:Data remanence。
操作系统、运行中的程序或日志式文件系统,可能会在你不知情的情况下隐式将未加密数据分散拷贝到磁盘的任意角落。不过,只有当需要执行像前文提到的那种高级操作(例如为出售设备而彻底销毁数据,或为全盘加密做准备)并直接操作整个磁盘时,这个潜在的风险才显得至关重要。
若能准确地确定数据在磁盘上的位置,且能确定其并没有被显式或隐式地复制其它地方,可以通过向对应位置写入伪随机数据来快速、彻底地擦除数据。
例如,在擦除LUKS密钥槽位时,cryptsetup就将向槽位所在位置写入/dev/urandom产生的伪随机数据。
由于闪存(包括固态硬盘)的写放大等特性,可靠的擦除较为困难。
由于硬件控制器和操作系统之间的透明抽象层,在操作系统层面对数据的操作和控制器实际的操作可能完全不同。操作系统层面对数据的覆写不会实际覆盖闪存中的数据,因而难以可靠地擦除文件或设备上的特定数据块。
此外,设备可能还提供其它功能,例如所有SandForce固态硬盘都启用了透明压缩。若使用全零或简单重复性模式擦除,则设备可能将写入压缩。若发现擦除的速度远超预期,可能是透明压缩的作用。
使用较简单的设备即可拆解闪存设备,直接不经设备控制器地读取闪存上的数据,从而进行数据恢复。许多数据恢复公司都以较低价格提供对闪存的数据恢复服务。
更多信息详见:
若硬盘将某一扇区标记为坏扇区,则其将隔离该扇区,使得不可能再通过软件写入该扇区。因此,即使在软件层面进行了全盘写入,该扇区的数据也不会被覆盖。好在,由于扇区大小所限,坏扇区中理论可恢复的数据一般只有几KiB。
对于现代高密度磁存储设备,使用全零或随机数据进行一次全盘写入后,设备上就不再有任何有恢复意义的数据。因而,在如今,无需再进行多次擦除[1]。 虽然在磁盘上可能留下单个比特的磁性残留痕迹,但将这些残留痕迹组合成有意义的字节基本上是不可行的[2]。 另见[3]、[4]、[5]。
使用fdisk查找用户具有读取权限的所有设备。
在输出中留意以/dev/sdX开头的行。
例如,对于一个安装了Linux系统的机械硬盘,输出如下:
# fdisk -l
Disk /dev/sda: 250.1 GB, 250059350016 bytes, 488397168 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00ff784a Device Boot Start End Blocks Id System /dev/sda1 * 2048 206847 102400 83 Linux /dev/sda2 206848 488397167 244095160 83 Linux
对于写入了Arch Linux引导镜像的4GB U盘,输出如下:
# fdisk -l
Disk /dev/sdb: 4075 MB, 4075290624 bytes, 7959552 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x526e236e Device Boot Start End Blocks Id System /dev/sdb1 * 0 802815 401408 17 Hidden HPFS/NTFS
若担心不小心损坏了计算机上的其它重要数据,可以考虑使用隔离的环境进行操作。例如,可以将要擦除的硬盘连接到虚拟环境(VirtualBox,VMWare,QEMU等),在虚拟环境中进行操作;或使用只连接了需要擦除的硬盘的单独的计算机,并使用Live 介质(USB,CD,PXE等)启动;也可使用脚本避免不小心擦除已挂载的设备。
在擦除敏感数据时,擦除数据源的数据模式应当符合要求。
在大多数情况下,使用/dev/zero或其它简单数据模式擦除设备都是安全的。特别是对于现代机械硬盘,这是一种快速且恰当的擦除方式。
然而,若使用全零或简单数据模式擦除的速度异常地快,说明设备可能使用了透明压缩功能,此时很多磁盘块可能并未被擦除。部分#闪存设备存在此问题。
若要将擦除的部分用于存储加密数据,则不应当使用全零或其它简单数据模式擦除,而应当使用随机数据擦除(见下节),以避免削弱加密强度。
/dev/urandom的随机数据由Linux内核生成,可作为快速且密码学安全的伪随机数来源。若要详细了解随机数和伪随机数来源,参见随机数生成。
曾经,内核的随机数生成器速度较慢,因而常常使用加密数据流作为伪随机数据来源。加密数据流通常通过使用随机密钥加密/dev/zero获得。虽然加密数据流从理论上来讲仍是安全的,但随着内核随机数生成器的改进,其相比直接使用内核随机数生成器已经没有任何优势,且存在临时随机密钥被意外保存的风险。
另见Wikipedia:Dd (Unix)#Block size,blocksize io-limits。
若使用的机械硬盘采用先进磁盘格式,推荐在擦除时手动设置块大小,而不是默认的512字节设置。采用符合硬盘物理扇区大小的设置,可提高擦除速度。可通过dd命令的bs选项设置块大小(例如,bs=4096将设定块大小为4KiB)。
可使用fdisk获取设备的逻辑/物理块大小。或者,也可以通过sysfs暴露的信息获取:
/sys/block/sdX/size /sys/block/sdX/queue/physical_block_size /sys/block/sdX/queue/logical_block_size /sys/block/sdX/sdXY/alignment_offset /sys/block/sdX/sdXY/start /sys/block/sdX/sdXY/size
块设备被划分为扇区,可通过扇区数量乘扇区大小得到设备的大小。
例如,在使用dd命令擦除硬盘上的某一分区时,可按如下方式传递扇区数量、扇区大小等参数:
# dd if=<擦除数据源> of=/dev/sdX bs=<扇区大小> count=<扇区数量> seek=<分区起始扇区> status=progress
下面将使用具体实例进行阐述。对于/dev/sdX设备,fdisk的输出如下:
# fdisk -l /dev/sdX
Disk /dev/sdX: 1.8 TiB, 2000398934016 bytes, 3907029168 sectors Disk model: ST3500413AS Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes ... Device Boot Start End Sectors Size Id Type /dev/sdX1 2048 3839711231 3839709184 1,8T 83 Linux /dev/sdX2 3839711232 3907029167 67317936 32,1G 5 Extended
- 输出的第一行显示了设备大小(按字节计)、逻辑扇区数量。
- 此外,也可通过
blockdev --getsize64 /dev/sdXY获取整个设备或设备上某一分区的大小。 - 输出中“Units”开头的行显示了设备的逻辑扇区大小。也可通过设备大小(按字节计)除以逻辑扇区数量得到逻辑扇区大小(
echo $((2000398934016 / 3907029168)))。 - 输出中“Sector size”开头的行显示了逻辑/物理扇区大小。使用物理扇区大小进行写入,可提高速度。
- 可通过设备大小(按字节计)除以物理扇区大小得到物理扇区数量(
echo $((2000398934016 / 4096)))。
- 在本例中,将使用逻辑扇区大小进行擦除。
- 通过计算下一分区的起始扇区数与前一分区的结束扇区数的差值,可以使用
dd命令擦除未分区的磁盘空间。
例如,要擦除/dev/sdX1,并使用逻辑扇区大小作为擦除的块大小:
- 使用要擦除的分区的起始扇区数、结束扇区数定位:
# dd if=<擦除数据源> of=/dev/sdX bs=${BytesInSector} count=${End - Start} seek=${Start} status=progress
根据fdisk的输出,代入Start=2048,End=3839711231,BytesInSector=512即可。
- 或者,使用逻辑扇区数:
# dd if=<擦除数据源> of=/dev/sdX1 bs=${BytesInSector} count=${LogicalSectors} status=progress
根据fdisk的输出,代入BytesInSector=512,LogicalSectors=3839709184即可。
也可以擦除整个设备,并使用物理扇区大小作为擦除的块大小:
# dd if=<擦除数据源> of=/dev/sdX bs=${PhysicalSectorSizeBytes} count=${AllDiskPhysicalSectors} seek=0 status=progress
根据fdisk的输出,代入AllDiskPhysicalSectors=488378646,PhysicalSectorSizeBytes=4096即可。
sdXY指定了分区的范围时,无需手动指定count=选项。dd将写满整个区域,不过会在写入结束时显示剩余空间不足的错误消息。可以使用多种工具对目标设备进行覆写。若只需要擦除一个文件,参见安全地擦除磁盘/提示和技巧#擦除单个文件。
通过输出重定向,可以创建文件、覆写分区中的空闲空间、擦除单一分区或整个设备。下文的例子中使用/dev/zero以全零填充设备,若需要随机擦除,应当改用/dev/urandom。
如下展示了如何将其它工具的标准输出重定向到块设备中,达到覆写目的:
# cat /dev/zero > /dev/sdXY
cat: write error: No space left on device
# xz -z0 /dev/zero -c > /dev/sdXY
xz: (stdout): Write error: No space left on device
# dd if=/dev/zero status=progress > /dev/sdXY
dd: writing to ‘standard output’: No space left on device 20481+0 records in 20480+0 records out 10485760 bytes (10 MB, 10 MiB) copied, 2.29914 s, 4.6 MB/s
dd在执行操作前不会二次确认。一定要仔细确认输出指向了正确的设备或分区。确保of=...选项指向的是目标设备,而不是系统磁盘。使用/dev/zero全零流向磁盘每个可寻址位置写入零:
# dd if=/dev/zero of=/dev/sdX bs=4096 status=progress
或者,使用/dev/urandom随机流:
# dd if=/dev/urandom of=/dev/sdX bs=4096 iflag=fullblock status=progress
当dd提示No space left on device并结束时,表示擦除完成:
dd: writing to ‘/dev/sdX’: No space left on device 7959553+0 records in 7959552+0 records out 4075290624 bytes (4.1 GB, 3.8 GiB) copied, 1247.7 s, 3.3 MB/s
对于容量较大的目标设备,可参见如下提速技巧:
- 安全地擦除磁盘/提示和技巧#使用dd进行高级擦除 使用OpenSSL
- 安全地擦除磁盘/提示和技巧#使用模板文件 使用非随机数据覆写(例如,重复写入一个文件的内容),随机性不足,但速度快
- Dm-crypt/准备磁盘#dm-crypt_专用方案 使用dm-crypt
文件复制命令cp(1)不会检查目标位置的类型,因而也可以用于覆写设备:
# cp /dev/zero /dev/sdXY
cp: error writing ‘/dev/sdXY’: No space left on device cp: failed to extend ‘/dev/sdXY’: No space left on device
pv包工具可在覆写时显示进度条、已用时间、预计剩余时间。将选定的擦除数据源作为参数传递给pv(1),并使用-o/--output选项指定要覆写的目标。例如,使用/dev/zero全零填充/dev/sdX:
# pv /dev/zero -o /dev/sdX
wipe是wipe包的一部分,专门用于擦除文件。例如,要擦除特定目标:
$ wipe -r /path/to/wipe
参见wipe(1)。注意:该工具上次更新于2009年,其SourceForge 页面显示当前已处于不维护状态。
shred是coreutils包的一部分。其专门用于安全地删除单个文件或整个设备。被删除的文件或设备即使理论上能够被恢复,其难度也非常高,并且需要使用专用设备。默认情况下,shred将使用伪随机数据执行3次覆写操作。可手动指定覆写操作执行的次数。
使用默认参数调用shred,这将在擦除时显示进度条:
# shred -v /dev/sdX
shred也可用于擦除单个分区。例如,要擦除设备上的第一个分区,使用shred -v /dev/sdX1。
此外,也可设定只执行1次覆写操作,选择/dev/urandom作为熵源,并在结束前再使用全零覆盖一次:
# shred --verbose --random-source=/dev/urandom -n1 --zero /dev/sdX
scrub将按指定的顺序,用不同的模式多次覆盖文件或设备,使得数据恢复更加困难。
如下命令使用默认参数调用scrub,覆写整个设备(模式1)。覆写使用的模式组合遵循NNSA Policy Letter NAP-14.x规范。这是效率最高的方式。
$ scrub /dev/sdX
如下命令使用默认参数调用scrub,覆写特定文件(模式2)。覆写使用的模式组合遵循NNSA Policy Letter NAP-14.x规范。实际覆写的字节数将向上取整到能够完全覆盖文件占用的最后一个文件系统块。注意,使用该模式时有一些需要注意的地方,详见手册。
$ scrub /path/to/file # file是常规文件
如下命令使用默认参数调用scrub,此时将创建一个目录,在目录中创建文件直到占满文件系统,之后再覆写创建的文件(模式3)。覆写使用的模式组合遵循NNSA Policy Letter NAP-14.x规范。对于每一个文件,实际覆写的字节数将向上取整到能够完全覆盖文件占用的最后一个文件系统块。注意,使用该模式时有一些需要注意的地方,详见手册。
$ scrub /path/to/dir # dir是目录
更多信息和用法,参见手册。
badblocks是e2fsprogs包的一部分,其通过反复向设备写入、读取数据检测坏扇区。由于其写入操作对存储的数据而言是破坏性的,因而也能用于擦除设备。在默认情况下,其将执行4次读取、写入循环,因而可能需要花费较长时间。
# badblocks -wsv /dev/device
hdparm支持ATA Secure Erase功能,该功能由设备固件处理,作用大致相当于使用全零填充整个设备,并包括软件无法访问的区域。其相当于现代版本的“低级格式化”命令。对于固态硬盘,有报道称执行该命令后,性能可恢复到出厂水平,但可能不足以完全擦除数据(参见#闪存)。
某些设备支持Enhanced Secure Erase功能,其具体实现方式依厂商而异。若hdparm -I的输出显示,使用Enhanced Secure Erase相比普通Secure Erase的用时显著更短,则说明设备可能支持硬件加密功能,而Enhanced Secure Erase将通过擦除加密密钥保护数据安全。
有关ATA Secure Erase的更多信息,参见固态硬盘/存储单元擦除、Linux ATA wiki。