Linux「磁盘加密」完全指南

 发表于 2019-11-18   分类于 Linux
 本文字数: 7.8k

Linux「磁盘加密」完全指南

本文只涉及 Linux 下的加密操作,如果你是在 Windows 平台下的话,直接用系统自带的「BitLocker」就可以了,图形界面操作也相对简单。

本文中使用的 Linux 发行版是 Arch Linux,在不同发行版下的相关操作可能有所不同,以你使用的发行版官方Wiki为准。

磁盘加密的意义

磁盘加密是为了保护计算机系统的物理安全(当我们不在家时如何保证电脑内数据的安全、防止系统被偷偷植入木马后门、……)。如果磁盘上存储了重要的商业机密或者是像我们这种重视隐私的人,为了防止信息泄漏以及来自线下的降维攻击),除了需要做好计算机网络、系统的安全以外,还需要考虑到物理环境下的主机安全。「安全」本身是符合「水桶理论」的,任何一个安全缺陷都会对整体的安全性造成威胁。

dm_crypt

dm-crypt 是使用内核加密API框架和设备映射器(device mapper)子系统的磁盘加密系统。使用 dm-crypt,管理员可以加密整个磁盘,逻辑卷以及分区等。

在 Linux 下通过 cryptsetup 工具来操作 dm-crypt,dm-crypt 已经被整合到 Linux Kernel 中,大部分的主流发行版都已经内置了 cryptsetup 工具,如果你使用的系统没有自带,请参考发行版的官方Wiki或网上教程进行安装。

虚拟分区

通过创建一个大文件来作为加密容器使用,它的好处是灵活(它本质上就是一个文件),可以很容易地复制到其他计算机上继续使用(当然需要密码)。

首先使用 fallocate 命令生成一个名为 enc 的 10GB 大小的文件容器:

fallocate -l 10G enc

使用 cryptsetup 对文件容器进行加密:

cryptsetup -c aes-xts-plain64 -s 512 -h sha512 -i 5000 luksFormat enc

它首先会提示设备内的数据将被覆盖,是否确定,确认无误后输入 YES(大写),然后输入两次密码。
注:enc 是前面创建的那个文件路径,这里因为 enc 就在当前目录,所以没有写路径。

参数解释
-c –cipher加密算法
-s –key-size密钥大小
-h –hash哈希算法
-i –iter-time用来对抗暴力破解,值越大暴力破解越困难(在解密时也越耗时)

完成后使用以下命令解密容器,并指定映射名为 myenc(这个参数不可省略):

cryptsetup open enc myenc

密码正确的话,你将在 /dev/mapper/ 目录下看到映射设备(myenc)。

解密后就可以把容器当作普通分区来操作了,先创建文件系统:

mkfs.ext4 enc

挂载文件系统:

mount /dev/mapper/myenc /mnt

使用完后记得及时关闭,密钥将会马上从内存中被清除:

先卸载文件系统:umount /mnt,然后关闭加密盘cryptsetup close myenc

物理分区

加密分区的基本操作和使用,在上面的「虚拟分区」 步骤中应该已经有所了解了,对物理分区的加密也是一样的(示例:cryptsetup [参数1 参数2 ……] luksFormat /dev/sda1),这里不再重复讲解。

加密现有的未加密的分区

如果你要加密根分区或 /boot 分区,还需要额外的配置工作,请参考「全盘加密」章节
如果设备中有重要数据,务必先做好备份再进行下列操作。

如果要对现有的未加密分区进行加密的话,就要使用到 cryptsetup-reencrypt 命令。

假设要加密的设备是 /dev/sdx1。

首先卸载分区:umount /dev/sdx1

因为 LUKS(Linux 硬盘加密标准)的加密头需要在分区的头部位置有 512 字节的大小,我们需要收缩文件系统的大小来给它腾出空间。(你分区的剩余空间应该够吧?……)
先检查文件系统:e2fsck -f /dev/sdx1
收缩文件系统:resize2fs -M /dev/sdx1(可能需要几分钟,取决于分区大小。)

加密分区:cryptsetup-reencrypt [参数1 参数2 ……] luksFormat --new --reduce-device-size 4096S /dev/sdx1(因为要重新加密整个设备,如果你的分区比较大的话,我建议你在睡前进行加密操作,起床时应该就好了……)

加密完成后,解密分区:cryptsetup open /dev/sdx1 www

再次检查文件系统(加密以后就不要操作原来的分区了,要操作解密后映射出来的设备,这里是 /dev/mapper/www):e2fsck -f /dev/mapper/www
恢复文件系统大小:resize2fs /dev/mapper/www

完成~

修改加密容器的加密参数

如果设备中有重要数据,务必先做好备份再进行下列操作。

如果在加密后想要修改加密容器的某些加密参数但不想对整个设备进行重新加密时,就会需要对现有的加密容器重新进行加密,同样是使用 cryptsetup-reencrypt 命令。

假设要重新加密的加密容器为 /dev/sdc3(这里要操作的是真实的分区,而不是映射的设备)。

首先确保加密容器已关闭,然后使用新参数对加密容器进行重新加密:

cryptsetup-reencrypt [参数1 参数2 ……] /dev/sdc3

输入密码,等待完成即可。

cryptsetup 常用操作

到这里,你应该已经能够理解基本的加/解密操作了,在后面的操作中,一些基本的操作不会做很详细的解释。在这里我把 cryptsetup 的常用操作统一做个讲解,在后面更复杂的操作中,可以专注于主要的部分,而不是在加密命令上纠缠。

密钥槽

密钥槽(Key slot)是用来表示解密密钥的“孔位”,LUKS 默认有 8 个密钥槽,也就是可以同时设置 8 个用来解密的密钥,在加密时我们已经用掉了第一个,使用 cryptsetup luksDump /dev/sdx?命令可以看到第一个(从 0 开始)密钥孔位已经被使用。

添加新密钥

新的解密密钥默认会使用下一个未使用的密钥孔位,也可以 通过 -S (--key-slot) 来指定要使用哪一个密钥孔位(0-7)。
使用 cryptsetup 的luksAddKey 选项添加一个新的密码或密钥文件(key-file),这里添加一个新的密码:

cryptsetup luksAddKey /dev/sdx1

提示说输入任何一个已存在的密钥槽的密码,输入密码,然后输入两次新密码即可。完成后使用 cryptsetup luksDump /dev/sdx1 命令可以看到现在已经有两个密钥槽被使用了,这两个密钥槽的密码都能用来解密加密容器。

删除一个密钥槽

使用 cryptsetup 命令的 luksKillSlot 选项来删除孔位为 1 的密钥槽:

cryptsetup luksKillSlot /dev/sdx1 1

输入其他剩下的任意一个密钥槽的密码即可。

key-file

除了密码,还可以使用 key-file 作为密钥来解密加密容器,它通常是一个随机数二进制文件。密码通常只有 10-20 位,因为有记忆成本,但是 key-file 没有这个限制。只使用 key-file 作为解密密钥,可以完全杜绝暴力破解,只要你的密钥文件不被窃取,那么它就是相对安全的。

通过 dd 命令生成一个 32KB 大小的名字为 key 的随机数文件作为 key-file:

dd if=/dev/urandom of=key bs=1k count=32

命令完成后,在当前目录下可以看到一个名为 key 的文件,把它加入到密钥槽:

cryptsetup luksAddKey /dev/sdx? key(密钥文件路径)

输入其他任意一个已有的密钥槽的密码。
如果你只想使用 key-file 来解密容器,添加成功后删除原来的密钥槽即可。

如果在加密时想使用 key-file 作为密钥,方法也是一样的:cryptsetup luksFormat /dev/sdd? key(密钥文件)

在解密时使用-d (--key-file)参数指定密钥文件:cryptsetup open /dev/sdx? myenc -d key(密钥文件)

在启动时解密设备

/etc/crypttab 文件类似于 fstab 文件,用来在启动时解密被加密的设备。
crypttab 文件于 fstab 文件之前被加载,这样可以在设备被解密后通过 fstab 文件正常挂载设备。

示例文件:

#<name> <device> <password> <options>

home UUID=b8ad5c18-f445-495d-9095-c9ec4f9d2f37 /etc/mypassword1
data1 /dev/sda3 /etc/mypassword2
data2 /dev/sda5 /etc/cryptfs.key
swap /dev/sdx4 /dev/urandom swap,cipher=aes-c

配置文件总共有四个字段,分别是映射设备名、加密设备、用来解密的密钥文件(key-file)、选项,第一和第二个字段是必需的。

省略第三个字段或把第三个字段设置为none-则表示在解密时手动输入密码。

如果要在系统启动时通过手动输入密码来解密 /dev/sdx1,配置文件可以写为如下:

myenc /dev/sdx1 none

系统在启动过程中会要求输入密码来解密设备。

如果要在解密后自动挂载设备,只需要像正常设备一样配置 fstab 文件即可:

/dev/mapper/myenc(映射设备名) /home ext4 rw,relatime 0 2

相关文档

  • 关于 crypttab 文件的详细信息可以看这里
  • 关于 cryptsetup 命令的详细信息可以看这里
  • 关于 cryptsetup-reencrypt 命令的详细信息可以看这里

全盘加密

这部分主要讲解如何在 Linux 下实现包含 /boot 分区的全盘加密

加密 /boot 的必要性

/boot 分区中包含第二阶段引导所需要的内核文件,加密 /boot 分区可以缓解大部分在启动过程中发生的攻击。(但仍然可能受到 BIOS/UEFI 固件篡改、冷启动攻击、硬件键盘记录器等攻击手段的威胁,这超出了本文范围,在此不作解释。)

Grub 是支持 /boot 分区加密的,我们会使用它来作为我们的引导加载程序。(不过 Grub 有一个小限制,下面会谈到。)

加密方案

要对系统进行「全盘的」加密,首先需要选择一种加密方案,以下有几种常见的加密方案,后续会针对这几种加密方案进行加密操作:
如果你使用 UEFI 引导:不管在什么情况下,EFI 分区都是不能被加密的。

  1. LVM 物理卷在加密容器下(先加密了整个分区,然后在加密分区中创建了 LVM物理卷。)
  2. 加密容器在 LVM 下(对 LVM 划分出来的逻辑卷进行加密。PS:逻辑卷可能有多个,如果想加密所有逻辑卷,那么需要分别对各个逻辑卷进行加密。)
  3. 没有使用 LVM 的普通分区
  4. 额外需要注意的问题:/boot 是否要进行单独分区?

来说说以上这几种分区布局应该如何进行加密,以及它们之间的一些区别:

第一种和第二种方案是使用 LVM 的情况,区别在于 LVM 和 加密容器的层级关系不同,如果 LVM 物理卷是创建在加密容器之下的,那么 LVM 的分区布局对外是不可见的,因为它被外层的加密容器所保护;如果加密容器是在 LVM 之下的话,实际上就是加密了 LVM 下的逻辑卷,这种情况下,LVM的分区布局对外是透明的,它本身(LVM)并没有被加密,被加密的是它底下的那些逻辑卷,第二种方案可以支持加密与非加密分区的混合使用,只加密需要加密的分区,具体操作和普通的分区加密没区别,这个章节说的是全盘加密,会假设所有逻辑卷都需要加密。
除了布局的可见性以外,第一和第二种方案还有一个区别,就是内核钩子的加载顺序,第一种情况需要先加载 dm-crypt 模块解密容器,然后 LVM 逻辑卷才会被映射出来,后续才能被正常挂载、使用;第二种情况则是需要 LVM 模块先加载,然后加载 dm-crypt 来解密逻辑卷。

第三种情况是没有使用 LVM,这是最普通的情况,这种情况和第二种情况基本相同,只是它不再需要加载 LVM 模块。

还有一个需要注意的点,就是 /boot 目录是否要进行单独分区? /boot 目录包含了系统引导时所需要的内核文件,如果要加密 /boot 分区,需要「引导加载程序」支持,Grub 就支持加密的 /boot 分区,我们使用它作为引导加载程序,但是 Grub 只支持 LUKS1 加密(cryptsetup 默认会使用 LUKS2,LUKS1 和 LUKS2 的主要区别在于 LUKS2 拥有更多功能,别担心,LUKS1 也是一样安全的),所以会有两种情况:/boot 进行了单独分区,在加密 /boot 分区时要指定-M luks1参数;/boot 没有进行单独分区,它和根分区在同一分区,那么就要在加密根分区的时侯指定-M luks1参数。

全盘加密-操作

已经详细讲过的操作在接下来不会讲得很细,需要的时候我依然会进行一些说明。

  • LVM 在加密容器中
    这里通过一个稍微复杂点的布局来说明各种操作。
    假设总共创建了三个分区,分别是/dev/sdx1/dev/sdx2/dev/sdx3, /dev/sdx1作为 EFI 分区(如果你是BIOS引导则不需要此分区),/dev/sdx2 作为 /boot 分区,/dev/sdx3中打算用来创建 LVM 物理卷。
    这里的 /boot 目录进行了单独分区,对它进行单独加密并指定使用 LUKS1:cryptsetup luksFormat -M luks1 /dev/sdx2
    打开加密容器:cryptsetup open /dev/sdx2 boot
    为 /boot 分区创建文件系统:mkfs.ext4 /dev/mapper/boot
    加密 dev/sdx3cryptsetup luksFormat /dev/sdx3
    打开加密容器:cryptsetup open /dev/sdx3 root
    创建文件系统用来存储物理卷:mkfs.ext4 /dev/mapper/root
    创建物理卷:pvcreate /dev/mapper/root
    创建名为 main 的卷组:vgcreate main /dev/mapper/root
    创建用来挂载在 /home 目录的逻辑卷:lvcreate -L 500G(大小) -n home(名字) main
    创建用来作根分区的逻辑卷:lvcreate -L 500G -n root main
    为逻辑卷分别创建文件系统(命令省略)。
    把逻辑卷分别挂载好,就可以正常安装系统了,安装好系统再进行下一步。
    前面说了,LVM 和 dm-crypt 的加载是有顺序的,这里需要先加载 dm-crypt 来解密容器,然后再加载 LVM。为了能够输入密码,还需要在 dm-crypt 之前先加载 keyboard 模块,LVM 和 dm-crypt 对应的模块名分别是lvm2encrypt。 内核钩子的配置文件在 Arch Linux 的位置是 /etc/mkinitcpio.conf,HOOKS这一行就是内核钩子了,把keyboardencryptlvm2 插入到合适的位置,正确的顺序大概是这样的(keyboard 模块只要在 dm-crypt 模块之前加载就没问题):HOOKS=(base udev autodetect keyboard modconf block encrypt lvm2 filesystems fsck)
    修改了内核钩子后,要重新生成 initramfs:mkinitcpio -p linux
    安装引导(如果没安装 Grub,请先安装):grub-install --target=x86_64-efi --efi-directory=/efi
    这里我以 UEFI 引导为例并假设 EFI 分区挂载在 /efi 目录,如果你使用 BIOS 引导的话参考这条命令:grub-install --target=i386-pc /dev/sdx(根分区)
    修改 Grub 内核启动参数(/dev/default/grub),修改GRUB_CMDLINE_LINUX=这一行配置,用来在引导时解密根分区,格式为如下:GRUB_CMDLINE_LINUX="cryptdevice=UUID=加密容器的UUID,可以通过 blkid 命令查看:映射设备名 root=根分区"
    我的配置如下:GRUB_CMDLINE_LINUX="cryptdevice=UUID=42ad71f4-d8a3-4ce5-8c38-7eb608008e35:root root=/dev/mapper/main-root"
    开启 Grub 的加密选项,用来在引导时解密 /boot 分区,将GRUB_ENABLE_CRYPTODISK=选项设置为GRUB_ENABLE_CRYPTODISK=y
    生成 Grub 配置文件:grub-mkconfig -o /boot/grub/grub.cfg现在已经完成了全盘加密,在引导过程中会要求你输入密码解密 /boot 分区,随后在启动系统时会再次要求你输入密码以解密根分区。如果你觉得麻烦,只想在开机时输入一个密码,可以通过把密钥嵌入到 initramfs 中实现,这里不作演示。知识补充:
    在解密根分区的时候,实际上就是在解锁加密容器,因为根分区包含在加密容器中,加密容器解锁之后,在它下面的那些逻辑卷也就自然解锁了,除了 /boot 和 根分区的解密需要在引导过程中完成,其他像 /home /var 之类的普通分区可以通过设置 /etc/crypttab 文件和 /etc/fstab 文件来在系统启动后自动解密并挂载,把密钥文件放在加密的根分区中(一定不要放在未加密的分区中,那等于白送……),在解密容器的过程中系统会自动读取密钥文件,而不用手动输入密码,同时因为密钥文件本身是存放在加密的根分区中,在根分区解密之前,它会得到保护。
  • 加密容器在 LVM 中
    这个方案的操作和上面的大同小异,只是加密容器和 LVM 的层级不同,内核钩子的顺序和上面的不一样(LVM 先加载,然后加载 encrypt);这种情况下逻辑卷都是单独加密的,要给除了根分区和 /boot 分区以外的逻辑卷都添加一个密钥文件用来启动时自动解密(如果要手动输入的话,逻辑卷比较多时岂不是要输到怀疑人生?),这样在启动时就只需要手动输入根分区和 /boot 分区的密码,系统成功启动后就会自动解密、挂载其他剩下的逻辑卷。
  • 没有使用 LVM这种情况和「加密容器在 LVM 中」的操作是基本相同的,只是不需要加载 lvm2 的内核钩子了(因为没有用到嘛),其他的配置和「加密容器在 LVM 中」没有区别。
  • /boot 和根分区在同一分区这种情况下,在加密根分区时一定要记得使用 LUKS1 加密,如果因为某些原因根分区一定要用 LUKS2,那就把 /boot 目录拉出来单独分区,单独使用 LUKS1 加密。
    在这种情况下你会发现一个问题:为什么 /boot 目录和根分区在同一分区,但是在启动时却需要输入两次密码?这是因为 Grub 在解锁分区后不会传递给 initramfs,第一次是为 Grub 输入密码,第二次是为 initramfs。解决办法是把密钥文件嵌入到initramfs 中。

Leave a Reply

Your email address will not be published. Required fields are marked *