人们为什么通常会加密其个人计算机(有时是服务器)的驱动器? 很明显,没有人从磁盘上偷走了他们最喜欢的宠物猫的照片! 那真是倒霉:加密的驱动器要求您在每次启动时都从键盘输入一个关键词,而且它又长又无聊。 删除它,以便至少有时没有必要征募它。 是的,因此不会丢失加密的含义。
好吧,将其完全删除将不起作用。 您可以改为在USB闪存驱动器上制作密钥文件,它也可以使用。 如果没有闪存驱动器(并且网络上没有第二台计算机),这可能吗? 如果您很幸运使用BIOS,您几乎可以做到! 内容将是有关如何通过具有以下属性的LUKS配置磁盘加密的指南:
- 关闭计算机时,密码或密钥文件不会以打开形式(或等效于打开形式)存储在任何地方。
- 第一次打开计算机时,必须输入密码。
- 在随后的重启(关闭之前)中,不需要密码。
这些说明已在CentOS 7.6,Ubuntu 19.04和openSUSE Leap 15.1的虚拟机和实际硬件(台式机,便携式计算机和两台服务器)中进行了测试。 他们应该在具有Dracut工作版本的其他发行版上工作。
是的,以一种很好的方式,它应该已经出现在“异常系统管理”中心中,但是没有这样的中心。
我建议在LUKS容器中使用一个单独的插槽,并将其密钥存储在RAM中!
什么样的插槽?LUKS容器实现了多级加密。 磁盘上的有用数据通常使用aes-xts-plain64
对称密码进行加密。 此对称密码的密钥(主密钥)在容器创建阶段作为字节的随机序列生成。 在一般情况下,主密钥以加密形式存储-多份(插槽)。 默认情况下,八个插槽中只有一个处于活动状态。 每个活动插槽都有一个单独的密钥短语(或一个单独的密钥文件),您可以使用它们解密主密钥。 从用户的角度来看,事实证明您可以使用几个不同的关键短语(或关键文件)中的任何一个来解锁驱动器。 在我们的情况下,使用关键字短语(插槽0)或使用一块内存作为密钥文件(插槽6)。
重新启动时,大多数主板上的BIOS不会清理内存,或者您可以将其配置为不清理(已知例外:“英特尔公司S1200SP / S1200SP,BIOS S1200SP.86B.03.01.0042.013020190050 01/30/2019”)。 因此,您可以在其中存储密钥。 关闭电源后,RAM本身的内容将在一段时间后被擦除,同时密钥的未保护副本也将被擦除。
所以走吧
第一步:将系统安装在使用LUKS加密的磁盘上
在这种情况下,安装在/boot
的磁盘分区(例如/dev/sda1
)应保持未加密状态,而其他所有内容(例如/dev/sda2
)都应加密的其他分区。 加密分区上的文件系统可以是任何文件,也可以使用LVM,以便根文件系统,用于交换的卷以及除/boot
之外的所有其他/boot
位于同一容器中。 选择加密选项时,这对应于CentOS 7和Debian中的默认磁盘分区。 SUSE所做的一切都不同(加密/boot
),因此需要对磁盘进行手动分区。
结果应该类似于以下内容:
$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 9G 0 part └─luks-d07a97d7-3258-408c-a17c-e2fb56701c69 253:0 0 9G 0 crypt ├─centos_centos--encrypt2-root 253:1 0 8G 0 lvm / └─centos_centos--encrypt2-swap 253:2 0 1G 0 lvm [SWAP]
在使用UEFI的情况下,还将有一个EFI系统分区。
对于Debian和Ubuntu用户:用dracut
替换initramfs-tools
软件包。
# apt install --no-install-recommends dracut
在我们的情况下, initramfs-tools
实现了错误的逻辑,该逻辑应用于带有密钥文件的加密部分。 这些部分要么被完全忽略,要么将密钥文件的内容明文复制到initramfs(即结果复制到磁盘)中,而这是我们不需要的。
第二步:创建一个密钥文件,该文件将在热重启后用于自动解锁驱动器
128个随机位对我们来说足够了,即 16个字节。 该文件将存储在加密的磁盘上,因此,不知道加密密钥并且没有对已加载系统的root访问权限的任何人都不会读取该文件。
# touch -m 0600 /root/key # head -c16 /dev/urandom > /root/key
密钥文件中有足够多的真正随机位,因此实际上并不需要使用慢速PBKDF算法,该算法从潜在的弱密钥短语中很难选择加密密钥。 因此,添加键时,可以减少迭代次数:
# cryptsetup luksAddKey --key-slot=6 --iter-time=1 /dev/sda2 /root/key Enter any existing passphrase:
如您所见,密钥文件存储在加密的磁盘上,因此如果关闭计算机,则不会造成安全风险。
第三步:在物理内存中分配空间以存储密钥
Linux至少具有三个不同的驱动程序,可让您访问已知地址的物理内存。 这是linux/drivers/char/mem.c
,它还负责设备/dev/mem
以及phram
模块(模拟MTD芯片,给出设备/dev/mtd0
)和nd_e820
(与nd_e820
使用时,给出/dev/pmem0
)。 它们都有不愉快的特征:
- 如果发行版使用了 Matthew Garrett 的LOCKDOWN补丁集 ,则使用安全启动时
/dev/mem
不可写的(如果发行版要使用Microsoft签名的引导程序来支持安全启动,则必须使用此补丁集); phram
在CentOS和Fedora上不可用-维护人员在构建内核时根本没有启用相应的选项。nd_e820
需要保留至少128 MB的内存-这就是NVDIMM的工作方式。 但这是在带有安全启动的CentOS上运行的唯一选项。
由于没有理想的选择,因此下面将全部三个考虑在内。
使用任何一种方法时,都需要格外小心,以免意外影响设备或所需存储范围以外的范围。 对于已经具有MTD芯片或NVDIMM模块的计算机,尤其如此。 即, /dev/mtd0
或/dev/pmem0
可能不是与保留用于存储密钥的存储区域相对应的设备。 配置文件和脚本所依赖的现有设备的编号也可能会造成混淆。 因此,建议您暂时禁用所有依赖现有设备/dev/mtd*
和/dev/pmem*
。
通过将memmap
选项传递给memmap
来memmap
Linux物理内存。 我们对该选项的两种类型感兴趣:
memmap=4K$0x10000000
保留(即保留标记,以便内核本身不使用)4 KB内存,从物理地址0x10000000开始;memmap=128M!0x10000000
标记128兆字节的物理内存,从地址0x10000000开始,作为NVDIMM(显然是假的,但对我们有用)。
c $
选项适合与/dev/mem
和phram
,c选项!
-对于nd_e820
。 使用$
时,使用!
时,保留存储器$
起始地址必须为0x1000
的倍数(即4 KB) !
0x8000000
的倍数(即128兆字节)。
重要:GRUB配置文件中的美元符号( $
)是特殊字符,必须转义。 两次:一次-从/etc/default/grub
生成grub.cfg
时,第二次-在引导阶段解释生成的配置文件时。 即 在/etc/default/grub
,以下行应最终出现:
GRUB_CMDLINE_LINUX="memmap=4K\\\$0x10000000 ... ..."
如果不对$
符号进行两次转义,系统将无法启动,因为它将认为它只有4 KB的内存。 带有感叹号没有这些困难:
GRUB_CMDLINE_LINUX="memmap=128M!0x10000000 ... ..."
root
在/proc/iomem
伪/proc/iomem
访问物理存储卡(需要找出要保留的地址):
# cat /proc/iomem ... 000f0000-000fffff : reserved 000f0000-000fffff : System ROM 00100000-7ffddfff : System RAM 2b000000-350fffff : Crash kernel 73a00000-7417c25e : Kernel code 7417c25f-747661ff : Kernel data 74945000-74c50fff : Kernel bss 7ffde000-7fffffff : reserved 80000000-febfffff : PCI Bus 0000:00 fd000000-fdffffff : 0000:00:02.0 ...
RAM被标记为“系统RAM”,足以让我们保留其页面之一来存储密钥。 猜测BIOS内存中的哪一部分在重新启动时不会碰到,这将无法可靠地提前进行。 除非另外一台计算机具有与本手册完全相同的BIOS版本和相同的内存配置。 因此,在一般情况下,有必要通过反复试验来采取行动。 通常,BIOS重启时,它只会在每个内存范围的开始和结尾更改数据。 通常,从边缘撤退128兆字节( 0x8000000
)。 对于具有1 GB或更多内存的KVM虚拟机,建议的选项( memmap=4K$0x10000000
和memmap=128M!0x10000000
)有效。
使用phram
模块时, phram
需要另一个内核命令行参数,该参数实际上告诉模块使用哪个物理内存-我们的,保留的。 该参数称为phram.phram
,它包含三个部分:名称(任意最多63个字符,在sysfs
可见),起始地址和长度。 起始地址和长度必须与memmap
的相同,但不支持后缀K
和M
GRUB_CMDLINE_LINUX="memmap=4K\\\$0x10000000 phram.phram=savedkey,0x10000000,4096 ..."
编辑/etc/default/grub
您需要重新生成GRUB在启动时读取的实际配置文件。 正确的命令取决于分发。
# grub2-mkconfig -o /boot/grub2/grub.cfg # CentOS (Legacy BIOS) # grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg # CentOS (UEFI) # update-grub # Debian, Ubuntu # update-bootloader --reinit # SUSE
更新GRUB配置后,应该重新启动计算机,但是稍后在更新initramfs时我们将重新启动计算机。
第四步:配置LUKS从内存中读取密钥
/etc/crypttab
加密设置存储在/etc/crypttab
。 它的每一行都包含四个字段:
- 解锁时应获得的设备,
- 加密设备
- 在何处获取密钥文件(
none
表示从键盘输入密钥短语), - 选项的可选字段。
如果密钥文件存在但不适合,则Dracut询问密钥短语。 实际上,在首次启动时将需要该文件。
新安装的发行版中的/etc/crypttab
的示例:
# cat /etc/crypttab # luks-d07....69 UUID=d07....69 none
在我们的案例中,密钥文件将是一块物理内存。 即 /dev/mem
, /dev/mtd0
或/dev/pmem0
,具体取决于所选的内存访问技术。 需要使用选项来指示文件的哪一部分是关键。
# cat /etc/crypttab # # /dev/mem: luks-d07....69 UUID=d07....69 /dev/mem keyfile-offset=0x10000000,keyfile-size=16 # phram: luks-d07....69 UUID=d07....69 /dev/mtd0 keyfile-size=16 # nd_e820: luks-d07....69 UUID=d07....69 /dev/pmem0 keyfile-size=16
只是它不会那样工作。
关键是systemd如何确定何时可以解锁设备。 即,他从第三列中取出该设备并等待相应的设备单元变为活动状态。 似乎合乎逻辑:尝试解锁LUKS容器是没有道理的,直到出现带有密钥文件的设备。 但是设备单元与设备本身不同。 默认情况下,Systemd仅为与块设备和网络接口的子系统相关的内核设备创建设备单元。 设备/dev/mem
和/dev/mtd0
是逐个字符的,因此默认情况下不会对其进行监视,并且永远不会将它们识别为就绪。
您将不得不告诉systemd他应该通过在/etc/udev/rules.d/99-mem.rules
文件中创建udev规则来跟踪它们:
# /dev/mem KERNEL=="mem", TAG+="systemd" # /dev/mtd* KERNEL=="mtd*", TAG+="systemd" # /dev/pmem*
第五步:重新生成initramfs
我提醒您:本文仅讨论使用Dracut的发行版。 包括那些默认情况下不使用但可访问且有效的地方。
您需要重新生成initramfs,以便在那里更新/etc/crypttab
文件。 并且-在其中包含其他内核模块和udev规则。 否则,将不会创建设备/dev/mtd0
或/dev/pmem0
。 Dracut force_drivers
配置参数负责启用和加载其他内核模块,而install_items
则负责其他文件。 我们使用以下内容创建文件/etc/dracut.conf.d/mem.conf
(需要使用引号引起来的空格,这是一个分隔符):
# /dev/mem: install_items+=" /etc/udev/rules.d/99-mem.rules" # phram: install_items+=" /etc/udev/rules.d/99-mem.rules" force_drivers+=" phram" # nd_e820: force_drivers+=" nd_e820 nd_pmem"
实际上再生initramfs:
# dracut -f
对于Debian和Ubuntu用户,维护者大肆宣传:生成的文件被错误地调用。 您需要重命名它,以便使用与GRUB配置中规定的相同的方法来命名:
# mv /boot/initramfs-5.0.0-19-generic.img /boot/initrd.img-5.0.0-19-generic
安装新内核时,正确执行了通过Dracut自动创建initramfs的问题,该错误仅影响dracut -f
的手动启动。
第六步:重新启动计算机
需要重新启动才能使GRUB和Dracut配置更改生效。
# reboot
在此阶段,内存中没有密钥,因此您需要输入密码。
重新启动后,需要检查内存备份是否正常工作。 至少,在/proc/iomem
伪/proc/iomem
所需的内存应标记为“保留”(使用/dev/mem
或phram
)或“持久性内存(旧版)”。
使用phram
或nd_e820
您需要确保设备/dev/mtd0
或/dev/pmem0
确实引用了先前保留的内存部分,而不是其他内容。
# cat /sys/class/mtd/mtd0/name # : "savedkey" # cat /sys/block/pmem0/device/resource #
如果不是这样,那么您需要找到/dev/mtd*
或/dev/pmem*
哪个设备/dev/pmem*
“我们的”,然后修复/ etc / crypttab,重新生成initramfs并在再次重启后重新检查结果。
第七步:配置将密钥文件复制到内存
重启前,密钥文件将被复制到内存中。 在系统关闭阶段运行任何命令的一种方法是在systemd服务的ExecStop
伪指令中注册它。 为了使systemd知道这不是一个守护进程,并且不会因为缺少ExecStart
指令而发誓,您需要将服务类型指定为oneshot
,并且即使该服务没有任何工作进程,也建议该服务被视为正在运行。 因此,这是/etc/systemd/system/savekey.service
文件。 只需要保留ExecStop
指令的给定变体之一。
[Unit] Description=Saving LUKS key into RAM Documentation=https://habr.com/ru/post/457396/ [Service] Type=oneshot RemainAfterExit=true # /dev/mem: ExecStop=/bin/sh -c 'dd if=/root/key of=/dev/mem bs=1 seek=$((0x10000000))' # /dev/mtd0: ExecStop=/bin/dd if=/root/key of=/dev/mtd0 # /dev/pmem0: ExecStop=/bin/dd if=/root/key of=/dev/pmem0 [Install] WantedBy=default.target
由于dd
无法理解十六进制表示法, /bin/sh
需要使用/bin/sh
构造。
我们激活该服务,请检查:
# systemctl enable savekey # systemctl start savekey # reboot
在随后的重新引导过程中,您不必从磁盘输入密码。 并且,如果有必要,这通常意味着保留存储区的起始地址选择不正确。 可以修复并重新生成多个文件,然后重新启动计算机两次。
使用phram
或nd_e820
只需编辑GRUB配置。 使用/dev/mem
在/etc/crypttab
也提到/dev/mem
起始地址(因此,必须重新生成initramfs)和systemd服务中。
但这还不是全部。
安全问题
关于安全性问题的任何讨论均基于威胁模型。 即 攻击者的目标和手段。 我知道下面的一些例子牵强。
对关闭的计算机进行物理访问的情况与在内存中未配置密钥存储的情况没有什么不同。 旨在获取关键短语的攻击类型相同,包括“ 邪恶女仆 ”和相同的安全功能 。 我们没有停下来,因为没有新的东西。
当计算机打开时,更有趣的情况是。
情况1 。 攻击者无法物理访问计算机,也不知道密码短语,但是可以通过ssh进行root访问。 目标是解密磁盘的密钥。 例如,访问虚拟机磁盘映像的逐个扇区的旧备份。
实际上,飞碟上的密钥位于/root/key
文件中。 问题是这与执行该指令之前发生的事情有何关系。 答:对于luks1,威胁并不新鲜。 有一个dmsetup table --target crypt --showkeys
显示主密钥,即 还有允许访问旧备份的数据。 对于luks2,这种情况下的安全性确实发生了:dm-crypt密钥存储在内核级别的密钥链中,并且不可能从用户空间查看它们。
情况2 。 攻击者可以使用键盘看屏幕,但还没有准备打开外壳。 例如,我使用了IPMI泄漏的密码或在云中拦截了noVNC会话。 他不知道关键字,也不知道其他任何密码。 目标是root访问。
请:通过Ctrl-Alt-Del
重启,通过GRUB添加内核选项init=/bin/sh
。 由于成功从内存中读取了密钥,因此不需要密码。 为了避免这种情况,您必须防止GRUB加载菜单上未包含的内容。 不幸的是,此功能在不同的发行版中以不同的方式实现。
从7.2版开始,CentOS具有grub2-setpassword
,该grub2-setpassword
实际上使用密码保护GRUB。 其他发行版可能具有自己的实用程序来完成同一任务。 如果不是,那么您可以直接编辑/etc/grub.d
目录中的文件并重新生成grub.cfg
。
在/etc/grub.d/10_linux
文件中,更改CLASS变量,在--unrestricted
添加--unrestricted
选项(如果不存在):
CLASS="--class gnu-linux --class gnu --class os --unrestricted"
在/etc/grub.d/40_custom
文件中,添加指定编辑内核命令行所需的用户名和密码的行:
set superusers="root" password_pbkdf2 root grub.pbkdf2....... # grub2-mkpasswd-pbkdf2
或者,如果完全需要禁用此类功能,则如下所示:
set superusers=""
情况3 。 攻击者可以访问随附的计算机,从而使您可以从不受信任的媒体启动。 这可以是物理访问,而无需打开外壳或通过IPMI访问。 目标是root访问。
它可以从USB闪存驱动器或CD-ROM加载GRUB,并将init=/bin/sh
到您的内核参数中,如上例所示。 因此,BIOS中应禁止从任何可怕的媒体引导。 并且还可以通过密码保护BIOS设置更改。
情况4 。 攻击者可以物理访问附带的计算机,包括打开盒子的能力。 目的是找出密钥或获得root用户访问权限。
总的来说,这是一个失败的情况。 尚未通过冷却内存模块来攻击 ( 冷启动攻击 )。 从理论上讲(未检查),您可以利用现代SATA磁盘支持热重新连接这一事实。 重新启动计算机时,断开磁盘连接,将grub.cfg
更改为init=/bin/sh
,重新连接,然后重新引导系统。 事实证明(如果我理解正确的话)根访问权限。
云托管的不道德的员工可以通过对虚拟机进行快照及其后续修改来制作快照,从而完成几乎相同的事情。
其他事项
重新启动过程中将密钥保留在内存中是一种嘲弄。 最纯净的形式,可免费使用。 较干净的解决方案是使用kexec并将密钥添加到动态生成的initramfs中。 它还可以防止替换内核参数 。 是的,如果kexec在工作,那就是。 现代发行版使kexec配置变得过于复杂 。
在数据中心,甚至在云中,电力永远不会消失。 原来,不再需要关键词了吗? 确实, 如果您对此有把握,可以将其删除。 它最终将成为一台正在运行的服务器,没人知道它的磁盘密钥¹,因此也不会透露,但是可以使用常规方法对其上的系统进行更新。 — sudo poweroff
.
¹ /root/key
— , cron.
? IPMI, . IPMI Java. .
? SSH . ! . , sudo reboot
, ?
, . SSH , . , , .