dm-crypt/交换分区加密
本文介绍了多种加密交换分区的方法,可满足不同的需求。若在每次重启时都重新对交换分区进行加密(使用不同密钥)的方式,可避免换出到交换分区上的敏感文件片段因未被覆盖而长期存在,从而更好地保护数据。然而,若使用重新加密,则无法使用休眠功能。
若无需使用休眠功能,则可以通过配置/etc/crypttab,在每次启动时以随机密码,使用dm-crypt plain模式进行解密。在关机时,随机密码将被丢弃,设备上只含有加密的、无法访问的交换数据。
要使用该配置,只需要取消注释/etc/crypttab中以swap开头的一列,并修改“设备”(device)参数指向交换分区,例如:
/etc/crypttab
# <name> <device> <password> <options> swap /dev/sdX# /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096
上述配置将解密/dev/sdX#,并映射到/dev/mapper/swap。之后,像普通的swap分区一样,将/dev/mapper/swap添加到/etc/fstab中,即可完成交换分区配置。若之前已经配置了非加密的交换分区,需要将其禁用(或直接将其在fstab中的条目指向/dev/mapper/swap)。上面给出的选项应当足以应对大多数场景。若需要了解每个选项的详细解释,可参见crypttab(5)、point cryptsetup FAQ 2.3。
/dev/sda, /dev/sdb)指定设备,简单命名的顺序可能在每次启动时都不同,导致设备数据被意外清除。建议使用如下方式指定:
- 使用
by-id、by-path。然而,当出现硬件变化时,两者仍可能会改变,详见块设备持久化命名#通过_id_和_通过路径。 - 使用PARTLABEL、PARTUUID。
- 使用LVM逻辑卷名称。
- 使用#文件系统UUID和文件系统标签中介绍的方法。由于每次启动时,都将重新加密设备,并使用
mkswap重新格式化swap分区, 不能直接使用文件系统标签和文件系统UUID,参见cryptsetup FAQ。但可通过间接手段达成目的。
若要使用by-id持久化命名,首先,找到swap分区对应的设备:
# find -L /dev/disk -samefile /dev/sdaX
/dev/disk/by-id/ata-WDC_WD2500BEVT-22ZCT0_WD-WXE908VF0470-partX /dev/disk/by-id/wwn-0x60015ee0000b237f-partX
之后,修改配置文件,使用持久化引用(若上述命令返回多个结果,任选一个即可):
/etc/crypttab
# <name> <device> <password> <options> swap /dev/disk/by-id/ata-WDC_WD2500BEVT-22ZCT0_WD-WXE908VF0470-partX /dev/urandom swap,cipher=aes-xts-plain64,size=512,sector-size=4096
重启并激活加密交换后,使用swapon -s命令可以看到交换分区对应映射后的设备(例如,/dev/dm-1);使用lsblk命令,可看到交换分区的FSTYPE为crypt。由于每次启动时都重新加密并格式化交换分区,/dev/mapper/swap的文件系统UUID会在每次启动时变化。
在crypttab中使用内核简单命名(例如,/dev/sdX#)、ID命名(例如,/dev/disk/by-id/ata-SERIAL-partX)指定加密交换分区的目标设备有较大风险,设备名称、分区结构的细微变化都可能导致指向错误的设备,从而造成数据丢失。使用PARTLABEL、PARTUUID(与分区,而不是文件系统、设备绑定)的风险相对较小,但若打算将目标设备用作其它用途,而忘记移除crypttab中的条目,数据仍可能被覆盖(文件系统改变,分区不改变)。
更可靠的方法是使用文件系统标签或文件系统UUID。但在默认情况下,每次重启时,dm-crypt和mkswap都将覆盖目标分区上的内容,导致文件系统标签和文件系统UUID被移除。不过,可以指定swap分区的起始偏移量,以在设备开始处放置另一个空的、容量较小的文件系统。该文件系统仅用于提供持久性的文件系统UUID和文件系统标签。
使用指定文件系统标签创建文件系统:
# mkfs.ext2 -L cryptswap /dev/sdX# 1M
设备名后的1M参数限制文件系统的大小为1MiB,为之后加密的交换分区留出空间。
# blkid /dev/sdX#
/dev/sdX#: LABEL="cryptswap" UUID="b72c384e-bd3c-49aa-b7a7-a28ea81a2605" TYPE="ext2"
这样,无论设备名称、分区数量如何变化,始终可以使用文件系统UUID或文件系统标签指向/dev/sdX#。
接下来,配置/etc/crypttab和/etc/fstab条目即可:
/etc/crypttab
# <name> <device> <password> <options> swap LABEL=cryptswap /dev/urandom swap,offset=2048,cipher=aes-xts-plain64,size=512,sector-size=4096
注意偏移量的指定(offset=2048),偏移量的单位为512字节的扇区(不受dm-crypt扇区大小的影响),因此offset=2048相当于1MiB,跳过了提供UUID的文件系统的区域,因此不影响文件系统UUID和文件系统标签,并正确处理了数据对齐。
/etc/fstab
# <filesystem> <dir> <type> <options> <dump> <pass> /dev/mapper/swap none swap defaults 0 0
在本配置中,只会尝试使用特定文件系统标签的设备作为加密交换分区,而不受设备名称影响。若打算将加密交换分区用作其它用途,当格式化时,文件系统标签被覆盖,因此/etc/crypttab不会在下次启动时意外覆盖新的文件系统及数据。
桌面环境可能无法自动检测到swap分区已被随机密码加密,不能用作休眠,仍错误显示休眠按钮。
若使用Xfce,可通过如下命令隐藏“休眠”、“混合睡眠”按钮:
$ xfconf-query -c xfce4-session -np /shutdown/ShowHibernate -t bool -s false $ xfconf-query -c xfce4-session -np /shutdown/ShowHybridSleep -t bool -s false
下面介绍在需要进行休眠的情况下,设置加密swap的三种方法。若已经使用其中一种方法完成设置,需要注意系统换出的敏感数据可能在交换分区/交换文件中保留较长时间,直到被覆盖。若要降低相关风险,可以设置系统任务,在特定时机(例如,系统关机时)重新加密交换分区/重置交换文件。
交换文件可放置在加密设备的文件系统中。依照Swap#交换文件中的方法创建交换文件,并完成休眠设置即可。
若采用加密根文件系统的配置,并使用基于systemd的initrd,则配置加密交换文件非常简单、灵活。在完成配置加密的根文件系统后,只需要按常规方式创建并激活交换文件,并将其添加到/etc/fstab中即可,无需额外配置。
resume=内核参数时,注意其必须指向解密后产生的映射设备,该映射设备包含存储有交换文件的文件系统。swapoff /dev/device将其禁用,并在/etc/crypttab中移除相关条目。使用cryptsetup-luksFormat(8)创建容纳交换分区的加密容器:
# cryptsetup luksFormat --label swap /dev/device
打开该容器,并映射到/dev/mapper/swap:
# cryptsetup open /dev/disk/by-label/swap swap
在映射设备上创建swap文件系统:
# mkswap /dev/mapper/swap
若不使用Systemd#GPT分区自动挂载,需要将映射设备加入/etc/fstab中:
/dev/mapper/swap none swap defaults 0 0
要启用休眠,添加resume=/dev/mapper/swap内核参数。详见电源管理/挂起与休眠#传递休眠位置到_initramfs。
通过在TPM中存储密钥,可自动完成交换分区的加密与解密。
可以使用systemd-cryptenroll将密钥注册到LUKS容器和TPM中。之后,清除之前创建的密码槽位:
# systemd-cryptenroll --tpm2-device auto /dev/device # systemd-cryptenroll --wipe-slot password /dev/device
可通过如下命令验证结果:
# systemd-cryptenroll /dev/device SLOT TYPE 0 tpm2
若要从加密的交换分区中唤醒,必须在initramfs中解密该分区。
若使用基于systemd的inintramfs(使用sd-encrypt钩子),则可使用如下任意一个方案:
- 正确设置
rd.luks.uuid=内核参数以解密交换分区; - 编辑
crypttab.initramfs,并重新生成 initramfs。
例如,若使用TPM加密交换分区:
/etc/crypttab.initramfs
swap UUID=56f8ee97-54b3-4a65-9282-688deb922527 none tpm2-device=auto
若使用基于busybox的initramfs(使用encrypt钩子),需进行如下操作。
若交换分区所在设备与根文件系统的不同,则其不会被encrypt钩子打开。由于从休眠中唤醒的过程在读取/etc/crypttab前进行,不能在/etc/crypttab中进行配置以打开加密的交换分区,而需要修改/etc/mkinitcpio.conf并创建自定义钩子,以在唤醒开始之前打开加密的交换分区。
encrypt钩子,其只能解密单个设备(archlinux/mkinitcpio/mkinitcpio#231);若使用sd-encrypt钩子,则可以解密多个设备(包括加密的交换分区),详见Dm-crypt/系统配置#systemd-gpt-auto-generator。mkinitcpio-openswapAUR中包含了所需的钩子及配置文件。
-o ro,noload挂载选项。这些选项适用于ext4文件系统,若使用其它文件系统,需修改openswap.conf,使用作用相似的选项。将openswap钩子添加到/etc/mkinitcpio.conf的HOOKS数组中,注意应当添加到filesystem钩子之前,encrypt钩子之后。再在openswap钩子之后添加resume钩子。
HOOKS=(... encrypt openswap resume filesystems ...)
最后,重新生成 initramfs。
启动时,openswap钩子将打开加密的交换分区,以便内核从中唤醒。若使用自定义钩子进行唤醒操作,需确保在HOOKS数组中,相关钩子放置在openswap之后。注意,由于在initrd阶段便挂载了交换分区,/etc/crypttab中无需包含交换分区相关条目。
创建密钥文件:
# dd bs=512 count=4 if=/dev/random iflag=fullblock | install -m 0600 /dev/stdin /etc/cryptsetup-keys.d/swap.key
将密钥文件添加到LUKS卷:
# cryptsetup luksAddKey /dev/device /etc/cryptsetup-keys.d/swap.key
修改dracut配置,加入resume模块,并将swap.key文件加入到initramfs中(详见Dracut#休眠):
/etc/dracut.conf.d/resume-from-hibernate.conf
add_dracutmodules+=" resume " install_items+=" /etc/cryptsetup-keys.d/swap.key "
在内核参数中添加与交换分区对应的rd.luks.name、rd.luks.key参数。
修改后的内核命令行应当与下例相似:
kernel /vmlinuz-linux cryptdevice=/dev/sda2:root root=/dev/mapper/root resume=/dev/mapper/swap rd.luks.name=fd839505-3213-4603-9a70-c5a96a24768f=swap rd.luks.key=/etc/cryptsetup-keys.d/swap.key ro
若交换分区位于LVM卷组内,且对应卷组在initramfs中被激活,只需要按照电源管理/挂起与休眠#休眠中的步骤进行配置即可。
- 系统日志中出现形如
Stopped (with error) /dev/dm-1的错误。详见systemd issue 1620。