让系统更安全 - 系统分区加密 (Btrfs on LUKS) 操作实录

加密方案介绍

我选择的系统盘文件系统是Btrfs,这是个很棒的文件系统,功能很强大,但不具备加密功能。

ArchWiki介绍了两种加密分区的思路。一种是在现有的文件系统上加密,这种方式可能会导致部分Btrfs的功能失效;另一种是在创建文件系统前加密,也就是使用LUKS

LUKS的加密方案花样繁多,大致有以下几种:

LUKS on Partition

这是最常用的方法。在空分区上建立LUKS加密头,然后创建文件系统。说白了就是先加密再分区。

不明白分区和文件系统区别的小伙伴建议先百度了解!

优点是配置方便,可以自由添加和更换加密密钥。

缺点是不灵活,必须预先分配要加密的磁盘空间。 可以,但很麻烦,而且很少有这种需求。

Plain dm-crypt

纯粹的加密,没有LUKS加密头,对SSD最友好,但局限较大的一种方式。

使用这种方式需要重视所有加密参数,并且保管好加密密钥,因为一旦加密完成,你就不能更改密钥了。

其他方式

还有其他加密方式,比如LUKS on RAID这样的软RAID加密,还有LUKS on LVMLVM on LUKS这样的套娃加密,一般用户并没有这样的需求,所以不介绍了。

接下来的操作将以LUKS on Partition作为加密盘的加密方案。

准备阶段

驱动器安全擦除

建立加密分区需要擦除硬盘。常规方法是建立分区前擦除,当然也可以建立加密后擦除

这里采用一种适用于NVMe驱动器的方法。

  1. 删除所有的分区,cfdisk fdisk gdisk用什么都行。

  2. 擦除驱动器。

    擦除NVMe驱动器的方法有两种,formatsanitize。后者是更完全的擦除,对硬盘寿命会有损耗。这里选择前者。假设硬盘块文件为/dev/nvme0n1,执行

    nvme format /dev/nvme0 -ses 1 -n 1

    nvme0表示是NVMe字符设备NVMe character device

    nvme0n1表示是NVMe命名空间块设备namespace block device,输入错误系统会报错并给出提示

    后面的-n 1参数指定了NVMe命名空间为1

分区

常规的分区操作。如果是UEFI引导需要划分至少两个分区。

如果不需要加密BOOT分区,需要额外划分一个分区作为BOOT分区,或者直接将EFI分区作为BOOT分区,这样的话就需要给EFI分区划分足够的空间。个人认为如果没有安装多内核的需要,给EFI分区划分100M的空间足矣。

另外,创建EFI分区的时候需要将分区类型修改为EFI Partition,格式化EFI分区可以使用mkfs.vfat

建立加密头

cryptsetup luksFormat /dev/nvme0n1p2

/dev/nvme0n1p2 加密分区的分区块路径

当然也可以设置其他参数,比如要指定LUKS版本,可以输入

cryptsetup --type luks1 luksFormat /dev/nvme0n1p2

默认参数

如果不添加参数,则会采用默认参数,那就相当于执行

cryptsetup --type luks2 --cipher aes-xts-plain64 --hash sha256 --iter-time 2000 --key-size 256 --pbkdf argon2i --sector-size 512 --use-urandom --verify-passphrase luksFormat device

其实多数情况下,默认参数完全够用,除非使用LUKS1,或者需要更强的安全性能,否则不需要手动设置参数。

至于原因,可以自己跑一下Benchmark,看一看各种算法在自己设备上的性能。执行以下指令

cryptsetup benchmark

我的测试结果如下

# 测试仅使用内存 (无存储 IO)。

PBKDF2-sha1 1476867 iterations per second for 256-bit key

PBKDF2-sha256 2766691 iterations per second for 256-bit key

PBKDF2-sha512 1109604 iterations per second for 256-bit key

PBKDF2-ripemd160 557753 iterations per second for 256-bit key

PBKDF2-whirlpool 429744 iterations per second for 256-bit key

argon2i 4 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)

argon2id 4 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)

# Algorithm | Key | Encryption | Decryption

aes-cbc 128b 1143.6 MiB/s 3209.4 MiB/s

serpent-cbc 128b 105.7 MiB/s 682.4 MiB/s

twofish-cbc 128b 210.9 MiB/s 411.1 MiB/s

aes-cbc 256b 901.8 MiB/s 3020.8 MiB/s

serpent-cbc 256b 114.7 MiB/s 710.2 MiB/s

twofish-cbc 256b 220.9 MiB/s 409.4 MiB/s

aes-xts 256b 1848.2 MiB/s 1753.7 MiB/s

serpent-xts 256b 676.4 MiB/s 670.6 MiB/s

twofish-xts 256b 398.2 MiB/s 406.0 MiB/s

aes-xts 512b 1640.6 MiB/s 1636.7 MiB/s

serpent-xts 512b 705.9 MiB/s 693.5 MiB/s

twofish-xts 512b 414.6 MiB/s 413.1 MiB/s

很明显,采用cbc加密方式的算法性能最好,但根据ArchWiki,这类算法有缺陷并不推荐使用。那排除采用cbc的算法之后,我们得到最好的加密算法组合是PBKDF2-sha256 aes-xts/256b,正是系统设置的默认参数。

密钥

  • 密码

如果在创建的过程中没有添加任何其他参数,系统就会要求创建密码来加密分区。这个密码后期是可以修改的,而且这个密码 (或密钥文件)可以不唯一。

  • 密钥文件

如果不想使用密码加密,可以直接使用密钥文件创建加密盘。需要使用这样的指令

cryptsetup luksFormat /dev/nvme0n1p2 /path/to/keyfile

这样的话,在创建的过程中系统就不会提示输入密码了。

ArchWiki给出了创建随机密钥文件的一个方法,需要用ROOT权限执行

dd bs=512 count=4 if=/dev/random of=/path/to/keyfile iflag=fullblock
chmod 000 /path/to/keyfile

创建的密钥文件需要保存到系统中,Wiki给出的示例是/etc/mykeyfile。以防万一,建议做好外部备份,防止因密钥丢失造成不可逆的数据损失。

安装系统

这部分就不赘述了,和正常安装系统无异。这里主要讲一下如何

解密分区

安装系统前需要解锁分区

cryptsetup open /dev/nvme0n1p2 dm_name

解密后的分区会被映射到/dev/mapper/dm_namedm_name可以修改为其他名称

如果使用了密钥文件加密,解密时需要指定密钥路径

cryptsetup open /dev/nvme0n1p2 dm_name --key-file /etc/mykeyfile

然后就可以在加密盘上创建文件系统了。我选用的是Btrfs。假设设定的dm_name为Root_

mkfs.btrfs /dev/mapper/Root_
mount /dev/mapper/Root_ /mnt

接下来的操作和正常的安装步骤一致。配置系统,并安装GRUB引导。

如果需要加密BOOT分区请参考BOOT分区加密的方法安装GRUB引导。

BOOT分区加密

如果没有特殊的安全需要,并不推荐加密BOOT分区,因为会大幅延长开机时间且要求设置密码或其他解密方式。

GRUB仅支持解密LUKS1加密的BOOT分区,尚未完全支持LUKS2加密的BOOT分区。

这不影响系统根目录使用LUKS2加密。

打开/etc/default/grub,修改这一项

GRUB_ENABLE_CRYPTODISK=y

修改后重新安装GRUB引导

grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB --recheck

--recheck 用于防止重复添加启动项

如果没有修改GRUB配置文件就安装引导,系统会报错并提示你修改。

配置开机解密

需要修改两处地方,mkinitcpio配置的HOOK,以及GRUB配置文件的内核启动参数。

/etc/mkinitcpio.conf

  1. 添加解密LUKS需要的HOOK。

    Archlinux提供了两套方案,可以使用busybox或者systemd提供的HOOK。

    推荐使用systemd,因为systemd提供的HOOK支持更多的功能,比如同时解密多个LUKS加密分区等。

    添加HOOK,以systemdHOOK为例

    HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt filesystems fsck)

    需要在对应位置添加粗体标注的条目。

  2. 添加密钥文件路径,不需要引号

    FILES=(/path/to/keyfile)

    然后执行mkinitcpio -p 内核名称创建内核文件,如果创建失败请检查是否安装了btrfs-progs包。

/etc/default/grub

  • 修改内核启动参数,即GRUB_CMDLINE_LINUX_DEFAULT变量。

其他引导器同理,修改内核启动参数即可。

例如systemd-boot需要修改entries的options

在已有的参数前添加参数:

rd.luks.name=device-UUID=cryptroot 指定加密盘的位置。这里使用了[UUID][Persistent_block_device_naming_(简体中文) - ArchWiki]作为分区定位方式。UUID可以使用指令查看lsblk -o name,uuid。注意填入的UUID是被加密分区的UUID,不是解密后映射分区的UUID。cryptroot是解密后映射分区的dm_name,一定要与解密分区时设置的dm_name一致。

rd.luks.options 设置解密参数,主要是LUKS加密等参数,一般不需要额外设置。如果是SSD,可以添加discard参数以提供对TRIM功能的支持。

rd.luks.key=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX=/path/to/keyfile 指定加密文件的位置,需要填入被加密分区的UUID以及文件路径

root=/dev/mapper/cryptroot 解密后映射的系统分区位置

下为示例

GRUB_CMDLINE_LINUX_DEFAULT="rd.luks.name=6120c3ba-7811-4692-a9c0-b0319a8effa4=root_ rd.luks.options=timeout=10s,discard,cipher=aes-xts-plain64:sha256,size=512 rd.luks.key=6120c3ba-7817-4692-a9c0-b0314a679ea4=/etc/cryptluks.key root=/dev/mapper/root_ loglevel=3 quiet"

然后生成grub.cfg,执行

grub-mkconfig -o /boot/grub/grub.cfg

/etc/crypttab.initramfs

此项与上条引导器参数只需要二选一。

新建文件/etc/crypttab.initramfs。这个文件会在执行mkinitcpio操作时自动添加入内核镜像中,但是每次修改后都需要执行一次mkinitcpio操作。

此文件的格式与/etc/crypttab相同,这里也可以将这个文件复制一份并重命名,即执行:

cp /etc/crypttab /etc/crypttab.initramfs

在文件后添加如下条目:

block_  UUID=566dfa42-21ce-4fda-b130-98c6404fdb45  -  discard,cipher=aes-xts-plain64:sha256,size=512

条目一共分为四列:

  • 第一列:映射分区的名称,即rd.luks.name=device-UUID=cryptrootcryptroot
  • 第二列:加密分区的物理位置,即rd.luks.name=device-UUID=cryptrootdevice-UUID,只是需要加上UUID的前缀。
  • 第三列:密钥文件的路径。留空就填none-
  • 第四列:解密参数,即rd.luks.options的内容。

编辑完后需要执行mkinitcpio

别忘了添加引导器的内核参数,只需要添加这项:

root=/dev/mapper/cryptroot 解密后映射的系统分区位置

使用Yubikey开机解密

最近入手了一个Yubikey 5,想着能不能用它来开机解密,实现真正的安全启动。答案是可以的。参考资料

这个方法使用的是U2F硬件密钥解密,要求提前配置好Yubikey的U2F(FIDO)功能。

引导时需要使用systemd的HOOK,这是由systemd提供的实现。

配置方法

  1. 插入Yubikey,执行以下指令。需要ROOT权限。

    指令中的块文件路径请按自己的加密盘实际路径修改。

    这里需要的是加密盘物理分区的块文件,而不是解密后的映射分区块文件。

    systemd-cyptenroll /dev/nvme0n1p2 --fido2-device=auto

    有以下可选参数:

    --fido2-with-client-pin=BOOL 解锁时是否需要输入PIN,默认为yes。建议添加并设置为yes,否则每次开机不仅要插入密钥还要输入PIN。

    --fido2-with-user-presence=BOOL 解锁时是否需要用户确认(即触摸密钥),默认为yes,需要FIDO设备支持”up”特性。如果使用Yubikey 5就不用设置这个了,即使关闭了也会被强制打开(有提示信息)。

    --fido2-with-user-verification=BOOL 解锁时是否需要用户确认,默认为no,需要FIDO设备支持”uv”特性。没看出和上面那条有什么区别,不用管即可。

    执行过程中需要输入Yubikey的FIDO PIN,并且需要输入加密盘的密码。

    此处并不能指定密钥文件。

    建议进行此操作前先添加一个密码,可在完成添加操作后再移除。

  2. 修改引导的解密参数,去除文件解密。并删除添加入启动镜像的密钥文件,否则硬件密钥会形同虚设。

    方法参考上文,反向操作即可。

  3. 添加额外的加密参数指定FIDO启动方式。

    在解密参数中添加fido2-device=auto即可。

开机

重启即可。

配置Yubikey解密后,开机需要将其插入,等待指示灯闪烁后触摸。如果设置了输入PIN解密,需要先输入PIN再触摸。

不要求通电前就插入密钥,可在屏幕点亮后再插入。

设置了Yubikey解密的同时,建议设置自动登陆,以实现“一键开机”的功能。

参考资料

Btrfs (简体中文)#加密 - ArchWiki

Encrypting an entire system#LUKS on a partition - ArchWiki

Encrypting an entire system#Encrypted boot partition (GRUB) - ArchWiki

Drive preparation - ArchWiki

Memory cell clearing - ArchWiki

Device encryption#Encryption options for LUKS mode - ArchWiki

Device encryption#Keyfiles - ArchWiki

Persistent block device naming (简体中文) - ArchWiki


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!