Linux 基础体系 · 第 61/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 磁盘加密:dm-crypt、LUKS、密钥槽、启动解锁和恢复

磁盘加密解决的是一个明确的问题:当存储介质脱离运行中的系统后,攻击者能否直接读取其中的数据。它不能自动解决运行中系统被入侵、用户已经登录、内存中的密钥被窃取、恶意固件或篡改过的启动链等问题。

在 Linux 中,常见的磁盘加密栈可以抽象为:

文件系统
  ↓
挂载点
  ↓
LVM / RAID / 其他块设备层
  ↓
LUKS 容器(保存加密元数据和密钥槽)
  ↓
dm-crypt 映射(内核设备映射器)
  ↓
底层分区、逻辑卷或整块磁盘

实际顺序取决于设计。例如:

文件系统 → LVM → LUKS → 分区
文件系统 → LUKS → 分区
文件系统 → LUKS → RAID

“先 RAID 后加密”和“先加密后 RAID”会影响故障可见性、校验和、重建方式以及密钥管理,不能混为一谈。


一、dm-crypt、LUKS 和 cryptsetup 分别是什么

1. dm-crypt:内核中的块设备加密目标

dm-crypt 是 Linux device-mapper 框架中的一个内核目标。它把一个底层块设备映射成另一个块设备:

/dev/sda2  ── dm-crypt ──> /dev/mapper/cryptroot

/dev/mapper/cryptroot 写入的明文扇区会被加密后写到 /dev/sda2;从底层读取的密文扇区会被解密后返回给上层文件系统。

dm-crypt 本身主要负责:

  1. 使用给定的加密密钥;
  2. 对块数据执行加密和解密;
  3. 将逻辑扇区映射到物理扇区;
  4. 通过 device-mapper 暴露一个新的块设备。

不负责

  • 保存人类输入的密码;
  • 管理多个密码;
  • 保存文件系统;
  • 判断密码是否正确;
  • 设计启动时如何询问密码;
  • 备份和恢复加密元数据。

因此,“dm-crypt 加密磁盘”和“LUKS 加密磁盘”不是严格同义词。LUKS 通常使用 dm-crypt 实现数据加密,但 LUKS 还定义了元数据、密钥槽和密码派生流程。

2. LUKS:加密卷的元数据格式和管理规范

LUKS,即 Linux Unified Key Setup,是一种磁盘加密格式。它在加密数据区域之外保存元数据,例如:

  • 卷的格式版本;
  • 加密算法和模式;
  • 数据区域起始位置;
  • 扇区大小;
  • 主密钥相关信息;
  • 密钥槽;
  • LUKS2 的元数据区域;
  • 可选的 token 信息,例如 TPM2、FIDO2 或 Clevis 相关配置。

LUKS1 和 LUKS2 都可以使用 dm-crypt 承载数据加密,但元数据能力不同:

  • LUKS1 结构简单,兼容性广;
  • LUKS2 支持更灵活的元数据布局、JSON 元数据、更多密钥槽、更现代的密码派生器以及 token 机制。

现代发行版通常优先创建 LUKS2,但具体默认值由 cryptsetup 和发行版版本决定,不能仅凭系统名称推断。

3. cryptsetup:用户空间管理工具

cryptsetup 是用户空间工具,负责把 LUKS 元数据和内核 dm-crypt 连接起来。常用操作包括:

cryptsetup luksFormat
cryptsetup luksOpen
cryptsetup luksClose
cryptsetup luksDump
cryptsetup luksAddKey
cryptsetup luksRemoveKey
cryptsetup luksHeaderBackup
cryptsetup status

大致流程如下:

cryptsetup luksOpen
    ↓
读取 LUKS 头部
    ↓
尝试用户提供的密码或密钥文件
    ↓
使用对应密钥槽解封卷密钥
    ↓
向内核创建 dm-crypt 映射
    ↓
/dev/mapper/<name> 出现

密码验证不是通过保存一个“密码哈希”完成的。LUKS 会使用密码派生算法从用户输入生成候选密钥,然后尝试解开某个密钥槽中的受保护材料;只有解开后得到正确的卷密钥,后续数据校验才会成功。


二、LUKS 的核心模型:一个卷密钥和多个密钥槽

1. 密码不是直接用于加密所有数据

设:

  • PP 是用户输入的密码;
  • SiS_i 是第 ii 个密钥槽的随机盐;
  • Ki=KDF(P,Si,参数)K_i = \mathrm{KDF}(P, S_i, \text{参数}) 是密码派生结果;
  • VV 是真正用于加密数据的卷密钥;
  • WiW_i 是用 KiK_i 包装或加密 VV 后得到的密钥槽内容。

可以把一个密钥槽抽象为:

Wi=WrapKi(V)W_i = \mathrm{Wrap}_{K_i}(V)

解锁时执行:

Ki=KDF(P,Si,参数)K_i = \mathrm{KDF}(P, S_i, \text{参数})

V=UnwrapKi(Wi)V = \mathrm{Unwrap}_{K_i}(W_i)

如果解包后的结果通过校验,则认为密码正确,并把 VV 交给 dm-crypt。

数据扇区的加密可以抽象为:

Cj=EV(IVj,Mj)C_j = E_V(\mathrm{IV}_j, M_j)

其中:

  • MjM_j 是第 jj 个明文扇区;
  • CjC_j 是密文扇区;
  • VV 是卷密钥;
  • IVj\mathrm{IV}_j 是由扇区位置等信息确定的初始化向量或 tweak;
  • EE 是所选的块加密模式。

这意味着修改密码通常不需要重新加密整个磁盘。只要用旧密码解出 VV,再用新密码生成的新 KiK_i 重新包装 VV 即可。

2. 密钥槽为什么有用

一个 LUKS 卷可以有多个密钥槽。例如:

  • 槽 0:系统管理员的密码;
  • 槽 1:另一名管理员的密码;
  • 槽 2:启动时使用的密钥文件;
  • 槽 3:自动解锁 token 的相关材料。

这些密钥槽通常指向同一个卷密钥 VV,而不是各自使用一把独立的数据加密密钥。因此:

  • 增加一个密钥槽不会重新加密所有数据;
  • 删除一个密钥槽不会影响其他槽;
  • 删除最后一个有效密钥槽会使卷无法正常解锁;
  • 获得任一有效解锁凭据的人,都能解密整个卷。

密钥槽不是“不同权限级别”。它们通常没有“只能访问某个目录”的能力。若需要不同用户只能访问不同数据,应使用文件系统权限、独立加密卷或应用层加密,而不是把多个密码放进同一个 LUKS 卷。

3. 使用 luksDump 查看槽状态

对现有设备执行:

sudo cryptsetup luksDump /dev/nvme0n1p3

输出通常会包括:

LUKS header information
Version:        2
Epoch:          ...
Metadata area:  ...
Keyslots:
  0: luks2
        Key:        ...
        Priority:   normal
        Cipher:     ...
        PBKDF:      argon2id
        ...
  1: luks2
        ...
Tokens:
Digests:

字段名称和详细布局会随 cryptsetup 版本、LUKS 版本和加密参数变化。应重点确认:

  • Version 是 LUKS1 还是 LUKS2;
  • 哪些 Keyslot 为已占用状态;
  • 使用了哪种 PBKDF;
  • 是否存在 token;
  • 数据区域起点和扇区大小;
  • 是否有 detached header。

luksDump 会显示元数据,但不会显示卷密钥本身。卷密钥不能通过该命令“打印出来”。


三、密码派生、密钥槽和暴力破解

1. 为什么不能只使用快速哈希

如果密码直接经过一次 SHA-256,就得到数据加密密钥,那么离线攻击者可以复制磁盘后高速尝试大量密码。密码通常具有低熵,攻击者不需要破解 AES,而只需要猜中密码。

LUKS 使用密码派生函数(KDF)增加每次尝试的成本。典型参数包括:

  • PBKDF2:通过大量哈希迭代增加计算成本;
  • Argon2id:同时消耗计算和内存,通常更能抑制大规模并行硬件攻击;
  • 时间成本;
  • 内存成本;
  • 并行度或线程参数;
  • 随机盐。

SiS_i 的作用不是让弱密码变强,而是让相同密码在不同卷或不同槽上产生不同的派生结果,并阻止预计算彩虹表。

2. 密钥槽验证的实际含义

解锁时,cryptsetup 不会知道用户输入“语义上是否正确”。它会对一个或多个槽执行:

  1. 读取槽的 KDF 参数和盐;
  2. 从输入密码计算候选 KiK_i
  3. 尝试解封 WiW_i
  4. 校验得到的卷密钥是否符合该槽的完整性校验;
  5. 成功后配置 dm-crypt。

因此,“密码错误”的表现通常是:

No key available with this passphrase.

这不代表数据区域已被扫描,也不代表文件系统损坏。它通常只表示没有任何密钥槽被该输入成功解锁。

3. 密码强度和 KDF 的边界

KDF 只能提高每次猜测的成本,不能替代高熵密码。一个短而常见的密码,即使使用 Argon2id,也可能被离线猜中;一个由密码管理器生成的随机长密码,通常更适合保护 LUKS 槽。

KDF 参数也存在工程取舍:

  • 参数过低,离线猜测更便宜;
  • 参数过高,启动和解锁等待时间增加;
  • 内存参数过高,低内存机器或早期启动环境可能失败;
  • 发行版升级、硬件变化和恢复环境差异可能影响可用性。

不能仅凭某个发行版当前的默认参数推断所有机器都使用相同配置。


四、创建、打开和关闭 LUKS 卷

以下示例使用一个文件创建回环设备,避免直接破坏真实磁盘。它要求系统安装 cryptsetup,并且有足够权限。

1. 创建实验设备

truncate -s 1G /tmp/luks-demo.img
sudo losetup --find --show /tmp/luks-demo.img

第二条命令可能输出:

/dev/loop0

记录实际输出。后文假设设备为 /dev/loop0

truncate 创建的是稀疏文件,文件逻辑大小为 1 GiB,但实际占用空间可能较少。对真实磁盘执行 luksFormat 是破坏性操作;对已有分区使用前必须确认设备名,不能凭 /dev/sdX 的猜测操作。

2. 初始化 LUKS

sudo cryptsetup luksFormat /dev/loop0

命令会要求确认,并提示输入密码。成功后,底层设备包含 LUKS 头部和加密数据区域;原有内容会被视为不可恢复。

如果需要明确指定格式,可以使用:

sudo cryptsetup luksFormat --type luks2 /dev/loop0

是否能使用某个 PBKDF、是否默认选择 Argon2id,以及参数如何设置,取决于 cryptsetup 版本和发行版策略。若目标是兼容旧版启动环境,应先验证该环境能否识别所选格式。

3. 打开映射

sudo cryptsetup open /dev/loop0 luks-demo

输入正确密码后,通常会创建:

/dev/mapper/luks-demo

检查:

lsblk -f
sudo cryptsetup status luks-demo

cryptsetup status 会显示映射类型、底层设备、扇区范围等信息。此时只是创建了块设备映射,还没有文件系统。

4. 创建文件系统并挂载

sudo mkfs.ext4 /dev/mapper/luks-demo
sudo mkdir -p /mnt/luks-demo
sudo mount /dev/mapper/luks-demo /mnt/luks-demo

sudo sh -c 'printf "secret data\n" > /mnt/luks-demo/example.txt'
sync
cat /mnt/luks-demo/example.txt

数据路径是:

example.txt
 → ext4 文件系统块
 → /dev/mapper/luks-demo
 → dm-crypt 加密
 → /dev/loop0
 → /tmp/luks-demo.img

文件系统不能直接创建在仍然保持 LUKS 头部的底层设备上。正确对象是打开后的 /dev/mapper/luks-demo

5. 关闭映射

sudo umount /mnt/luks-demo
sudo cryptsetup close luks-demo

只有在没有进程继续访问挂载点或映射时,关闭才是安全的。若 umount 报“target is busy”,应使用 findmntfuserlsof 定位占用者,而不是直接强制卸载。

实验结束后:

sudo losetup -d /dev/loop0
rm -f /tmp/luks-demo.img

五、LUKS 数据区、头部和加密边界

1. LUKS 头部通常不加密

LUKS 头部需要在解锁前被读取,所以其中的格式信息、槽布局和其他元数据通常是明文可见的。攻击者可能知道:

  • 这是一个 LUKS 卷;
  • 使用了 LUKS1 还是 LUKS2;
  • 槽和 KDF 参数;
  • 数据区大致位置;
  • UUID 和部分配置。

但知道这些信息不等于拥有卷密钥。真正的数据区域仍由 dm-crypt 加密。

文件名、目录结构、文件大小和空闲空间的可观察性取决于加密边界。若文件系统在 LUKS 内,文件系统元数据通常也在加密范围内;若 /boot 位于未加密分区,启动文件和内核可能对攻击者可见。

2. 整盘加密并不等于所有内容都加密

常见布局可能是:

EFI System Partition   未加密
/boot                   可能未加密
LUKS                    加密根文件系统
swap                    加密或位于加密卷内

EFI 分区通常需要固件读取,因此常保持明文。未加密 /boot 会暴露内核和 initramfs,也会增加启动链被离线篡改的风险。是否能通过 Secure Boot、签名内核、测量启动和 TPM 绑定来减轻该风险,取决于整套启动验证链,而不是 LUKS 单独保证。

3. 关闭映射后才接近“静态数据保护”

系统运行时,打开的 LUKS 映射会把明文块设备提供给内核。拥有足够权限的进程通常可以通过文件系统、块设备或内存访问数据。攻击者如果已经取得 root 权限,LUKS 不会自动阻止其读取已挂载文件。

因此,磁盘加密的主要边界是:

机器关机或卷未解锁:保护较强
卷已解锁且系统运行:保护转移到主机权限和运行时安全

内存转储、休眠文件、交换区、备份和日志也必须纳入威胁模型。密码输入只在启动时出现,并不意味着密钥之后不会存在于内核内存中。


六、密钥槽的添加、轮换和删除

1. 添加新密码

先确认卷处于关闭或可安全管理状态,然后执行:

sudo cryptsetup luksAddKey /dev/nvme0n1p3

该命令通常先要求一个已有有效密码,再要求输入新密码。其逻辑是:

旧密码
  ↓
解出卷密钥 V
  ↓
新密码 + 新盐 + KDF
  ↓
创建新的密钥槽,包装同一个 V

添加完成后,可以使用新密码测试打开。不要只测试命令是否返回成功,还应验证映射中的文件系统和数据。

2. 修改或删除密码

修改密码常见的安全做法是添加新密码、验证新密码、再删除旧槽:

sudo cryptsetup luksAddKey /dev/nvme0n1p3
sudo cryptsetup luksDump /dev/nvme0n1p3
sudo cryptsetup luksKillSlot /dev/nvme0n1p3 1

槽编号必须根据实际 luksDump 输出确认,不能假设旧密码一定在槽 1。luksKillSlot 会删除指定槽,错误地删除唯一有效槽会使卷无法解锁。

如果只是想删除某个已知密码,也可以使用:

sudo cryptsetup luksRemoveKey /dev/nvme0n1p3

它会要求提供要删除的密码,并由工具定位对应槽。不同版本的行为和安全检查可能存在差异,生产环境应先阅读本机 cryptsetup 手册并验证备份。

3. 正在使用的卷也要谨慎修改头部

添加或删除密钥槽主要修改 LUKS 元数据,不会正常重写整个数据区,但这仍是关键操作。若设备存在 I/O 错误、掉电风险或头部损坏,应先安排维护窗口和可靠的头部备份。

密钥槽删除不是“撤销已经泄露的数据”。如果攻击者已经获得卷密钥 VV,删除密码槽不会让旧数据重新加密;它只阻止通过该槽中的密码继续解出 VV


七、头部备份:恢复的关键与危险

1. 为什么头部如此重要

LUKS 头部保存了:

  • 如何解释数据区域;
  • 使用什么加密参数;
  • 密钥槽在哪里;
  • 如何用密码解封卷密钥。

如果头部损坏,即使加密数据区字节完全未变,也可能无法找到并解开卷密钥。因此,LUKS 头部备份是恢复策略的核心部分。

执行:

sudo cryptsetup luksHeaderBackup /dev/nvme0n1p3 \
  --header-backup-file /secure-backup/nvme0n1p3.luks-header

备份文件应存放在与原盘不同的安全位置,并限制权限:

sudo chmod 600 /secure-backup/nvme0n1p3.luks-header

2. 头部备份本身是敏感材料

头部备份通常包含密钥槽中的加密卷密钥材料。它不一定能单独解密数据,因为还需要密码或其他解锁凭据;但如果攻击者同时获得了头部备份和一个弱密码,风险会显著增加。

所以头部备份不能随意上传公共网盘,也不能和密码写在同一个明文工单中。它应当:

  • 加密保存;
  • 控制访问权限;
  • 标记对应设备和创建时间;
  • 定期验证能否读取;
  • 在设备更换或重建密钥槽后重新备份。

3. 恢复头部不是普通文件复制

恢复命令类似:

sudo cryptsetup luksHeaderRestore /dev/nvme0n1p3 \
  --header-backup-file /secure-backup/nvme0n1p3.luks-header

这是高风险操作。它会覆盖目标设备的 LUKS 头部。如果备份对应的是另一块设备、另一时刻、另一种布局,可能导致当前可用的密钥槽消失,甚至让数据区无法解释。

正确的恢复顺序应包括:

  1. 确认目标设备序列号和分区;
  2. 确认备份文件来源;
  3. 保存当前头部,以便回滚;
  4. 在维护环境中执行恢复;
  5. 用已知密码尝试 luksOpen
  6. 打开后以只读方式检查文件系统和关键文件;
  7. 确认恢复结果后再进行写操作。

如果只是希望检查头部备份是否可读,不能把它直接恢复到生产盘上。应在隔离设备或完整块设备副本上测试。

4. Detached header

LUKS 可以把头部放在独立文件或独立设备中,数据设备本身不包含普通 LUKS 头部。例如:

sudo cryptsetup luksFormat /dev/nvme0n1p3 \
  --header /secure/luks/root.header

打开时也必须指定同一个头部:

sudo cryptsetup open /dev/nvme0n1p3 cryptroot \
  --header /secure/luks/root.header

这会改变恢复模型:

  • 数据设备和头部必须同时可用;
  • 头部丢失会导致数据无法解锁;
  • 头部与数据设备分离可能降低某些场景下的可识别性;
  • 但它不会替代安全备份;
  • 头部文件必须有自己的访问控制和备份。

八、启动解锁:从固件到根文件系统

1. 启动解锁为什么发生在 initramfs

根文件系统位于 LUKS 内时,内核刚启动时还看不到真正的根文件系统。启动过程通常是:

flowchart TD
    A[固件 UEFI/BIOS] --> B[引导程序]
    B --> C[内核与 initramfs]
    C --> D[识别底层块设备]
    D --> E[读取 LUKS 头部]
    E --> F[询问密码或使用自动解锁凭据]
    F --> G[创建 dm-crypt 映射]
    G --> H[激活 LVM 或组装 RAID]
    H --> I[挂载根文件系统]
    I --> J[切换到真实 root filesystem]
    J --> K[启动 systemd 与用户空间服务]

initramfs 是启动早期使用的临时根文件系统。它必须包含:

  • 存储控制器驱动;
  • dm-crypt 支持;
  • cryptsetup 或 systemd-cryptsetup 相关组件;
  • LUKS 元数据识别能力;
  • 必要的键盘、网络或 TPM/FIDO2 支持;
  • 根文件系统所需的其他模块。

不同发行版使用的实现不同。例如 Debian/Ubuntu 常见 initramfs-tools,Fedora/RHEL 系列常见 dracut,systemd 发行版也可能使用 systemd 的 cryptsetup 集成。不能直接把某个发行版的配置文件路径套用到另一发行版。

2. /etc/crypttab 的作用

常见配置形态是:

cryptroot UUID=<luks-uuid> none luks,discard

字段通常表示:

映射名   加密设备       密钥来源   选项

例如先查询 LUKS UUID:

sudo blkid /dev/nvme0n1p3

可能输出:

/dev/nvme0n1p3: UUID="..." TYPE="crypto_LUKS"

crypttab 中应使用 LUKS 设备的 UUID,而不是文件系统 UUID。两者属于不同层:

LUKS UUID       → 用于找到加密容器
文件系统 UUID   → 用于挂载已解密的文件系统

根文件系统还需要在 /etc/fstab 或生成的启动配置中引用解锁后的文件系统 UUID。完成配置后,通常需要重新生成 initramfs,例如某些发行版使用:

sudo update-initramfs -u

另一些发行版使用:

sudo dracut --force

命令必须以本机发行版文档为准;错误生成 initramfs 可能导致下一次重启无法解锁根卷。

3. 启动密码提示失败的诊断路径

如果启动时提示密码错误,应区分以下故障:

  1. 密码确实不匹配;
  2. initramfs 找错了设备;
  3. LUKS UUID 配置错误;
  4. 键盘布局不同,例如启动环境使用 US 布局;
  5. LUKS2 所需功能未被早期启动环境支持;
  6. 底层设备驱动未加载;
  7. LVM 或 RAID 在解锁后没有激活;
  8. 根文件系统 UUID 或挂载参数错误。

在救援 shell 中可以检查:

lsblk -f
blkid
cryptsetup luksDump /dev/正确的设备
cryptsetup open /dev/正确的设备 test-root

如果手工 cryptsetup open 成功,而正常启动失败,问题通常不在密码或密钥槽,而在 initramfs、crypttab、设备发现或后续挂载阶段。


九、自动解锁:密钥文件、TPM2、FIDO2 和网络

1. 密钥文件不是“更安全的密码”

可以使用随机密钥文件添加一个槽:

sudo install -m 600 /dev/null /root/luks.key
sudo dd if=/dev/urandom of=/root/luks.key bs=64 count=1 status=none

sudo cryptsetup luksAddKey /dev/nvme0n1p3 /root/luks.key

打开时:

sudo cryptsetup open /dev/nvme0n1p3 cryptroot \
  --key-file /root/luks.key

如果密钥文件和加密磁盘放在同一个未保护的介质上,攻击者可以同时拿走二者,自动解锁就失去意义。密钥文件适合放在受保护的外部介质、硬件设备或经过可靠访问控制的启动环境中,但必须设计丢失和轮换流程。

2. TPM2 自动解锁的真实边界

TPM2 可以把解锁材料绑定到某些平台状态,例如 PCR 值。常见目标是:

预期的启动链和平台状态
  ↓
TPM 允许释放密钥材料
  ↓
initramfs 使用材料解锁 LUKS

这不是“TPM 直接存储 LUKS 卷密钥并保证绝对安全”。安全性依赖于:

  • 哪些 PCR 被绑定;
  • Secure Boot 是否启用并正确验证;
  • 启动组件和 initramfs 是否可信;
  • 密钥材料如何封装;
  • 系统升级后 PCR 是否变化;
  • 是否保留人工恢复密码。

启动链变化可能导致 TPM 自动解锁失败,这是预期的安全行为,而不一定是数据损坏。生产部署必须保留至少一个经过验证的人工恢复槽,否则一次固件、引导程序或 initramfs 变更可能把管理员锁在系统外。

3. FIDO2、Clevis/Tang 和网络解锁

FIDO2 等硬件认证器可以参与密钥槽或 token 解锁;Clevis/Tang 常用于网络绑定解锁。它们的集成方式依赖 cryptsetup、systemd、发行版工具包及 initramfs 配置,不能仅凭 LUKS 格式本身推断可用性。

网络解锁还引入新的故障和攻击面:

  • 启动阶段需要网卡和网络配置;
  • DNS、DHCP、路由和时间可能尚未可用;
  • 网络服务可能被阻断;
  • Tang 服务器不可达会阻止启动;
  • 自动解锁凭据的信任根必须单独保护。

自动解锁的目标通常是减少人工输入,不是消除密钥管理。人工密码、头部备份和灾难恢复介质仍需存在,并且不能与自动解锁设备一起保存。


十、LUKS 与 LVM、RAID、分区的组合

1. 先 LUKS 后 LVM

典型布局:

分区 → LUKS → LVM PV → VG → LV → 文件系统

优点是一个 LUKS 卷保护整个 LVM 区域,多个逻辑卷共享同一个解锁边界。启动时先解锁 LUKS,再激活 LVM。

扩容路径通常是:

扩大底层分区
  → 扩大 LUKS 数据区
  → 扩大 PV
  → 扩大 LV
  → 扩大文件系统

每一步的工具和顺序都很重要。例如,扩大分区并不会自动扩大 LUKS 数据区;扩大 LV 也不会自动扩大所有文件系统。执行前应记录:

lsblk
sudo cryptsetup status cryptname
sudo pvs
sudo vgs
sudo lvs
df -hT

2. 先 LVM 后 LUKS

另一种布局是:

分区 → LVM PV → LV → LUKS → 文件系统

这种方式可以给不同 LV 使用不同密码或独立加密边界,但每个 LUKS 卷都需要单独管理。根、交换区、数据库和备份卷可以拥有不同生命周期,但启动和监控复杂度会增加。

3. RAID 与加密的顺序

常见的两种顺序:

分区 → RAID → LUKS → 文件系统
分区 → LUKS → RAID → 文件系统

前者通常让 RAID 直接看到底层成员设备,再由上层统一加密;后者让每个成员先成为独立加密设备,再组装 RAID。两者在启动顺序、密钥输入次数、故障定位和数据重建方面不同。

RAID 不是备份,LUKS 也不是备份。RAID 主要提高可用性或提供冗余,无法防止误删、勒索软件、错误命令和同步破坏。


十一、恢复场景:密码丢失、头部损坏和文件系统损坏

1. 忘记密码但有另一个有效槽

如果仍有其他有效密码或密钥文件:

sudo cryptsetup open /dev/nvme0n1p3 cryptroot

打开后,可以添加新密码:

sudo cryptsetup luksAddKey /dev/nvme0n1p3

然后删除已失效或已泄露的槽。此过程不需要重新加密整个数据区。

2. 所有密码都丢失

如果没有任何有效密码、密钥文件、硬件 token 或可用的密钥托管,LUKS 的设计目标就是无法恢复卷内明文。头部备份不能绕过密码;它只恢复元数据和密钥槽材料。

这也是为什么恢复流程必须在设备故障前建立,而不是在密码丢失后临时猜测。对弱密码进行离线猜测是技术上可能的攻击路径,但不是可靠恢复方案,也不应把 KDF 当作备份替代品。

3. 头部损坏但有头部备份

先不要运行会写入原盘的修复命令。应:

  1. 对原设备做块级副本或至少保留只读证据;
  2. 核对头部备份对应的设备;
  3. 备份当前损坏头部;
  4. 恢复已验证的 LUKS 头部;
  5. 用已知凭据打开;
  6. 先只读检查文件系统。

打开 LUKS 成功只说明卷密钥和元数据恢复成功,不代表文件系统一定完好。后续还可能需要针对 ext4、XFS、Btrfs 等文件系统使用各自的检查和恢复工具;这些工具的在线/离线要求不同,不能在未确认挂载状态时直接运行。

4. LUKS 打开失败和文件系统损坏的区别

下面两类错误处于不同层:

No key available with this passphrase.

通常表示 LUKS 解锁阶段失败。

而:

mount: wrong fs type, bad superblock, ...

可能表示:

  • 打开的不是预期文件系统;
  • LVM 或 RAID 层次不完整;
  • 文件系统元数据损坏;
  • 使用了错误的映射名或设备;
  • 文件系统驱动或挂载参数错误。

诊断时应逐层验证:

lsblk -f
sudo cryptsetup status cryptroot
sudo pvs
sudo vgs
sudo lvs
sudo blkid /dev/mapper/cryptroot

不要因为挂载失败就立即重新格式化。格式化会覆盖文件系统元数据,可能使后续恢复更加困难。


十二、备份、擦除和 TRIM 的边界

1. 加密备份仍需要密钥管理

将数据备份到另一块磁盘时,可以:

  • 对备份磁盘建立独立 LUKS 卷;
  • 使用备份软件自身的加密;
  • 对文件做应用层加密。

复制原卷的 LUKS 头部并不等于复制了一个可独立管理的安全备份。若复制的是完整块设备,必须明确它是否包含相同卷密钥、相同 UUID 以及相同的密钥槽。两个设备共享同一卷密钥会使密钥轮换和资产识别变得复杂。

2. 删除文件不等于安全擦除

在已加密卷中删除文件,通常只是修改文件系统元数据,旧数据块可能仍存在于空闲区域。若卷始终保持安全加密,攻击者没有卷密钥时,这些旧块仍是密文;但如果卷密钥后来泄露,历史密文可能仍可分析。

直接覆盖 SSD 也不一定可靠,因为闪存控制器可能进行磨损均衡。设备自带的安全擦除能力、加密擦除策略和供应商实现需要单独评估。

3. discard/TRIM 的信息泄露

在 SSD 上启用 discard 可能让底层设备知道哪些逻辑块已经被释放。它有助于回收和性能维护,但会泄露空闲块模式。crypttab 中的 discard 是否适合使用,取决于威胁模型和存储设备特性。

对于某些加密模式,discard 还可能造成额外的模式信息暴露。它不是纯粹的性能开关,应在安全要求和设备支持之间权衡。


十三、完整的检查和操作流程

对一台已经存在的 LUKS 设备,较稳妥的检查顺序是:

lsblk -o NAME,PATH,SIZE,TYPE,FSTYPE,UUID,MOUNTPOINTS
sudo blkid
sudo cryptsetup luksDump /dev/nvme0n1p3
sudo cryptsetup status cryptroot

若要确认密钥槽和密码是否可用,可以在维护窗口中:

sudo cryptsetup open --readonly /dev/nvme0n1p3 crypt-test
sudo cryptsetup status crypt-test
sudo cryptsetup close crypt-test

--readonly 的具体效果和限制取决于调用层次;它不能代替对文件系统和底层设备的全面只读保护。若测试的是生产卷,仍应避免让上层文件系统执行写入。

修改密钥槽前:

sudo cryptsetup luksHeaderBackup /dev/nvme0n1p3 \
  --header-backup-file /secure-backup/nvme0n1p3-$(date +%F).header

sudo cryptsetup luksDump /dev/nvme0n1p3

修改后再次检查:

sudo cryptsetup luksDump /dev/nvme0n1p3

并使用新凭据实际打开、挂载和读取校验文件。仅看到新槽出现在 luksDump 中,不足以证明启动环境也能使用该槽。


十四、常见误解和反例

误解一:LUKS 会隐藏所有磁盘活动

反例是系统已经启动且卷已解锁。此时 root 权限进程可以读取挂载目录,攻击者也可能从内存、交换区或备份中获得敏感信息。LUKS 主要保护离线存储,不是运行时隔离机制。

误解二:删除密码后,旧数据就被重新加密

反例是删除密钥槽只删除了“通过该密码解封卷密钥”的路径。数据扇区没有重新加密,卷密钥 VV 也没有改变。若旧密码对应的卷密钥此前已经泄露,删除槽不能撤销泄露。

误解三:RAID 可以替代备份

反例是管理员误执行:

rm -rf /重要目录

RAID 会把删除动作同步到冗余成员,LUKS 也会正常加密这个删除后的状态。只有独立、可恢复的备份才能处理这类问题。

误解四:头部备份可以单独恢复数据

反例是头部备份通常仍需要有效密码或其他解锁凭据。它解决的是“头部丢失”,不是“密钥丢失”。

误解五:启动自动解锁后就不需要恢复密码

反例是 TPM PCR 因固件或引导链升级发生变化,或者 TPM 主板损坏。若没有人工恢复槽,自动解锁机制本身可能成为单点故障。

误解六:看到 /dev/mapper/name 就说明已经安全

反例是映射名称只是设备路径。是否使用了正确设备、正确 LUKS 卷、正确文件系统以及正确挂载选项,仍需通过 lsblkcryptsetup status、UUID 和数据校验确认。


十五、生产环境中的取舍

LUKS 的安全性不是单个命令产生的,而是由以下链条共同决定:

高熵解锁凭据
  + 合适的 KDF 参数
  + 可信的启动链
  + 正确的 initramfs 配置
  + 安全的密钥槽管理
  + 独立的头部备份
  + 可验证的离线备份
  + 经过演练的恢复流程

对于服务器,应明确以下状态:

  • 关机时是否要求人工输入密码;
  • 无人值守重启时是否允许 TPM 或硬件 token 自动解锁;
  • 远程解锁依赖哪些网络和服务;
  • 失去 TPM、FIDO2 设备或网络后如何恢复;
  • /boot 和 EFI 分区如何保护;
  • 备份是否单独加密以及谁拥有解锁凭据;
  • 密钥槽变更和头部恢复是否有审计记录。

磁盘加密还应与主机安全加固配合:最小化运行服务和权限,正确配置 SELinux 或 AppArmor,启用审计与漏洞治理,并保护 SSH 等远程管理入口。OpenSSH 的密钥认证和主机验证可以保护远程管理会话,但不能替代 LUKS 解锁凭据;如果通过 SSH 实现远程启动解锁,还必须额外保护早期用户空间、网络路径和临时凭据。

最后,必须把“成功启动一次”与“具备恢复能力”区分开。真正可用的 LUKS 部署至少应能回答四个问题:

  1. 正常密码或自动机制如何解锁?
  2. 某个管理员离职或凭据泄露后如何轮换?
  3. LUKS 头部损坏后如何恢复?
  4. 主机、磁盘、启动介质或自动解锁硬件损坏后,如何在另一环境验证并恢复数据?

如果这些问题没有经过实际演练,系统只是“配置了加密”,还没有形成可验证的存储安全方案。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。