dm-crypt/设备加密
本节介绍如何从命令行手动利用 dm-crypt 来加密系统。
准备工作
在开始使用 cryptsetup 前,请务必确保 dm_crypt 内核模块 已加载。
Cryptsetup 用法
cryptsetup(8) 是与 dm-crypt 交互以创建、访问和管理加密设备的命令行工具。该工具后来经过扩展,支持了依赖于 Linux 内核 device-mapper 和 cryptographic 模块的不同加密类型。其中最重要的扩展是 Linux 统一密钥设置 (LUKS) 扩展,它将 dm-crypt 所需的所有设置信息存储在磁盘本身,并抽象了分区和密钥管理,以提高易用性。通过 device-mapper 访问的设备被称为块设备。更多信息请参阅 静态数据加密#块设备加密。
该工具的用法如下:
# cryptsetup action options device name
它对选项和加密模式有编译好的默认设置,如果在命令行中未指定其他设置,则将使用这些默认设置。请查看:
$ cryptsetup --help
其中按顺序顺列出了选项、操作和加密模式的默认参数。完整选项列表可在 man 页面中找到。由于根据加密模式和操作的不同,所需的或可选的参数也不同,以下章节将进一步指出差异。块设备加密速度很快,但速度也非常重要。由于在设置后更改块设备的加密密码很困难,因此提前检查各个参数的 dm-crypt 性能非常重要:
$ cryptsetup benchmark
可以在安装前为选择算法和密钥大小提供参考。如果某些 AES 密码具有明显更高的吞吐量,那么这些密码很可能获得了 CPU 的硬件加速支持。
Cryptsetup 密码和密钥
加密的块设备受密钥保护。密钥可以是:
- 密码:参见 Security#Passwords。
- 密钥文件:参见 #Keyfiles。
这两种密钥类型都有默认的最大限制:密码最长可达 512 个字符,密钥文件最大为 8192 KiB。
此处需要注意 LUKS 的一个重要区别:密钥用于解锁 LUKS 加密设备的主密钥 (master-key),且可通过 root 权限更改。其他加密模式在设置后不支持更改密钥,因为它们不使用主密钥进行加密。详情参见 静态数据加密#块设备加密。
加密选项
Cryptsetup 支持不同的加密操作模式与 dm-crypt 配合使用:
--type luks使用默认的 LUKS 格式版本(cryptsetup < 2.1.0 为 LUKS1,cryptsetup ≥ 2.1.0 为 LUKS2),--type luks1使用旧版本的 LUKS1,--type luks2使用引入了额外扩展的当前版本 LUKS2,--hw-opal-only用于在支持 TCG OPAL 标准的驱动器上进行基于硬件的加密。参见 自加密驱动器#使用 cryptsetup 和 cryptsetup(8) § SED (自加密驱动器) OPAL 扩展。--hw-opal将 OPAL 硬件加密与 dm-crypt 软件加密分层使用。
--type plain使用 dm-crypt plain 模式,--type loopaes使用 loopaes 遗留模式,--type tcrypt使用 TrueCrypt 兼容模式。--type bitlk使用 BitLocker 兼容模式。参见 cryptsetup(8) § BITLK (Windows BitLocker 兼容) 扩展。
可用的基本加密选项(密码和哈希)可用于所有模式,并依赖于内核加密后端的特性。可以通过以下命令查看运行时已加载且可作为选项使用的所有特性:
$ less /proc/crypto
$ cryptsetup benchmark 将触发加载可用模块。下面介绍 luks, luks1, luks2 和 plain 模式的加密选项。请注意,表格中列出的是本文示例中使用的选项,并非所有可用选项。
LUKS 模式的加密选项
在 LUKS 加密模式下设置新的 dm-crypt 设备的 cryptsetup 操作是 luksFormat。与名称所暗示的不同,它并不格式化设备,而是设置 LUKS 设备头,并使用所需的加密选项对主密钥进行加密。
要使用 cryptsetup --help 中列出的编译默认值创建一个新的 LUKS 容器,只需执行:
# cryptsetup luksFormat device
从 cryptsetup 2.4.0 开始,这等同于:
# cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --hash sha256 --iter-time 2000 --key-size 256 --pbkdf argon2id --use-urandom --verify-passphrase device
下表将默认值与具有更高加密规范的示例进行了对比,并附带了说明:
| 选项 | Cryptsetup 2.1.0 默认值 | 示例 | 评论 |
|---|---|---|---|
| --cipher -c |
aes-xts-plain64
|
aes-xts-plain64
|
1.6.0 版本 将默认值更改为 XTS 模式下的 AES 密码(见 FAQ 的项目 5.16)。建议不要使用之前的默认值 --cipher aes-cbc-essiv,因为它存在已知的 问题 和针对它们的实际 攻击。 |
| --key-size -s |
256 (对于 XTS 为 512) |
512
|
默认情况下,XTS 密码使用 512 位密钥大小。但请注意,XTS 会将提供的密钥对半拆分,因此结果是使用了 AES-256。 |
| --hash -h |
sha256
|
sha512
|
用于 密钥派生 的哈希算法。1.7.0 版本将默认值从 sha1 更改为 sha256,“并非出于安全原因,[而是] 主要是为了防止已逐步淘汰 SHA1 的加固系统上的兼容性问题”[1]。之前的默认值 sha1 仍可用于与旧版本 cryptsetup 的兼容,因为它被 认为是安全的(见项目 5.20)。 |
| --iter-time -i |
2000
|
5000
|
用于 PBKDF 密码处理的毫秒数。1.7.0 版本将默认值从 1000 更改为 2000,以“尽量保持 PBKDF2 迭代次数足够高,同时也能让用户接受”[2]。此选项仅与设置或更改密码的 LUKS 操作(如 luksFormat 或 luksAddKey)相关。指定 0 参数将选择编译默认值。 |
| --use-urandom | --use-urandom
|
--use-random
|
选择要使用的 随机数生成器。请注意 /dev/random 阻塞池已被移除。因此,--use-random 标志现在等同于 --use-urandom。 |
| --verify-passphrase -y |
是 | - | 在 Arch Linux 中默认对 luksFormat 和 luksAddKey 开启。 |
| --sector-size | 512 或 4096 (取决于设备) |
4096
|
设置磁盘加密使用的扇区大小(以字节为单位)。对于自报为 4Kn 或 512e 的块设备,默认值为 4096;对于自报为 512n 的块设备,默认值为 512。在大多数现代存储设备上,将扇区大小从 512 字节增加到 4096 字节可以提供更好的性能。参见 高级格式#dm-crypt。 |
LUKS 特性和选项的属性在 LUKS1 (pdf) 和 LUKS2 (pdf) 规范中有详细描述。
迭代时间
源自 cryptsetup FAQ§2.1 和 §3.4:
- 密钥插槽的解锁时间 [...] 是在设置密码时计算的。默认情况下为 1 秒(LUKS2 为 2 秒)。[...]
- 密码迭代次数基于时间,因此安全级别取决于创建 LUKS 容器系统的 CPU 性能。[...]
- 如果你在快速的机器上设置密码,然后在慢速机器上解锁它,解锁时间可能会长得多。
因此,最好始终在最常访问该容器的机器上创建容器。
阅读这些章节的其余部分,了解如何在需要时正确调整迭代次数的建议。
Plain 模式的加密选项
在 dm-crypt plain 模式下,设备上没有主密钥,因此无需设置。相反,直接使用所采用的加密选项在加密磁盘和命名设备之间创建映射。该映射可以针对分区或整个设备。在后一种情况下,甚至不需要分区表。
要使用 cryptsetup 的默认参数创建 plain 模式映射:
# cryptsetup open --type plain options device name
执行它将提示输入密码,该密码应具有极高的熵;可以使用 --verify-passphrase 选项,但这不是默认设置。通常建议准确记录用于创建的加密选项,因为它们无法从加密设备或可选密钥文件中派生,且上游默认值可能会更改。
下面对比了默认参数与 dm-crypt/加密整个系统#Plain dm-crypt 中的示例。
| 选项 | Cryptsetup 2.7.0 默认值 | 示例 | 评论 |
|---|---|---|---|
| --hash -h |
sha256
|
- | 哈希用于从密码中创建密钥;它不用于密钥文件。 |
| --cipher -c |
aes-xts-plain64
|
aes-xts-plain64
|
密码由三部分组成:加密器-链模式-IV 生成器。有关这些设置的说明,请参阅 静态数据加密#加密算法和操作模式,有关某些可用选项的信息,请参阅 DMCrypt 文档。 |
| --key-size -s |
256
|
512
|
密钥大小(以位为单位)。大小取决于所使用的加密器以及所使用的链模式。XTS 模式所需的密钥大小 是 CBC 的两倍。 |
| --size -b |
目标磁盘的真实大小 | - (应用默认值) | 限制设备的最大大小(以 512 字节扇区为单位)。 |
| --offset -o |
0
|
0
|
从目标磁盘开头开始映射的偏移量(以 512 字节扇区为单位)。 |
| --skip -p |
0
|
2048 (将跳过 512B×2048=1MiB) |
在计算初始化向量 (IV) 时要跳过的加密数据的 512 字节扇区数。 |
| --key-file -d |
默认使用密码 | /dev/sdZ (或例如 /boot/keyfile.enc) |
要作为密钥使用的设备或文件。详情参见 #Keyfiles。 |
| --keyfile-offset | 0
|
0
|
密钥开始处相对于文件开头的偏移量(以字节为单位)。cryptsetup 1.6.7 及更高版本支持此选项。 |
| --keyfile-size -l |
8192kB
|
- (应用默认值) | 限制从密钥文件中读取的字节数。cryptsetup 1.6.7 及更高版本支持此选项。 |
| --sector-size | 512
|
4096
|
设置磁盘加密使用的扇区大小(以字节为单位)。除 4Kn 块设备外,所有设备的默认值均为 512。在大多数现代存储设备上,将扇区大小从 512 字节增加到 4096 字节可以提供更好的性能。参见 高级格式#dm-crypt。 |
假设使用设备 /dev/sdX,上方右栏的示例执行结果如下:
# cryptsetup open --type plain --cipher=aes-xts-plain64 --offset=0 --skip=2048 --key-file=/dev/sdZ --key-size=512 --sector-size 4096 /dev/sdX enc
现在我们可以检查映射是否已成功建立:
# fdisk -l
现在应该存在一个 /dev/mapper/enc 条目。
使用 cryptsetup 加密设备
本节展示如何利用这些选项来创建新的加密块设备并手动访问它们。
cryptsetup luksFormat --pbkdf pbkdf2)来使用 LUKS2。使用 LUKS 模式加密设备
格式化 LUKS 分区
要将分区设置为加密的 LUKS 分区,请执行:
# cryptsetup luksFormat device
随后将提示你输入密码并进行验证。
命令行选项详见 #LUKS 模式的加密选项。
你可以通过以下命令检查结果:
# cryptsetup luksDump device
你会注意到 dump 不仅显示了加密器头部信息,还显示了 LUKS 分区正在使用的密钥槽 (key-slots)。
以下示例将在 /dev/sda1 上创建一个加密根分区,使用 XTS 模式下的默认 AES 密码,有效加密强度为 256 位:
# cryptsetup luksFormat -s 512 /dev/sda1
使用 LUKS 和密钥文件格式化分区
在创建新的 LUKS 加密分区时,可以使用以下命令在创建时关联密钥文件:
# cryptsetup luksFormat device /path/to/mykeyfile
有关如何生成和管理密钥文件的说明,请参阅 #Keyfiles。
使用 device mapper 解锁/映射 LUKS 分区
LUKS 分区创建完成后,就可以解锁它们了。
解锁过程将使用 device mapper 将分区映射到一个新的设备名称。这会告知内核 device 实际上是一个加密设备,应通过 /dev/mapper/dm_name 形式的 LUKS 寻址,以免覆盖加密数据。为了防止意外覆盖,在完成设置后,请阅读有关 备份加密头部 (cryptheader) 的可能性。
要开启一个加密的 LUKS 分区,请执行:
# cryptsetup open device dm_name
随后将提示你输入密码以解锁该分区。通常,设备映射名称会描述被映射分区的功能。例如,以下操作解锁了一个名为 /dev/sda1 的根 LUKS 分区,并将其映射到名为 root 的设备映射:
# cryptsetup open /dev/sda1 root
开启后,根分区的设备地址将变为 /dev/mapper/root 而非原分区地址(如 /dev/sda1)。
如果要在加密层之上设置 LVM,则解密后的卷组设备文件将类似于 /dev/mapper/root 而非 /dev/sda1。LVM 随后会为创建的所有逻辑卷赋予额外名称,例如 /dev/lvmpool/root 和 /dev/lvmpool/swap。
为了向分区中写入加密数据,必须通过设备映射名称进行访问。访问的第一步通常是 创建文件系统。例如:
# mkfs.ext4 /dev/mapper/root
设备 /dev/mapper/root 随后可以像任何其他分区一样被 挂载。
要关闭 LUKS 容器,请先卸载分区,然后执行:
# cryptsetup close root
使用 TPM 存储密钥
参见 可信平台模块#LUKS 加密。
使用 plain 模式加密设备
dm-crypt plain 模式的创建和后续访问仅需使用带正确 参数 的 cryptsetup open 操作。下面通过两个非根设备的示例展示这一点,但通过嵌套两者增加了一个复杂性(即在第一个内部创建第二个)。显然,嵌套加密会加倍开销。此处的用例仅仅是为了说明加密器选项用法的另一个例子。
首先使用 cryptsetup 的 plain 模式默认值创建一个映射器,如上表左栏所述:
# cryptsetup --type plain -v open /dev/sdxY plain1
WARNING: Using default options for cipher (aes-xts-plain64, key size 256 bits) that could be incompatible with older versions. WARNING: Using default options for hash (sha256) that could be incompatible with older versions. For plain mode, always use options --cipher, --key-size and if no keyfile is used, then also --hash. Enter passphrase for /dev/sdxY: Command successful.
现在我们在其内部添加第二个块设备,使用不同的加密参数和(可选)偏移量,创建文件系统并挂载它:
# cryptsetup --type plain --cipher=serpent-xts-plain64 --hash=sha256 --key-size=256 --offset=10 open /dev/mapper/plain1 plain2
Enter passphrase for /dev/mapper/plain1:
# lsblk -p
NAME /dev/sda ├─/dev/sdxY │ └─/dev/mapper/plain1 │ └─/dev/mapper/plain2 ...
# mkfs -t ext2 /dev/mapper/plain2 # mount -t ext2 /dev/mapper/plain2 /mnt # echo "This is stacked. one passphrase per foot to shoot." > /mnt/stacked.txt
我们关闭嵌套层以检查访问是否正常:
# cryptsetup close plain2 # cryptsetup close plain1
首先,让我们尝试直接开启文件系统:
# cryptsetup --type plain --cipher=serpent-xts-plain64 --hash=sha256 --key-size=256 --offset=10 open /dev/sdxY plain2
# mount -t ext2 /dev/mapper/plain2 /mnt
mount: /mnt: wrong fs type, bad option, bad superblock on /dev/mapper/plain2, missing codepage or helper program, or other error.
dmesg(1) may have more information after failed mount system call.
为什么不起作用?因为 "plain2" 的起始块 (10) 仍然被 "plain1" 的密码加密。它只能通过嵌套的映射器访问。不过报错是任意的,尝试错误的密码或错误的选项也会产生相同的结果。对于 dm-crypt plain 模式,open 操作本身不会报错退出。
按正确顺序再次尝试:
# cryptsetup close plain2 # dysfunctional mapper from previous try
# cryptsetup --type plain open /dev/sdxY plain1
WARNING: Using default options for cipher (aes-xts-plain64, key size 256 bits) that could be incompatible with older versions. WARNING: Using default options for hash (sha256) that could be incompatible with older versions. For plain mode, always use options --cipher, --key-size and if no keyfile is used, then also --hash. Enter passphrase for /dev/sdxY:
# cryptsetup --type plain --cipher=serpent-xts-plain64 --hash=sha256 --key-size=256 --offset=10 open /dev/mapper/plain1 plain2
Enter passphrase for /dev/mapper/plain1:
# mount /dev/mapper/plain2 /mnt && cat /mnt/stacked.txt
This is stacked. one passphrase per foot to shoot.
dm-crypt 也能处理一些混合模式的嵌套加密。例如,LUKS 模式可以嵌套在 "plain1" 映射器之上。当其关闭时,其头部将在 "plain1" 内部被加密。
仅 plain 模式可用的选项是 --shared。使用它,可以将单个设备分割成不同的不重叠映射器。我们在下一个示例中演示,这次对 "plain2" 使用 loopaes 兼容的密码模式:
# cryptsetup --type plain --offset 0 --size 1000 open /dev/sdxY plain1
WARNING: Using default options for cipher (aes-xts-plain64, key size 256 bits) that could be incompatible with older versions. WARNING: Using default options for hash (sha256) that could be incompatible with older versions. For plain mode, always use options --cipher, --key-size and if no keyfile is used, then also --hash. Enter passphrase for /dev/sdxY:
# cryptsetup --type plain --offset 1000 --size 1000 --shared --cipher=aes-cbc-lmk --hash=sha256 open /dev/sdxY plain2
WARNING: Using default options for cipher (aes-cbc-lmk, key size 256 bits) that could be incompatible with older versions. For plain mode, always use options --cipher, --key-size and if no keyfile is used, then also --hash. Enter passphrase for /dev/sdxY:
# lsblk -p
NAME dev/sdxY ├─/dev/sdxY │ ├─/dev/mapper/plain1 │ └─/dev/mapper/plain2 ...
如设备树所示,两者位于同一层级,即不是嵌套的,且 "plain2" 可以单独开启。
LUKS 特有的 Cryptsetup 操作
密钥管理
可以为 LUKS 分区定义额外的密钥。这使用户能够为安全备份存储创建访问密钥。在所谓的“密钥托管 (key escrow)”中,一个密钥用于日常使用,另一个托管起来,以便在忘记日常密码或密钥文件丢失/损坏时获得分区的访问权限。不同的密钥槽也可用于向用户授予分区的访问权限(签发第二个密钥),稍后再将其吊销。
创建加密分区后,初始密钥槽 0 即被创建(如果未手动指定其他槽)。额外的密钥槽编号从 1 到 7。可以通过执行以下命令查看使用了哪些密钥槽:
# cryptsetup luksDump /dev/device
其中 device 是包含 LUKS 头部的块设备。本节中的此命令及后续所有命令同样适用于头部备份文件。
添加 LUKS 密钥
添加新密钥槽通过 luksAddKey 操作完成。为了安全,即使对于已经解锁的设备,它也始终会要求输入一个有效的现有密钥(任何现有槽位的密码),然后才能输入新密钥:
# cryptsetup luksAddKey /dev/device [/path/to/additionalkeyfile]
Enter any existing passphrase: Enter new passphrase for key slot: Verify passphrase:
如果给出了 /path/to/additionalkeyfile,cryptsetup 将为 additionalkeyfile 添加一个新的密钥槽。否则,它会提示输入新密码。若要使用现有的密钥文件 (keyfile) 授权操作,请使用 --key-file 或 -d 选项,后跟“旧的” keyfile,这将尝试解锁所有可用的密钥文件密钥槽:
# cryptsetup luksAddKey /dev/device [/path/to/additionalkeyfile] -d /path/to/keyfile
如果打算使用多个密钥并更改或吊销它们,可以使用 --key-slot 或 -S 选项来指定槽位:
# cryptsetup luksAddKey /dev/device -S 6
WARNING: The --key-slot parameter is used for new keyslot number. Enter any existing passphrase: Enter new passphrase for key slot: Verify passphrase:
# cryptsetup luksDump /dev/device
...
Keyslots:
...
6: luks2
Key: 512 bits
Priority: normal
...
6 打算在引导时解锁设备,请以 root 身份执行 cryptsetup config --priority prefer --key-slot 6 /dev/device。为了展示此示例中的关联操作,我们决定立即更改密钥:
# cryptsetup luksChangeKey /dev/device -S 6
Enter passphrase to be changed: Enter new passphrase: Verify passphrase:
然后继续将其移除。
移除 LUKS 密钥
有三种不同的操作可从头部移除密钥:
luksRemoveKey通过指定密码/密钥文件来移除密钥。参见 cryptsetup-luksRemoveKey(8)。luksKillSlot通过指定槽位来移除密钥(需要另一个有效密钥)。显然,如果你忘记了密码、丢失了密钥文件或无法访问它,这非常有用。参见 cryptsetup-luksKillSlot(8)。erase移除 所有 活动密钥。参见 cryptsetup-erase(8)。
- 上述所有操作都可能被用来不可撤销地删除加密设备的最后一个活动密钥!
erase命令 不会 提示输入有效密码!它不会 抹除 LUKS 头部,但会同时抹除所有密钥槽,除非你有 LUKS 头部的有效备份,否则你将无法重新获得访问权限。- 在 OPAL 硬件加密分区上使用
erase命令时,LUKS 头部将被抹除,OPAL 锁定范围也将被移除。与软件加密不同,即使有有效的 LUKS 头部备份,此操作也是不可恢复的。
鉴于上述警告,了解我们要 保留 的密钥是否有效非常有用。一个简单的检查方法是使用 -v 选项解锁设备,它会说明密钥占用了哪个槽位:
# cryptsetup --test-passphrase -v open /dev/device
No usable token is available. Enter passphrase for /dev/device: Key slot 1 unlocked. Command successful.
现在我们可以使用其密码移除上一小节中添加的密钥:
# cryptsetup luksRemoveKey /dev/device
Enter passphrase to be deleted:
如果为两个密钥槽使用了相同的密码,那么第一个槽位现在将被抹除。只有再次执行该命令才会移除第二个。
或者,我们可以指定密钥槽位:
# cryptsetup luksKillSlot /dev/device 6
Enter any remaining passphrase:
请注意,在这两种情况下,都不需要确认。
再次重申上述警告:如果密钥槽 1 和 6 使用了相同的密码,那么这两个槽位现在都消失了。
备份和恢复
如果 LUKS 加密分区的头部遭到破坏,你将无法解密数据。这与忘记密码或损坏用于解锁分区的密钥文件一样棘手。损坏可能是由于你后来在重新分区磁盘时失误造成的,也可能是第三方程序误读分区表造成的。因此,拥有头部的备份并将其存储在另一个磁盘上可能是一个好主意。
使用 cryptsetup 备份
Cryptsetup 的 luksHeaderBackup 操作存储 LUKS 头部和密钥槽区域的二进制备份:
# cryptsetup luksHeaderBackup /dev/device --header-backup-file /mnt/backup/file.img
其中 device 是包含 LUKS 卷的分区。
你也可以将纯文本头部备份到 tmpfs,并在将其写入持久存储之前使用 GPG 等进行加密:
# mount --mkdir -t tmpfs -o noswap tmpfs /root/tmp # cryptsetup luksHeaderBackup /dev/device --header-backup-file /root/tmp/file.img # gpg --recipient User_ID --encrypt /root/tmp/file.img # cp /root/tmp/file.img.gpg /mnt/backup/ # umount /root/tmp
noswap 选项可确保文件系统不会被交换到磁盘。使用 cryptsetup 恢复
为了避免恢复错误的头部,你可以先将其作为远程 --header 使用,以确保其确实有效:
# cryptsetup -v --header /mnt/backup/file.img open /dev/device test
No usable token is available. Enter passphrase for /dev/device: Key slot 0 unlocked. Command successful.
# mount /dev/mapper/test /mnt/test && ls /mnt/test # umount /mnt/test # cryptsetup close test
既然检查成功了,现在可以执行恢复:
# cryptsetup luksHeaderRestore /dev/device --header-backup-file ./mnt/backup/file.img
现在所有的密钥槽区域都已被覆盖;发出命令后,只有来自备份文件的活动密钥槽才可用。
手动备份和恢复
头部始终位于设备的开头,即使不使用 cryptsetup 也可以执行备份。首先,你必须找出加密分区的有效载荷偏移量 (payload offset):
# cryptsetup luksDump /dev/device | grep "Payload offset"
Payload offset: 4040
其次,检查驱动器的扇区大小:
# fdisk -l /dev/device | grep "Sector size"
Sector size (logical/physical): 512 bytes / 512 bytes
现在你已经知道了这些值,可以使用简单的 dd 命令备份头部:
# dd if=/dev/device of=/path/to/file.img bs=512 count=4040
并安全地存储它。
恢复时可以使用备份时的相同数值进行操作:
# dd if=./file.img of=/dev/device bs=512 count=4040
重新加密设备
cryptsetup 的 reencrypt 操作允许重新加密 LUKS 设备。对于 LUKS2 设备,重新加密可以在线执行,支持多个并行重新加密任务,并且对系统故障具有恢复韧性。LUKS1 设备的重新加密只能离线(取消挂载后)执行,仅支持单进程,且恢复韧性较差。
有关操作模式和选项,请参阅 cryptsetup-reencrypt(8)。
可以更改 #LUKS 模式的加密选项。它还可用于将现有的未加密文件系统转换为 LUKS 加密的文件系统,或永久移除设备的 LUKS 加密(使用 --decrypt;参见 移除系统加密)。对于分离式 LUKS 头部,重新加密也是可行的,但请注意 --header 选项的警告。不支持 LUKS 以外的模式(如 plain 模式)的重新加密。
重新加密的一个应用场景是在密码或 密钥文件 泄露 且 无法确定 LUKS 头部的副本是否已被获取后,重新确保数据的安全。例如,如果只有密码被窥视,但没有发生对设备的物理/逻辑访问,则只需更改相应的密码/密钥即可(#密钥管理)。
以下展示了加密未加密文件系统分区以及对现有 LUKS 设备进行重新加密的示例。
加密现有的未加密文件系统
/boot(参见 dm-crypt/加密整个系统#准备引导分区)。这并非严格必要,但有许多优点:- 如果
/boot位于加密的根分区内,机器启动时系统会询问两次密码。第一次发生在引导加载程序尝试读取加密/boot内的文件时,第二次是在内核尝试挂载加密分区时 [4]。这可能不是预期的行为,可以通过拥有一个独立的、未加密的引导分区来防止。 - 如果
/boot位于加密分区内,某些系统恢复应用程序(例如 timeshift)将无法正常工作 [5]。
LUKS 加密头部始终存储在设备的开头。由于现有文件系统通常会占用所有分区扇区,因此第一步是缩小它,为 LUKS 头部留出空间。
默认 LUKS2 头部需要 16 MiB。如果当前文件系统占用了所有可用空间,我们必须至少将其缩小这么多。要将 /dev/sdxY 上的现有 ext4 文件系统缩小到其当前可能的最小值:
# umount /mnt
# e2fsck -f /dev/sdxY
e2fsck 1.46.5 (30-Dec-2021) Pass 1: Checking inodes, blocks, and sizes ... /dev/sda6: 12/166320 files (0.0% non-contiguous), 28783/665062 blocks
# resize2fs -p -M /dev/sdxY
e2fsck 1.46.5 (30-Dec-2021) Resizing the filesystem on /dev/sdxY to 26347 (4k) blocks. The filesystem on /dev/sdxY is now 26347 (4k) blocks long.
-M 缩小到最小尺寸可能需要很长时间。你可能希望计算一个比当前大小小 32 MiB 的尺寸,而不是使用 -M。现在我们对其进行加密,使用默认密码无需明确指定:
# cryptsetup reencrypt --encrypt --reduce-device-size 32M /dev/sdxY
WARNING! ======== This will overwrite data on LUKS2-temp-12345678-9012-3456-7890-123456789012.new irrevocably. Are you sure? (Type 'yes' in capital letters): YES Enter passphrase for LUKS2-temp-12345678-9012-3456-7890-123456789012.new: Verify passphrase:
完成后,整个 /dev/sdxY 分区都被加密了,而不仅仅是文件系统被缩小到的空间。作为最后一步,我们在现已加密的分区上将原始 ext4 文件系统扩展至再次占用所有可用空间:
# cryptsetup open /dev/sdxY recrypt
Enter passphrase for /dev/sdxY: ...
# resize2fs /dev/mapper/recrypt
resize2fs 1.43-WIP (18-May-2015) Resizing the filesystem on /dev/mapper/recrypt to 664807 (4k) blocks. The filesystem on /dev/mapper/recrypt is now 664807 (4k) blocks long.
# mount /dev/mapper/recrypt /mnt
文件系统现在可以使用了。你可能希望将其添加到 crypttab 中。
- 配置 mkinitcpio 和内核参数。参见 dm-crypt/系统配置#在早期用户空间解锁。
- 更新 fstab 中
/的条目,使用已解锁卷的标识符(例如 UUID)。
重新加密现有的 LUKS 分区
在此示例中,对现有的 LUKS 设备进行重新加密。
luksChangeKey 和/或创建备份。要使用当前的加密选项重新加密设备,无需指定它们:
# cryptsetup reencrypt /dev/sdxY
当使用不同的加密器和/或哈希重新加密设备时,现有的密钥会被保留。
另一个用例是重新加密具有过时加密选项的 LUKS 设备。在这种情况下,必须指定所需的新选项。请注意,LUKS2 头部允许每个密钥槽单独设置加密选项,因此重新加密仅适用于数据段。
更改 LUKS 头部的能力也可能受到其大小的限制。例如,如果设备最初是使用 CBC 模式密码和 128 位密钥加密的 LUKS1 设备,则 LUKS 头部的大小将是上述 4096 扇区的一半:
# cryptsetup luksDump /dev/sdxY | grep -e "mode" -e "Payload" -e "MK bits"
Cipher mode: cbc-essiv:sha256 Payload offset: 2048 MK bits: 128
要升级此类设备的加密选项,请考虑在重新加密前将头部 转换 为 LUKS2。如果由于头部空间不足导致转换失败,你可能需要使用 --reduce-device-size 选项重新加密,以便为更大的 LUKS 头部留出更多空间。请记住,这两种方法都带有固有风险:转换过程中头部的风险,以及在需要额外头部扇区的情况下对文件系统数据的风险。
LUKS1 到 LUKS2 的相互转换
cryptsetup 工具具有 convert 操作,可用于 LUKS1 和 LUKS2 头部格式之间的转换。建议在转换前创建 头部备份。参数 --type 是 必填 的。
从 LUKS1 迁移到 LUKS2:
# cryptsetup convert --type luks2 /dev/sdxY
回退到 LUKS1(例如,为了从带有加密 /boot 的 GRUB 引导):
# cryptsetup convert --type luks1 /dev/sdxY
Cannot convert to LUKS1 format - keyslot 0 is not LUKS1 compatible.
如果容器正在使用 Argon2,则需要将其转换为 PBKDF2 才能兼容 LUKS1。
# cryptsetup luksConvertKey --pbkdf pbkdf2 /dev/sdxY
调整加密设备的大小
如果使用 dm-crypt 加密的存储设备正被克隆(使用 dd 等工具)到另一个更大的设备,或者分区正在扩大或缩小,则底层文件系统必须调整大小。对于基于 LUKS 的设备,这是唯一的必要步骤,因为 LUKS 并不存储有关分区的任何大小信息,而是默认使用全部大小(即未传递 --size 参数时)。为此,请按照通常步骤 调整分区大小。以下是扩大包含 ext4 文件系统的加密 LUKS 设备 /dev/sdX2 的示例:
首先,使用 Parted 或 fdisk 调整底层分区的大小。缩小分区时,这需要在最后进行。现在,开启你的设备并调整文件系统大小:
# cryptsetup luksOpen /dev/sdX2 sdX2 # e2fsck /dev/mapper/sdX2 # resize2fs /dev/mapper/sdX2 # Uses all available space on the enlarged LUKS partition
挂载已映射且扩容后的 LUKS 设备后,即可像以前一样使用它。
# mount /dev/mapper/sdX2 /mnt/enlarged_sdX2
LUKS上的LVM
参见 调整 LUKS 上的 LVM。
回环文件系统
假设一个加密的回环文件系统存储在文件 /bigsecret 中,回环连接到 /dev/loop0,映射到 secret 并挂载在 /mnt/secret,如 dm-crypt/加密非根文件系统#文件容器 中的示例所示。
如果该容器文件当前已映射和/或挂载,请先将其卸载和/或关闭:
# umount /mnt/secret # cryptsetup close secret # losetup -d /dev/loop0
接下来,按你想增加的数据大小扩展容器文件。在本例中,文件将增加 1 MiB × 1024,即 1 GiB。
oflag=append conv=notrunc 选项,否则你将覆盖文件而不是追加。强烈建议在此步骤前进行备份。# dd if=/dev/urandom of=/bigsecret bs=1M count=1024 iflag=fullblock oflag=append conv=notrunc status=progress
现在将容器映射到回环设备:
# losetup /dev/loop0 /bigsecret # cryptsetup open /dev/loop0 secret
之后,将容器的加密部分调整为容器文件的新最大大小:
# cryptsetup resize secret
最后,执行文件系统检查,如果没问题,调整其大小(以 ext2/3/4 为例):
# e2fsck -f /dev/mapper/secret # resize2fs /dev/mapper/secret
你现在可以再次挂载容器了:
# mount /dev/mapper/secret /mnt/secret
受完整性保护的设备
如果设备在格式化时开启了完整性支持(例如 --integrity hmac-sha256),并且底层块设备被缩小,它将无法被开启并报错:device-mapper: reload ioctl on failed: Invalid argument。
要在不再次抹除设备的情况下解决此问题,可以使用之前的主密钥对其进行格式化(从而保持每个扇区标签有效)。
# cryptsetup luksDump /dev/sdX2 --dump-master-key --master-key-file=/tmp/masterkey-in-tmpfs.key # cryptsetup luksFormat /dev/sdX2 --type luks2 --integrity hmac-sha256 --master-key-file=/tmp/masterkey-in-tmpfs.key --integrity-no-wipe # rm /tmp/masterkey-in-tmpfs.key
密钥文件
什么是密钥文件?
密钥文件是一个文件,其数据用作解锁加密卷的密码。这意味着如果此类文件丢失或被修改,可能再也无法解密该卷。
为什么要使用密钥文件?
密钥文件有许多种。下面总结了使用的每种类型密钥文件的优缺点:
密钥文件类型
密码
这是一种包含简单密码的密钥文件。此类密钥文件的好处是,如果文件丢失,其中的数据是已知的,且卷的所有者有望轻松记住它。尽管如此,这在系统启动时手动输入密码的基础上并没有增加任何安全性。
示例:correct horse battery staple
# printf '%s' 'your_passphrase' | install -m 0600 /dev/stdin /etc/cryptsetup-keys.d/keyfile.key
如果文件中包含引号或反斜杠等特殊字符,与其对这些字符进行转义,不如直接编辑密钥文件,输入或粘贴密码,然后使用简便的 perl 单行命令移除末尾的换行符:
# perl -pi -e 'chomp if eof' /etc/cryptsetup-keys.d/keyfile.key
随机字符
这是一种包含一组随机字符的密钥文件。这类密钥文件的优点是它比简单的密码更能抵抗字典攻击。在此类情况下可以利用的密钥文件的另一个优势是所使用数据的长度。由于这不是给人记忆后输入的字符串,因此创建包含数千个随机字符的文件作为密钥是非常容易的。缺点是如果此文件丢失或更改,如果没有备份密码,很可能无法访问加密卷。
随机字符密钥文件可以使用任何字符集,但在键盘布局或 Unicode 支持不可靠的情况下(例如在启动 LUKS 解锁阶段输入紧急密码,或者使用不支持 Unicode 的老式 POSIX 工具测量密钥文件),坚持使用通用的 ASCII 字母和数字集可以使情况更简单。你可以像这样生成一个此类字符串:
$ tr -dc '[:alnum:]' </dev/urandom | head -c64
示例:rTCBW6j1dI2aYC5KcD6Ar38rBGN2DkWyang3RT7pdMGpdf1kRuMXi8EBHKu0BJ8X
或者,随机的 (UTF-8) Unicode 字符密钥文件可能如下所示:
示例:W‰[5ODó?Oéµ»9…¬hjT}› DЧíŽuLÝæ•Ýœ§aþóx±)Ñ)léeð•ú=èe
二进制
这是一个已被定义为密钥文件的二进制文件。在确定密钥文件候选对象时,建议选择相对静态的文件,如照片、音乐、视频剪辑。这些文件的优点是它们具有双重功能,这使得它们更难被识别为密钥文件。密钥文件看起来像是普通的图像文件或音乐片段,而不是一个带有大量随机文本的文本文件。缺点是如果此文件丢失或更改,如果没有备份密码,很可能无法访问加密卷。此外,与随机生成的文本文件相比,理论上存在随机性损失。这是由于图像、视频和音乐在相邻数据位之间存在某些内在关联,而随机文本文件则不存在。然而,这在学术上存在争议,且从未在公开场合被利用过。
示例:图片, 文本, 视频, ...
创建包含随机字符的密钥文件
将密钥文件存储在文件系统上
密钥文件可以是任意内容和大小。
此处使用 dd 生成一个包含 2048 个随机字节的密钥文件,并将其存储在 /etc/cryptsetup-keys.d/mykeyfile.key 文件中:
# dd bs=512 count=4 if=/dev/random iflag=fullblock | install -m 0600 /dev/stdin /etc/cryptsetup-keys.d/mykeyfile.key
如果你打算将密钥文件存储在外部设备上,只需将输出文件更改为相应的目录即可:
# dd bs=512 count=4 if=/dev/random of=/run/media/user/usbstick/mykeyfile.key iflag=fullblock
安全覆盖存储的密钥文件
如果你将临时密钥文件存储在物理存储设备上并想将其删除,请记住不要只是稍后移除该密钥文件,而是使用类似如下命令:
# shred --remove --zero mykeyfile
将其安全覆盖。对于 FAT 或 ext2 等老旧文件系统,这已足够,但在日志文件系统、闪存硬件等情况下,强烈建议 彻底擦除整个设备。
将密钥文件存储在 tmpfs 中(禁用交换)
或者,你可以挂载一个禁用交换的 tmpfs 来临时存储密钥文件:
# mount --mkdir -t tmpfs -o noswap tmpfs /root/mytmpfs # cd /root/mytmpfs
优点是它驻留在 RAM 中而不是物理磁盘上,因此卸载 tmpfs 后无法恢复。在将密钥文件拷贝到另一个安全的持久文件系统后,使用以下命令再次卸载 tmpfs:
# umount /root/mytmpfs
配置 LUKS 使用密钥文件
为 LUKS 头部添加一个对应密钥文件的密钥槽:
# cryptsetup luksAddKey /dev/sda2 /etc/cryptsetup-keys.d/mykeyfile.key
Enter any existing passphrase:
使用密钥文件手动解锁分区
开启 LUKS 设备时使用 --key-file 选项:
# cryptsetup open /dev/sda2 dm_name --key-file /etc/cryptsetup-keys.d/mykeyfile.key
引导时解锁根分区
这只需配置 mkinitcpio 以包含必要的模块或文件,并配置 cryptkey 内核参数 从而知道在哪里找到密钥文件。
下面涵盖了两种情况:
- 使用存储在外部介质(例如 USB 闪存盘)上的密钥文件
- 使用嵌入在 initramfs 中的密钥文件
使用存储在外部介质上的密钥文件
配置mkinitcpio
你必须将该驱动器 文件系统 的内核模块添加到 /etc/mkinitcpio.conf 中的 MODULES 数组。例如,如果文件系统是 Ext4 则添加 ext4,如果是 FAT 则添加 vfat:
MODULES=(vfat)
如果在引导时出现有关坏超级块 (bad superblock) 和坏代码页 (bad codepage) 的消息,则需要加载额外的代码页模块。例如,你可能需要 nls_iso8859-1 模块用于 iso8859-1 代码页。
配置内核参数
- 对于使用 encrypt 钩子的 busybox 基础 initramfs,参见 dm-crypt/系统配置#cryptkey。
- 对于使用 sd-encrypt 钩子的 systemd 基础 initramfs,参见 dm-crypt/系统配置#rd.luks.key。
使用嵌入在 initramfs 中的密钥文件
- 在引导过程的早期使用某种形式的身份验证。否则将发生自动解密,完全违背了块设备加密的目的。
/boot是加密的。否则,通过不同安装环境(包括 Live 环境)获取 root 权限者可以从 initramfs 中提取你的密钥,且无需任何其他身份验证即可解锁设备。
此方法允许使用将被嵌入到 initramfs 中且特定命名的密钥文件,该文件将被 encrypt 钩子 提取,从而自动解锁根文件系统 (cryptdevice)。在使用 GRUB early cryptodisk 功能时应用此方法很有用,以避免在引导过程中输入两次密码。
生成密钥文件,赋予合适的权限并 将其添加为 LUKS 密钥:
# dd bs=512 count=4 if=/dev/random iflag=fullblock | install -m 0600 /dev/stdin /etc/cryptsetup-keys.d/root.key # cryptsetup luksAddKey /dev/sdX# /etc/cryptsetup-keys.d/root.key
600 权限,因此普通用户无法通过生成的 initramfs 读取该密钥文件。将密钥包含在 mkinitcpio 的 FILES 数组 中:
/etc/mkinitcpio.conf
FILES=(/etc/cryptsetup-keys.d/root.key)
对于 encrypt 钩子,密钥文件通过 cryptkey= 内核参数指定:对于 initramfs,语法为 rootfs:/path/to/keyfile。默认值为 /crypto_keyfile.bin,如果 initramfs 包含具有此路径的有效密钥,则可以省略 cryptkey。参见 dm-crypt/系统配置#cryptkey。
对于上述示例,使用带有 encrypt 钩子的 busybox 基础 initramfs 时,设置以下内核参数:
cryptkey=rootfs:/etc/cryptsetup-keys.d/root.key
如果改用 sd-encrypt 钩子,密钥文件通过 rd.luks.key= 内核参数指定:对于 initramfs,语法为 /path/to/keyfile。默认值为 /etc/cryptsetup-keys.d/name.key(其中 name 是用于 #使用 cryptsetup 加密设备 中解密的 dm_name),如果 initramfs 包含具有此路径的有效密钥,则可以省略 rd.luks.key。参见 dm-crypt/系统配置#rd.luks.key。
下次重启时,你应该只需输入一次容器解密密码。
(来源)