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 page)中找到。由于根据加密模式和操作的不同,某些参数是必须的或可选的,接下来的章节将进一步指出差异。块设备加密速度很快,但性能依然很重要。由于在设置后更改块设备的加密算法非常困难,因此预先检查 dm-crypt 在各个参数下的性能非常重要
$ cryptsetup benchmark
可以在安装前为决定算法和密钥大小提供指导。如果某些 AES 算法展现出明显更高的吞吐量,那么这些算法很可能是得到了 CPU 硬件支持的。
Cryptsetup 密码和密钥
加密的块设备受密钥保护。密钥可以是:
两种密钥类型都有默认的最大尺寸:密码最长可达 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,即 LUKS 的上一个版本,--type luks2使用 LUKS2,即引入了额外扩展的当前 LUKS 版本,--hw-opal-only仅在支持 TCG OPAL 标准的驱动器上使用硬件加密。参见 自加密驱动器#使用 cryptsetup 和 cryptsetup(8) § SED (自加密驱动器) OPAL 扩展。--hw-opal将 OPAL 硬件加密与 dm-crypt 软件加密分层结合。
--type plain使用 dm-crypt 普通模式,--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 普通 模式下,设备上没有主密钥,因此不需要对其进行设置。相反,所采用的加密选项直接用于在加密磁盘和命名设备之间创建映射。映射可以针对分区或整个设备。在后一种情况下,甚至不需要分区表。
要使用 cryptsetup 的默认参数创建 普通 模式映射:
# 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
|
加密算法由三部分组成:cipher-chainmode-IV 生成器。有关这些设置的说明,请参阅 静态数据加密#加密算法和操作模式,以及 DMCrypt 文档 以了解部分可用选项。 |
| --key-size(密钥大小) -s |
256
|
512
|
密钥大小(以位为单位)。大小取决于所使用的加密算法以及所使用的链模式 (chainmode)。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) |
用作密钥的设备或文件。有关更多详情,请参阅 #密钥文件。 |
| --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-slot)。
以下示例将在 /dev/sda1 上创建一个加密的根分区,使用 XTS 模式下的默认 AES 算法进行有效的 256 位加密:
# cryptsetup luksFormat -s 512 /dev/sda1
使用 LUKS 和密钥文件格式化分区
创建新的 LUKS 加密分区时,可以在创建时使用以下命令关联密钥文件:
# cryptsetup luksFormat device /path/to/mykeyfile
有关如何生成和管理密钥文件的说明,请参阅 #密钥文件。
使用 device mapper 解锁/映射 LUKS 分区
一旦 LUKS 分区创建完成,就可以将它们解锁。
解锁过程将使用 device mapper(设备映射器)将分区映射到新的设备名称。这会告知内核 device 实际上是一个加密设备,应该通过 /dev/mapper/dm_name 形式的 LUKS 寻址,以免覆盖加密数据。为了防止意外覆盖,请在完成设置后阅读有关 备份 cryptheader(加密头)的方法。
要打开一个加密的 LUKS 分区,请执行:
# cryptsetup open device dm_name
然后会提示你输入密码以解锁分区。通常,设备映射后的名称会描述该映射分区的功用。例如,以下操作解锁了 /dev/sda1 根 luks 分区,并将其映射到名为 root 的 device mapper:
# 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 普通模式加密的创建和后续访问都只需使用具有正确 参数 的 cryptsetup open 操作即可。下面通过两个非根设备的示例来展示这一点,并通过堆叠(即在第一个设备内创建第二个设备)来增加趣味。显然,堆叠加密会产生双倍开销。这里的用例仅为了说明 cipher 选项使用的另一个例子。
第一个映射器使用 cryptsetup 的普通模式默认值创建,如上表左栏所述:
# 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 普通模式,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" 关闭时,LUKS 的加密头也将被加密存储在 "plain1" 内部。
仅普通模式可用的选项是 --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 添加一个新的密钥槽。否则,它会提示输入新密码。要使用现有的 密钥文件 授权此操作,使用 --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 以外模式(例如普通模式)的重新加密。
重新加密的一个应用是在密码或 密钥文件 泄露 且 不能确定 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 工具具有用于 LUKS1 和 LUKS2 格式转换的 convert 操作。建议在转换前创建 加密头备份。参数 --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。
回环 (Loopback) 文件系统
假设一个加密的回环文件系统存储在 /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 内核参数 以便知道在哪里找到密钥文件即可。
下面涵盖了两种情况:
- 使用存储在外部介质(例如 U 盘)上的密钥文件
- 使用嵌入在 initramfs 中的密钥文件
使用存储在外部介质上的密钥文件
配置mkinitcpio
你必须将驱动器 文件系统 的内核模块添加到 /etc/mkinitcpio.conf 中的 MODULES 数组。例如,如果文件系统是 Ext4 则添加 ext4,如果是 FAT 则添加 vfat:
MODULES=(vfat)
如果启动时出现有关坏超级块和坏代码页的信息,则需要加载额外的代码页模块。例如,对于 iso8859-1 代码页,你可能需要 nls_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。
下次重启时,你应该只需输入一次容器解密密码。
(来源)