土法炼钢兴趣小组的算法知识备份

【存储工程】存储加密工程

文章导航

分类入口
storage
标签入口
#encryption#dm-crypt#luks#fscrypt#tde#aes-xts#aes-ni#key-management

目录

一块磁盘从服务器上拔下来,接到另一台机器上就能直接读取全部数据。一块 SSD 退役送去销毁,中途被截获,上面的用户数据一览无余。一台云主机的底层宿主机被攻破,租户的虚拟磁盘镜像文件直接暴露。这些场景有一个共同点:数据在存储介质上以明文形式存在,物理访问等于逻辑访问。

静态数据加密(Data-at-Rest Encryption)就是为了切断这条路径——即使攻击者拿到了存储介质的物理访问权限,没有密钥也无法读取数据内容。这个需求听起来直白,但工程实现上要回答的问题远不止”用 AES 加密一下”这么简单:加密应该在哪一层做?用什么模式?密钥放在哪里?轮转怎么做?对性能的影响有多大?

本文从威胁模型和合规要求出发,依次拆解块级加密(Block-Level Encryption)、文件级加密(File-Level Encryption)和应用级加密(Application-Level Encryption)三种架构,分析各自的加密算法选型,深入讨论密钥管理与轮转的工程设计,最后用性能实测数据回答”加密到底慢多少”这个关键问题。代码和命令基于 Linux 6.x 内核、cryptsetup 2.7.x、fscrypt 0.3.x,数据库部分基于 MySQL 8.0 和 PostgreSQL 16。


一、静态数据加密的工程动机

合规要求:不是可选项

静态数据加密在许多行业已经从”最佳实践”变成了硬性合规要求:

这些合规要求的共同特点是:它们不关心你用什么算法、在哪一层实现,但要求你能证明数据在存储介质上不是明文。审计时需要提供的证据包括:加密方案文档、密钥管理策略、轮转记录和访问日志。

威胁模型:加密到底防什么

静态数据加密的防护边界需要明确。它能防护的威胁:

威胁场景 加密是否有效 说明
磁盘被盗或丢失 有效 攻击者无法读取介质上的密文
退役硬件数据残留 有效 即使数据未擦除,没有密钥也无法解读
快照/备份泄露 有效 加密后的快照/备份同样是密文
多租户隔离失效 有效 宿主机层面的越权访问拿到的是密文
操作系统被入侵后的内存读取 无效 密钥在内存中,攻击者可以提取
应用层 SQL 注入 无效 应用通过正常路径读数据,加密层已经解密
特权管理员滥用 部分有效 取决于密钥管理架构是否与管理员权限分离

关键认知:静态数据加密保护的是”数据脱离系统控制后”的安全性,不保护运行时(Runtime)的数据访问。如果攻击者已经拿到了操作系统的 root 权限,静态加密基本失效,因为密钥通常在内核内存中。

加密粒度的选择

在决定加密方案之前,还需要回答一个关键问题:加密的粒度是什么?

粒度越细,灵活性越高,但管理复杂度也越高。下一节从架构层面展开这个选择。


二、加密层次架构

存储加密可以在存储栈的不同层实现,每一层的定位和特性不同。下面这张图展示了三种主要加密层次在 Linux 存储栈中的位置:

加密层次在存储栈中的位置

┌─────────────────────────────────────────────────────────────────┐
│                         应用层                                   │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  应用级加密(Application-Level Encryption)                │  │
│  │  MySQL TDE / PostgreSQL TDE / 应用自行加密                │  │
│  │  加密对象:表空间、列、字段                                │  │
│  └───────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│                       文件系统层                                 │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  文件级加密(File-Level Encryption)                       │  │
│  │  fscrypt(ext4/f2fs)/ eCryptfs                           │  │
│  │  加密对象:文件内容 + 文件名                               │  │
│  └───────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│                       块设备层                                   │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  块级加密(Block-Level Encryption)                        │  │
│  │  dm-crypt / LUKS                                          │  │
│  │  加密对象:整个块设备的所有扇区                             │  │
│  └───────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│                    物理存储设备                                   │
│  HDD / SSD / NVMe                                               │
│  (部分设备支持自加密驱动器 SED)                                 │
└─────────────────────────────────────────────────────────────────┘

三种层次的对比

维度 块级加密 文件级加密 应用级加密
加密粒度 整个块设备 单个文件/目录 表空间/列/字段
对文件系统的影响 无(文件系统不感知加密) 深度集成或堆叠 无(数据库内部处理)
多用户/多密钥 通常一个设备一把密钥 每个目录可以不同密钥 每个表空间可以不同密钥
元数据保护 全部加密(包括文件系统元数据) 文件内容和文件名加密,目录结构可见 只加密数据页,索引结构通常明文
性能开销 最低(内核态批量处理) 中等 较高(应用层加解密)
备份兼容性 备份密文,恢复需要密钥 可以选择性备份加密或明文 备份工具需要数据库配合
典型实现 dm-crypt/LUKS, BitLocker fscrypt, eCryptfs MySQL TDE, PostgreSQL TDE
适用场景 服务器全盘保护、云盘加密 多租户共享存储、移动设备 数据库合规、细粒度访问控制

选择哪一层取决于威胁模型和运维约束。块级加密最简单、最透明,适合”只要磁盘不被偷就行”的场景;文件级加密适合需要为不同用户或目录使用不同密钥的场景;应用级加密最灵活,但侵入性也最强。实际生产中,这三层可以组合使用——例如底层用 LUKS 做全盘加密,数据库层再用 TDE 加密敏感表空间。


三、块级加密:dm-crypt 与 LUKS

dm-crypt 的内核架构

dm-crypt 是 Linux 内核的设备映射(Device Mapper)框架的一个目标(Target)。它在块设备层工作,把一个加密的块设备映射为一个明文的虚拟块设备。上层文件系统和应用对加密完全无感知。

dm-crypt 的数据路径如下:

  1. 应用发起写请求,数据通过文件系统到达块设备层。
  2. dm-crypt 拦截 I/O 请求,对数据进行加密。
  3. 加密后的数据写入底层物理设备。
  4. 读取时反向操作:从物理设备读取密文,dm-crypt 解密后返回给上层。

内核中的关键数据结构是 struct crypt_config(Linux 6.x,drivers/md/dm-crypt.c),它保存了加密算法、密钥、初始向量(IV)生成方式等配置。dm-crypt 使用内核加密 API(Crypto API)来调用具体的加密算法实现,这意味着它可以自动利用 AES-NI 等硬件加速。

LUKS 格式

LUKS(Linux Unified Key Setup)是 dm-crypt 之上的一个标准化密钥管理格式。裸用 dm-crypt 需要用户自己管理密钥,而 LUKS 定义了一个标准的磁盘头部格式来存储加密元数据和密钥槽(Key Slot)。

LUKS2 头部结构(cryptsetup 2.x):

LUKS2 磁盘布局

偏移量          内容
──────────────────────────────────────────
0x0000          主头部(Primary Header)
                ├── 魔数:LUKS\xba\xbe
                ├── 版本:0x0002
                ├── 头部大小
                ├── JSON 元数据区域
                │   ├── 加密算法和模式
                │   ├── 密钥槽配置(最多 32 个)
                │   └── 段描述(加密区域范围)
                └── 校验和
──────────────────────────────────────────
头部备份        二进制密钥材料区域
                ├── Keyslot 0 的密钥材料
                ├── Keyslot 1 的密钥材料
                └── ...
──────────────────────────────────────────
数据区域        加密后的用户数据
                (按扇区加密,每个扇区 512B 或 4096B)
──────────────────────────────────────────

LUKS2 相比 LUKS1 的改进:

实战:使用 LUKS2 加密一个块设备

以下演示在一个空块设备上创建 LUKS2 加密卷并挂载使用。

# 环境:Linux 6.x, cryptsetup 2.7.x
# 警告:以下操作会销毁目标设备上的所有数据

# 1. 创建 LUKS2 加密卷
# 使用 AES-XTS 模式,256 位密钥(实际使用 512 位,因为 XTS 需要两把子密钥)
# KDF 使用 Argon2id
sudo cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --key-size 512 \
    --hash sha256 \
    --pbkdf argon2id \
    --pbkdf-memory 1048576 \
    --pbkdf-parallel 4 \
    --pbkdf-force-iterations 4 \
    /dev/sdb

# 2. 打开加密卷,创建明文映射设备
sudo cryptsetup open /dev/sdb encrypted_data

# 此时 /dev/mapper/encrypted_data 就是明文块设备
# 3. 在明文设备上创建文件系统
sudo mkfs.ext4 /dev/mapper/encrypted_data

# 4. 挂载使用
sudo mount /dev/mapper/encrypted_data /mnt/secure

# 5. 查看加密状态
sudo cryptsetup status encrypted_data

输出类似:

/dev/mapper/encrypted_data is active and is in use.
  type:    LUKS2
  cipher:  aes-xts-plain64
  keysize: 512 bits
  key location: keyring
  device:  /dev/sdb
  sector size:  512
  offset:  32768 sectors
  size:    209682432 sectors
  mode:    read/write

LUKS 密钥槽管理

LUKS 支持最多 32 个密钥槽(LUKS2),每个槽可以存储一个独立的口令(Passphrase)或密钥文件,它们都能解锁同一个主密钥(Master Key)。这个设计允许:

# 添加一个新的密钥槽(需要提供现有口令来验证)
sudo cryptsetup luksAddKey /dev/sdb

# 删除指定槽位
sudo cryptsetup luksKillSlot /dev/sdb 1

# 查看密钥槽状态
sudo cryptsetup luksDump /dev/sdb

luksDump 输出的 JSON 元数据中可以看到每个密钥槽的 KDF 参数:

{
  "keyslots": {
    "0": {
      "type": "luks2",
      "key_size": 64,
      "kdf": {
        "type": "argon2id",
        "time": 4,
        "memory": 1048576,
        "cpus": 4
      },
      "af": {
        "type": "luks1",
        "stripes": 4000
      }
    }
  }
}

dm-crypt 的 IV 生成模式

块级加密需要为每个扇区生成不同的初始向量(Initialization Vector, IV),否则相同的明文扇区会产生相同的密文,泄露数据模式。dm-crypt 支持多种 IV 生成器:

IV 模式 原理 安全性 适用场景
plain IV = 扇区号的低 32 位 低(扇区号超过 2^32 后 IV 重复) 不推荐
plain64 IV = 扇区号的完整 64 位 中(满足大多数场景) 默认推荐
essiv IV = E(sector_number, hash(key)) 高(隐藏扇区号与 IV 的关系) 需要额外防护水印攻击时
benbi IV = 大端序扇区号右移 兼容某些硬件

plain64 是目前的默认选择。essiv 提供更强的安全性,但在使用 AES-XTS 模式时不必要,因为 XTS 本身已经将扇区号融入加密过程。

扇区大小的影响

传统上 dm-crypt 使用 512 字节扇区大小。现代 NVMe 设备通常使用 4096 字节(4K)物理扇区。cryptsetup 2.x 支持通过 --sector-size 参数设置加密扇区大小:

sudo cryptsetup luksFormat --type luks2 \
    --cipher aes-xts-plain64 \
    --key-size 512 \
    --sector-size 4096 \
    /dev/nvme0n1p3

使用 4K 扇区大小的好处:

但也有限制:4K 扇区要求上层文件系统的最小 I/O 单元不小于 4K,这在 ext4 默认配置下是满足的。


四、文件级加密:fscrypt 与 eCryptfs

为什么需要文件级加密

块级加密对整个设备”一刀切”——要么全加密,要么不加密。在以下场景中,文件级加密更合适:

fscrypt 的架构

fscrypt 是 Linux 内核原生的文件级加密框架(Linux 4.1+ 引入,5.4+ 版本趋于成熟)。它直接集成在文件系统代码中(目前支持 ext4、f2fs 和 UBIFS),在文件系统写入磁盘前加密数据,读取时解密。

fscrypt 的核心设计:

fscrypt 的密钥派生流程:

fscrypt 密钥派生

用户口令
    │
    ▼
┌────────────────────┐
│  HKDF-SHA512       │  从主密钥派生出 per-file key
│  salt: 文件 nonce  │
└────────┬───────────┘
         │
         ▼
┌────────────────────┐
│  Per-File Key      │  每个文件的加密密钥不同
│  256 bits          │
└────────┬───────────┘
         │
    ┌────┴────┐
    ▼         ▼
 文件内容   文件名
 AES-XTS   AES-CTS

fscrypt 实战

# 环境:Linux 6.x, fscrypt 0.3.x, ext4 文件系统
# 前提:文件系统在挂载时启用了 encrypt 特性

# 1. 检查文件系统是否支持加密
sudo tune2fs -l /dev/sda2 | grep -i encrypt
# 输出应包含:Filesystem features: ... encrypt ...

# 如果不支持,启用加密特性(需要卸载)
sudo tune2fs -O encrypt /dev/sda2

# 2. 初始化 fscrypt 元数据
sudo fscrypt setup /
sudo fscrypt setup /mnt/data

# 3. 创建加密目录
mkdir /mnt/data/secrets
fscrypt encrypt /mnt/data/secrets
# 交互式选择保护方式:login passphrase / custom passphrase / raw key

# 4. 验证加密状态
fscrypt status /mnt/data/secrets

加密后的效果:

# 已解锁状态:正常读写
echo "sensitive data" > /mnt/data/secrets/test.txt
cat /mnt/data/secrets/test.txt
# 输出:sensitive data

# 锁定目录
fscrypt lock /mnt/data/secrets

# 锁定后:文件名变成加密哈希,内容不可读
ls /mnt/data/secrets/
# 输出类似:,Dv9SPBqdMCEXmGn0yF0tKg
cat /mnt/data/secrets/,Dv9SPBqdMCEXmGn0yF0tKg
# 输出:乱码(密文)

# 重新解锁
fscrypt unlock /mnt/data/secrets

fscrypt v2 策略

fscrypt v1 策略为每个文件从主密钥直接派生 per-file key,存在一个问题:非特权用户可以添加任意密钥到内核密钥环(Keyring),可能导致密钥混淆攻击。

fscrypt v2 策略(Linux 5.4+)的改进:

# 使用 v2 策略创建加密目录
fscrypt encrypt --policy-version=2 /mnt/data/secrets

eCryptfs:堆叠式文件级加密

eCryptfs 是另一种 Linux 文件级加密方案,它以堆叠文件系统(Stacked Filesystem)的方式实现:eCryptfs 挂载在一个底层文件系统之上,对写入的文件进行加密后存储到底层文件系统,读取时解密。

eCryptfs 与 fscrypt 的对比:

维度 fscrypt eCryptfs
实现方式 内联在文件系统代码中 堆叠文件系统
支持的底层文件系统 ext4, f2fs, UBIFS 任意(堆叠在上层)
性能 更好(少一层间接调用) 较差(双层文件系统开销)
元数据存储 文件系统扩展属性 文件头部
内核维护状态 积极维护 功能冻结(Linux 6.x 中已标记为候选移除)
Android 使用 是(FBE 方案) 否(已废弃)

当前的工程建议:新项目使用 fscrypt,不要使用 eCryptfs。eCryptfs 已不再接受新特性,内核社区的长期方向是 fscrypt。Ubuntu 从 21.04 开始也将主目录加密方案从 eCryptfs 切换到了 fscrypt。


五、应用级加密:透明数据加密

TDE 的基本原理

透明数据加密(Transparent Data Encryption, TDE)在数据库引擎内部实现,对应用程序透明——应用通过 SQL 正常读写数据,加解密由数据库引擎在数据页(Data Page)写入磁盘和从磁盘读取时自动完成。

TDE 的”透明”体现在:

但 TDE 不加密的内容包括:

MySQL 8.0 TDE 实战

MySQL 8.0 的 TDE 架构使用两层密钥:

MySQL TDE 密钥层次

┌──────────────────────────────────┐
│  外部密钥存储                      │
│  (keyring_file / keyring_vault) │
│  ┌────────────────────────────┐  │
│  │  Master Key(主密钥)       │  │
│  └──────────┬─────────────────┘  │
└─────────────┼────────────────────┘
              │ 加密/解密
              ▼
┌──────────────────────────────────┐
│  表空间头部                        │
│  ┌────────────────────────────┐  │
│  │  Tablespace Key(密文)     │  │
│  └──────────┬─────────────────┘  │
└─────────────┼────────────────────┘
              │ 加密/解密
              ▼
┌──────────────────────────────────┐
│  数据页                            │
│  ┌────────────────┐              │
│  │  Page 0 (密文)  │  16KB       │
│  │  Page 1 (密文)  │  16KB       │
│  │  ...            │             │
│  └────────────────┘              │
└──────────────────────────────────┘

配置步骤:

# my.cnf
[mysqld]
# 使用文件密钥环(生产环境应使用 HashiCorp Vault 等外部 KMS)
early-plugin-load=keyring_file.so
keyring_file_data=/var/lib/mysql-keyring/keyring

# 加密 InnoDB redo log
innodb_redo_log_encrypt=ON

# 加密二进制日志
binlog_encryption=ON

# 加密 undo log
innodb_undo_log_encrypt=ON
-- 创建加密表空间
CREATE TABLESPACE ts_encrypted
    ADD DATAFILE 'ts_encrypted.ibd'
    ENCRYPTION='Y';

-- 在加密表空间中创建表
CREATE TABLE sensitive_data (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    ssn VARCHAR(20)
) TABLESPACE ts_encrypted ENCRYPTION='Y';

-- 对已有表启用加密
ALTER TABLE existing_table ENCRYPTION='Y';

-- 查看加密状态
SELECT TABLE_SCHEMA, TABLE_NAME, CREATE_OPTIONS
FROM INFORMATION_SCHEMA.TABLES
WHERE CREATE_OPTIONS LIKE '%ENCRYPTION%';

-- 轮转主密钥(不需要重新加密数据页,只重新加密表空间密钥)
ALTER INSTANCE ROTATE INNODB MASTER KEY;

PostgreSQL TDE

PostgreSQL 的 TDE 历史比较曲折。社区版本长期缺乏原生 TDE 支持,直到 PostgreSQL 16 引入了集簇级加密(Cluster-Level Encryption)的基础设施。具体情况:

PostgreSQL TDE 的配置(以 pg_tde 扩展为例):

-- 安装扩展
CREATE EXTENSION pg_tde;

-- 配置密钥提供者(可以是文件、Vault 等)
SELECT pg_tde_add_key_provider_file(
    'local_file',
    '/etc/postgresql/tde_keyfile'
);

-- 设置主密钥
SELECT pg_tde_set_principal_key(
    'my_principal_key',
    'local_file'
);

-- 创建加密表(使用 tde_heap 访问方法)
CREATE TABLE classified_info (
    id SERIAL PRIMARY KEY,
    content TEXT
) USING tde_heap;

列级加密:pgcrypto

对于不需要整个表空间加密,而只需要加密特定列的场景,PostgreSQL 的 pgcrypto 扩展提供了字段级加密函数:

-- 安装 pgcrypto
CREATE EXTENSION pgcrypto;

-- 使用 AES-256-CBC 加密敏感字段
INSERT INTO users (name, encrypted_ssn)
VALUES (
    'Zhang San',
    pgp_sym_encrypt('310-12-3456', 'my_secret_key', 'cipher-algo=aes256')
);

-- 解密读取
SELECT name, pgp_sym_decrypt(encrypted_ssn, 'my_secret_key') AS ssn
FROM users;

列级加密的优势是粒度最细,但代价也明显:加密列无法建索引(因为索引存储的是密文),范围查询和模糊查询无法在密文上执行。如果查询模式需要对加密列做搜索,需要引入确定性加密(Deterministic Encryption)或同态加密(Homomorphic Encryption)等更复杂的技术,但这些方案目前在性能上仍不实用。


六、加密算法选型

AES-XTS:磁盘加密的事实标准

AES-XTS(XEX-based Tweaked-codebook mode with ciphertext Stealing)是 IEEE P1619 标准定义的磁盘加密模式。它是目前块级加密和文件级加密的默认选择。

AES-XTS 的工作原理:

  1. 将扇区号(Sector Number)作为可调参数(Tweak),经过 AES 加密生成 Tweak 值。
  2. 将明文分成 128 位(16 字节)的块。
  3. 每个块先与 Tweak 异或,然后 AES 加密,再与 Tweak 异或。
  4. Tweak 值在每个块之间通过 GF(2^128) 上的乘法更新。
AES-XTS 加密一个扇区

Sector Number ──▶ AES(Key2) ──▶ T (初始 Tweak)
                                  │
                                  ▼
Block 0:  P0 ⊕ T ──▶ AES(Key1) ──▶ ⊕ T ──▶ C0
                                  │
                                  ▼ T = T * α (GF 乘法)
Block 1:  P1 ⊕ T ──▶ AES(Key1) ──▶ ⊕ T ──▶ C1
                                  │
                                  ▼ T = T * α
Block 2:  P2 ⊕ T ──▶ AES(Key1) ──▶ ⊕ T ──▶ C2
                                  ...

AES-XTS 的关键特性:

AES-GCM:认证加密

AES-GCM(Galois/Counter Mode)是一种认证加密模式(Authenticated Encryption with Associated Data, AEAD),同时提供机密性和完整性保护。

AES-GCM 在磁盘加密中的优势和限制:

dm-crypt 支持通过 dm-integrity 配合 AEAD 模式(包括 AES-GCM)来实现认证加密:

# 使用认证加密模式(AES-GCM + dm-integrity)
# 注意:初始格式化较慢,因为需要初始化完整性元数据
sudo cryptsetup luksFormat --type luks2 \
    --cipher aes-gcm-random \
    --key-size 256 \
    --integrity aead \
    --sector-size 4096 \
    /dev/sdb

但在实际生产中,使用 AES-GCM 做磁盘加密的部署并不多。主要原因是 IV 管理复杂、性能开销大、以及对现有存储栈的侵入性强。

ChaCha20-Poly1305

ChaCha20-Poly1305 是 Google 推广的一种 AEAD 方案,由 ChaCha20 流密码和 Poly1305 消息认证码组合而成。

它在存储加密中的定位:

算法选型总结

算法模式 认证 IV/Nonce 管理 密文膨胀 硬件加速 推荐场景
AES-256-XTS 扇区号作为 Tweak,简单 AES-NI 块级/文件级加密的默认选择
AES-256-GCM 必须保证唯一,复杂 16B/扇区 AES-NI + CLMUL 需要完整性保护时
ChaCha20-Poly1305 必须保证唯一 16B/扇区 无(纯软件优势) 无 AES-NI 的低端设备
Adiantum 扇区号 Android 低端设备
AES-256-CBC-ESSIV ESSIV 模式 AES-NI 遗留系统兼容

工程判断:对于 x86 服务器上的存储加密,AES-256-XTS 是默认选择。只有在需要检测密文篡改时才考虑 AES-GCM(配合 dm-integrity),代价是性能下降和部署复杂度上升。ChaCha20 系列在嵌入式和移动端有用武之地,在服务器端基本不考虑。


七、密钥层次与管理

KEK/DEK 双层密钥架构

几乎所有成熟的存储加密方案都采用双层密钥架构:

这种设计的核心好处是密钥轮转不需要重新加密数据。轮转 KEK 时,只需要用新的 KEK 重新加密现有的 DEK,数据本身不动。由于 DEK 通常只有 32-64 字节,重新加密 DEK 的开销可以忽略。

双层密钥架构

┌───────────────────────────────────────────┐
│  密钥管理系统(Vault / KMS)               │
│                                           │
│  KEK(密钥加密密钥)                        │
│  ├── 版本 1: 0xAB12...(已轮转)           │
│  ├── 版本 2: 0xCD34...(当前)             │
│  └── 版本 3: (预生成,待启用)              │
│                                           │
│  KEK 的生命周期由 KMS 管理                  │
│  KEK 永远不离开 KMS 的内存                  │
└───────────────┬───────────────────────────┘
                │  用 KEK 加密/解密 DEK
                ▼
┌───────────────────────────────────────────┐
│  加密元数据(LUKS Header / 表空间头)        │
│                                           │
│  E(KEK, DEK) = 密文形式的 DEK              │
│  存储在加密卷头部或表空间头部                 │
└───────────────┬───────────────────────────┘
                │  用 DEK 加密/解密数据
                ▼
┌───────────────────────────────────────────┐
│  加密数据                                  │
│  E(DEK, Data) = 密文数据                   │
│  存储在磁盘扇区 / 数据页                     │
└───────────────────────────────────────────┘

HashiCorp Vault 集成

HashiCorp Vault 是最常用的开源密钥管理系统之一。Vault 的 Transit 密钥引擎(Transit Secrets Engine)提供”加密即服务”(Encryption as a Service),密钥永远不离开 Vault。

Vault Transit 与 LUKS 的集成方式:

# 1. 在 Vault 中创建加密密钥
vault secrets enable transit
vault write -f transit/keys/luks-kek type=aes256-gcm96

# 2. 生成 DEK(随机密钥材料)
DEK=$(head -c 64 /dev/urandom | base64)

# 3. 用 Vault 加密 DEK
ENCRYPTED_DEK=$(vault write -field=ciphertext \
    transit/encrypt/luks-kek \
    plaintext="$DEK")

# 4. 存储加密后的 DEK(安全存储,例如 etcd 或配置管理系统)
echo "$ENCRYPTED_DEK" > /etc/luks/encrypted-dek.txt

# 5. 解锁 LUKS 卷时,先从 Vault 解密 DEK
PLAIN_DEK=$(vault write -field=plaintext \
    transit/decrypt/luks-kek \
    ciphertext="$ENCRYPTED_DEK" | base64 -d)

# 6. 用解密后的 DEK 打开 LUKS 卷
echo -n "$PLAIN_DEK" | sudo cryptsetup open --key-file=- /dev/sdb encrypted_data

云 KMS 集成

主要云厂商都提供托管的密钥管理服务(Key Management Service, KMS):

云厂商 KMS 服务 与存储加密的集成
AWS AWS KMS EBS 加密、RDS TDE、S3 SSE-KMS
阿里云 密钥管理服务 KMS 云盘加密、RDS TDE、OSS 服务端加密
GCP Cloud KMS Persistent Disk 加密、Cloud SQL
Azure Azure Key Vault Managed Disk 加密、Azure SQL TDE

云 KMS 的优势是硬件安全模块(Hardware Security Module, HSM)托管、自动轮转和审计日志。但也引入了对云厂商的强依赖——KEK 存储在云端,离开该云就无法解密数据。

密钥的安全存储要求

无论使用什么密钥管理方案,都需要满足以下工程要求:

  1. 密钥与密文分离:KEK 不能存储在被加密的同一个设备上,否则加密形同虚设。
  2. 最小权限:只有需要解密数据的服务才能访问密钥。使用 Vault/KMS 的策略(Policy)来限制密钥访问范围。
  3. 审计日志:每次密钥的使用(加密、解密、轮转)都要记录日志,支持事后审计。
  4. 高可用:密钥管理系统本身必须高可用,否则密钥不可用等于数据不可用。Vault 建议至少 3 节点集群。
  5. 灾难恢复:KEK 的备份策略必须独立于加密数据的备份策略。建议使用 Shamir 秘密分享(Shamir’s Secret Sharing)将 KEK 备份拆分给多个管理员。

八、密钥轮转工程

为什么要轮转密钥

密钥轮转(Key Rotation)是安全运维的基本要求:

KEK 轮转(快速路径)

KEK 轮转不需要碰数据,只需要:

  1. 在 KMS 中生成新的 KEK 版本。
  2. 用旧 KEK 解密所有 DEK。
  3. 用新 KEK 重新加密所有 DEK。
  4. 更新加密元数据(LUKS 头部 / 表空间头部)。
  5. 废弃旧 KEK 版本(但保留一段时间以防回滚)。

这个过程通常在秒级完成,因为 DEK 的数量有限且体积小。

MySQL TDE 的主密钥轮转就是这种模式:

-- MySQL 主密钥轮转:秒级完成,不影响在线业务
ALTER INSTANCE ROTATE INNODB MASTER KEY;

-- 轮转过程:
-- 1. 生成新的 Master Key(从 keyring 插件)
-- 2. 读取所有加密表空间的头部,用旧 Master Key 解密 Tablespace Key
-- 3. 用新 Master Key 重新加密 Tablespace Key,写回表空间头部
-- 4. 更新 keyring 中的密钥版本
-- 数据页不需要重新加密

DEK 轮转(慢路径)

DEK 轮转需要用新密钥重新加密全部数据,开销与数据量成正比。这是一个 I/O 密集型操作,对在线业务有影响。

LUKS2 的在线重加密功能可以在不卸载文件系统的情况下轮转 DEK:

# LUKS2 在线重加密(cryptsetup 2.4+)
# 这会生成新的 DEK 并用新密钥重新加密全部数据
# 操作期间设备正常可用,但 I/O 性能会下降

# 查看当前加密参数
sudo cryptsetup luksDump /dev/sdb

# 在线重加密:更换加密算法和密钥
sudo cryptsetup reencrypt /dev/sdb \
    --cipher aes-xts-plain64 \
    --key-size 512 \
    --resilience journal

# --resilience 选项:
#   journal: 使用日志保证崩溃安全(推荐,但需要额外空间)
#   checksum: 使用校验和检测中断位置
#   none: 不做崩溃保护(最快,但中断后可能丢数据)

# 监控重加密进度
sudo cryptsetup status encrypted_data

在线重加密的工程注意事项:

零停机轮转策略

对于不能中断服务的系统,DEK 轮转的零停机策略:

  1. 滚动轮转:如果数据分布在多个加密卷上(例如分片数据库),可以逐个卷轮转。每个卷轮转时,该分片短暂降级但整体服务可用。
  2. 双写过渡:在轮转期间,新写入的数据用新密钥加密,旧数据逐步用后台任务迁移到新密钥。需要加密元数据同时维护新旧两个 DEK。
  3. 存储级快照:先对加密卷做快照,在快照上执行重加密验证,确认无误后再在主卷上执行。

九、硬件加速:AES-NI 与 QAT

AES-NI 指令集

AES-NI(Advanced Encryption Standard New Instructions)是 Intel 从 Westmere 架构(2010 年)开始引入的 x86 指令集扩展,提供了 AES 加密操作的硬件加速。AMD 从 Bulldozer 架构(2011 年)开始支持等效指令。

AES-NI 的核心指令:

指令 功能
AESENC 执行一轮 AES 加密
AESENCLAST 执行最后一轮 AES 加密
AESDEC 执行一轮 AES 解密
AESDECLAST 执行最后一轮 AES 解密
AESKEYGENASSIST 辅助密钥扩展
PCLMULQDQ 无进位乘法(用于 GCM 的 GHASH)

检查系统是否支持 AES-NI:

# 检查 CPU 是否支持 AES-NI
grep -o aes /proc/cpuinfo | head -1
# 输出 "aes" 表示支持

# 检查内核是否加载了 AES-NI 加速模块
lsmod | grep aesni
# 应看到 aesni_intel 模块

# 查看内核 Crypto API 中注册的 AES 实现
cat /proc/crypto | grep -A 4 "name.*aes"

AES-NI 的性能优势来自两方面:

  1. 指令级加速:一条 AESENC 指令完成一整轮 AES 变换(SubBytes + ShiftRows + MixColumns + AddRoundKey),而软件实现需要数十条指令完成同样的操作。
  2. 流水线并行:AES-NI 指令的延迟约 4 个时钟周期,但吞吐量为每周期 1 条指令。通过对多个独立块交错执行加密,可以充分利用流水线。AES-XTS 模式天然支持这种并行,因为同一扇区内的各个块的 Tweak 值可以预先计算。

Intel QAT

Intel QuickAssist Technology(QAT)是一种专用加速硬件,以 PCIe 卡或芯片组集成的形式提供,用于卸载加密和压缩操作。

QAT 与 AES-NI 的定位区别:

维度 AES-NI QAT
形态 CPU 指令集 独立加速器(PCIe / 芯片组集成)
占用 CPU 核心 否(异步卸载)
延迟 极低(纳秒级) 较高(微秒级,含 DMA 开销)
吞吐量 受 CPU 核心数限制 单卡可达 100 Gbps+
适用场景 通用加密,中低吞吐 高吞吐加密卸载,释放 CPU

QAT 的典型应用场景不是磁盘加密(延迟敏感),而是网络加密(TLS 终结)和大批量数据加密。对于存储加密,AES-NI 已经足够满足绝大多数场景的性能需求。

性能对比

以下引用数据来自 Linux 内核 Crypto API 的 benchmark 框架(tcrypt 模块)和 cryptsetup benchmark 工具的典型结果。不同 CPU 型号的绝对数值会有差异,但相对关系稳定。

测试环境描述(引用数据,非本地实测):

典型测试环境:
- CPU: Intel Xeon(Icelake 或 Sapphire Rapids 系列,支持 AES-NI 和 VAES)
- 内存: DDR4/DDR5
- OS: Linux 6.x
- 工具: cryptsetup benchmark / tcrypt 内核模块

cryptsetup benchmark 典型结果:

# cryptsetup benchmark(引用数据)
# 测试对象:各加密算法在内核 Crypto API 中的吞吐量

算法                密钥长度    加密速度        解密速度
aes-cbc             128 bit   1200 MiB/s     4100 MiB/s
aes-cbc             256 bit   900 MiB/s      3100 MiB/s
aes-xts             256 bit   3700 MiB/s     3800 MiB/s
aes-xts             512 bit   2900 MiB/s     3000 MiB/s
serpent-xts          256 bit   470 MiB/s      480 MiB/s
twofish-xts          256 bit   370 MiB/s      380 MiB/s

关键观察:

这个吞吐量意味着:在现代服务器上,AES-NI 加速的 AES-XTS 加密不太可能成为 I/O 路径的瓶颈。一个典型的 NVMe SSD 的顺序读写速度在 3-7 GB/s 范围,单核 AES-XTS 的加密速度与之匹配。


十、加密对存储性能的影响

性能开销的来源

存储加密的性能开销由以下几部分组成:

  1. CPU 加密计算:AES-NI 加速后,这部分开销通常很小。
  2. 内存拷贝:dm-crypt 需要分配额外的缓冲区来存放加密前/后的数据,增加内存拷贝次数。
  3. I/O 路径延长:加密引入了额外的处理层,增加了每个 I/O 请求的延迟。
  4. 内核线程调度:dm-crypt 使用内核工作队列(Workqueue)来执行加密操作,线程调度引入额外延迟。
  5. 密文膨胀(AEAD 模式):使用 AES-GCM 等认证加密模式时,认证标签占用额外空间,有效容量下降。

IOPS 和吞吐量影响

以下是典型的性能影响范围(综合多个公开的 benchmark 报告得出的经验值,具体数值因硬件和工作负载而异):

工作负载类型 无加密基线 dm-crypt AES-XTS(AES-NI) 性能损失比例
顺序读(128K 块) 3500 MB/s 3200-3400 MB/s 3%-8%
顺序写(128K 块) 3000 MB/s 2700-2900 MB/s 3%-10%
随机读(4K, QD32) 600K IOPS 500K-570K IOPS 5%-15%
随机写(4K, QD32) 500K IOPS 400K-470K IOPS 6%-20%

说明:以上数据是综合多个公开 benchmark 报告(Phoronix、Arch Wiki、Red Hat 文档)的经验范围,不是在单一环境下的精确实测。实际影响取决于 CPU 型号、NVMe 设备型号、内核版本、dm-crypt 配置参数和并发度。

关键趋势:

延迟分析

加密引入的额外延迟需要从 I/O 路径的角度分析:

未加密的 I/O 路径延迟分解(4K 随机读)

应用 → 文件系统 → 块层 → NVMe 驱动 → 设备
       ~1μs        ~1μs    ~1μs        ~80μs
                                    总延迟 ≈ 83μs

加密的 I/O 路径延迟分解(4K 随机读,dm-crypt)

应用 → 文件系统 → dm-crypt → 块层 → NVMe 驱动 → 设备
       ~1μs       ~3-5μs    ~1μs    ~1μs        ~80μs
                                            总延迟 ≈ 86-88μs

dm-crypt 增加的约 3-5 微秒包括:工作队列调度、加密计算和额外的内存拷贝。对于 NVMe 设备(基线延迟约 80-100 微秒),这个增量占比约 4%-6%。但对于更慢的 SATA SSD(基线延迟约 30-50 微秒)或 HDD(基线延迟数毫秒),加密延迟占比更小,几乎不可感知。

dm-crypt 性能优化参数

dm-crypt 有几个内核参数可以影响性能:

# 查看当前 dm-crypt 配置
sudo dmsetup table encrypted_data

# 优化 1:使用 no_read_workqueue 和 no_write_workqueue(Linux 5.9+)
# 跳过工作队列,在提交 I/O 的线程中直接加密
# 减少线程调度延迟,对延迟敏感的场景有帮助
sudo cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue \
    refresh encrypted_data

# 优化 2:使用 submit_from_crypt_cpus(Linux 4.0+)
# 让加密线程直接提交 I/O,避免线程切换
sudo cryptsetup --perf-submit_from_crypt_cpus \
    refresh encrypted_data

# 优化 3:调整 sector-size 对齐
# 确保加密扇区大小与底层设备匹配
sudo cryptsetup --sector-size 4096 \
    refresh encrypted_data

内联加密引擎

现代存储控制器和 UFS/eMMC 控制器开始支持内联加密引擎(Inline Encryption Engine),在硬件层面完成加密/解密,完全不占用 CPU。Linux 内核从 5.9 版本开始支持内联加密框架(blk-crypto),允许 dm-crypt 和 fscrypt 将加密操作卸载到存储控制器的硬件引擎。

内联加密的优势:

但限制也明显:

# 检查设备是否支持内联加密
cat /sys/block/sda/queue/crypto_profile/modes/AES-256-XTS 2>/dev/null
# 如果文件存在且内容非空,表示设备支持 AES-256-XTS 内联加密

性能影响的工程总结

场景 加密方式 性能影响 工程建议
服务器全盘加密 dm-crypt + AES-NI 3%-15% 默认开启,性能损失可接受
数据库 TDE 应用层加密 5%-20% 只加密敏感表空间,避免全库加密
高 IOPS 工作负载 dm-crypt + no_workqueue 5%-10% 启用 no_read/write_workqueue 优化
移动设备 fscrypt + 内联加密 <1% 依赖硬件内联加密引擎
低端 ARM 设备 Adiantum 5%-15% 无 AES-NI 时的折中方案

核心判断:在配备 AES-NI 的现代 x86 服务器上,dm-crypt 的性能开销在大多数工作负载下处于可接受范围(单位数百分比)。加密不再是”用不起”的奢侈品,而是应该默认启用的基础设施。性能调优的重点不在加密算法本身,而在 dm-crypt 的工作队列配置和扇区大小对齐。


十一、参考文献

规范与标准

源码与文档

论文与书籍

工具与实验


上一篇: 序列化格式深度对比 下一篇: 对象存储模型:从文件到对象的范式转变

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-31 · storage

【存储工程】块存储加密:LUKS 与 dm-crypt

在存储系统的安全防线中,加密是最后一道也是最重要的一道屏障。当物理安全措施失效——磁盘被盗、服务器被非法访问、退役硬盘未彻底销毁——只有加密能确保数据不被未授权方读取。块级加密(Block-Level Encryption)在存储栈的最低层工作,对上层文件系统和应用程序完全透明,不需要修改任何一行应用代码就能保护磁盘上…

2026-04-22 · db / storage

数据库内核实验索引

汇总本站数据库内核与存储引擎实验文章,重点覆盖从零实现 LSM-Tree 及其工程权衡。

2026-04-22 · storage

存储工程索引

汇总本站存储工程系列文章,覆盖 HDD、SSD、NVMe、持久内存、索引结构、压缩、分布式存储与对象存储。


By .