Crypto API 与存储加密

本页目录

更换解锁口令通常不用重写全盘,更换卷密钥却可能需要重加密。密码原语、密钥管理和实际数据路径需要分层理解。

保护离线介质,与阻止已解锁系统上的恶意进程读文件,是不同目标;存储加密仍需配合访问控制。

查阅目标阅读位置
算法实例与异步请求第 1 节
块加密、keyslot 与目录策略第 2~3 节
网络加密、TPM 与摘要实验第 4~6 节

1. Crypto API:算法实例与单次请求

接口类别任务例子
skcipher对称加解密XTS、CBC 等模式
AEAD加密并认证数据及附加数据GCM、ChaCha20-Poly1305
shash / ahash同步/异步摘要或相关 keyed hashSHA-256、HMAC
akcipher非对称加密等接口RSA 类操作
kpp密钥协商DH、ECDH
rng特定随机数生成器接口已注册 RNG 实现

ECDH 应归入密钥协商,不应拿它作为 RSA 式加密接口的例子。普通内核随机字节需求常使用 get_random_bytes() 等随机数子系统接口,也不是每个调用者都自行分配 crypto_rng。

tfm 是配置算法与密钥等信息的 transformation 对象;一次操作通常还需要 request、源/目标 scatterlist、长度和 IV。正确的 skcipher 加密入口接收 skcipher_request *,不是伪造的 crypto_skcipher_encrypt(tfm, src, dst, len)。

  1. 分配 tfm,检查错误并设置密钥。
  2. 建立 request、回调、源/目标范围与 IV。
  3. 提交后按返回状态区分同步完成或异步处理。
  4. 满足完成契约后,才释放请求、数据与上下文。

在可睡眠上下文可按官方示例用 crypto_wait_req() 等待;不能把它搬进原子上下文。见 skcipher API与完整示例。

算法名字不是具体执行单元

xts(aes) 之类名字组合模板和底层算法。多个实现可以提供同一通用算法名,Crypto API 根据匹配条件、实现优先级等选择;指定 driver 名则会收窄选择。/proc/crypto 可以观察当前注册实现,但不是系统所有可能加载算法组合的完整清单。

AES-NI、ARM 加密扩展和独立加速器具有不同调用成本。小块数据、队列延迟、DMA 和复制成本都可能影响结果,不能保证“存在硬件实现就固定快十倍”。内核也有直接的密码学库接口,并非每项密码操作都必须经过动态 tfm 分配。见 Crypto API 架构。

2. dm-crypt 与 LUKS 分别在哪一层

解锁密钥与读写数据走不同路径 口令 / TPM 等解锁材料 用户态处理解锁策略 LUKS keyslot 保护卷密钥 卷密钥 交给运行中的加密映射 文件系统块 I/O 已解锁时可访问明文 dm-crypt 按扇区加解密 下层存储 保存密文数据 更换口令通常改变 keyslot;更换卷密钥才涉及重新加密数据。

dm-crypt 是 device-mapper 的块加密 target,处理发往映射设备的数据读写;上面的文件系统通常看到解密后的块。LUKS 是磁盘格式和密钥管理约定,由 cryptsetup 等用户态工具读取头部、完成解锁,再配置内核映射。内核 dm-crypt 不负责解析每个 LUKS JSON keyslot。

变化通常涉及哪一层
增加或更换口令PBKDF 派生解锁材料,处理 keyslot
恢复卷密钥并打开映射使用 keyslot 中受保护的密钥材料
真正更换卷密钥涉及数据重加密流程

所以改口令通常不等于重新加密整个文件系统;两种操作的成本与恢复需求不同。

LUKS1 的 keyslot 上限是 8;LUKS2 支持最多 32 个槽位,实际可用数量还受 keyslot 区域大小等条件限制。LUKS2 有冗余元数据及 JSON 描述,头部、keyslot 区和数据偏移均有明确布局及可配置参数,不能把它们统一叫作“固定前 16 MB 的 header”。参见 LUKS2 格式规范和 cryptsetup 参数。

PBKDF2、Argon2i、Argon2id 的可用性及参数依格式与工具配置而定。内存成本可以增加离线口令猜测的代价,但不能使弱口令变成不可破解的密钥。解锁阶段的 PBKDF 开销,也不要与每次扇区读写的加密开销混为一谈。

机密性不等于完整性

常见 XTS 模式提供扇区数据机密性,不提供通用的篡改认证和防回滚保证。需要认证加密或完整性保护时,还要选择相应模式、元数据存储及 dm-integrity 等配合机制,并理解其边界。

放行 discard 可以暴露使用空间等模式信息。已解锁时,具有读取权限的进程仍能获得明文;卷密钥和缓存的明文也会参与运行中系统的内存威胁模型。见 dm-crypt 文档。

下面用浏览器实际加密同一段公开示例内容,再翻转密文中的一位。对比无认证的 CTR 与带认证的 GCM:能解出一些字节,与这些字节通过验证,是两种结果。

正在呈现知识画面
密文改一位,解密成功能否说明内容可信?

无认证的 CTR 可以在密文位翻转后解出被修改的明文;GCM 验证认证标签,拒绝这里的改动。机密性、完整性和防回滚需要分别说明。

3. fscrypt:在文件系统内选择保护范围

fscrypt 为支持它的文件系统提供文件内容和文件名加密。目录策略可以让不同目录树使用不同主密钥,便于在同一文件系统中管理多个解锁域。

它通常不隐藏文件大小、权限、时间戳和目录结构等非文件名元数据,也不能保证离线观察者无法关联文件。默认的按文件派生与 nonce 机制,还存在 DIRECT_KEY、IV_INO_LBLK 等策略差异,不能概括成“任何配置下每个文件都有一把独立数据密钥”。

普通 fscrypt 设计主要保护静态数据机密性,不自动提供抵抗主动篡改和回滚的完整文件系统认证。移除主密钥也需要考虑已打开文件和缓存中仍被使用的派生密钥;不能把 ioctl 返回理解成所有明文瞬间消失。

新目录策略通常在满足空目录等条件时设置,随后创建的文件继承相应策略;不会把一棵已有明文目录凭空转换完成。FS_IOC_GET_ENCRYPTION_PWSALT 是历史口令派生辅助接口,也不是读取任意目录独有盐值的通用接口。策略与限制见 fscrypt 文档。

比较点dm-cryptfscrypt
管理边界块映射文件系统内的策略及密钥域
文件系统元数据在加密映射内部时一并成为加密块许多非文件名元数据仍可见
解锁粒度通常是整个映射可管理不同目录树的密钥
解锁后的访问控制仍依赖权限/LSM 等同样仍依赖权限/LSM 等

两者也可以叠加;它们不是互斥的产品替代关系。

4. WireGuard 与 kTLS:不同网络层的加密

WireGuard 提供三层加密隧道,使用 Noise 握手、Curve25519 和 ChaCha20-Poly1305 等固定协议组件。静态公钥标识 peer,握手产生会话密钥;数据包的计数器、认证和重放处理共同参与保护。密钥更新由时间、消息数量和会话状态等条件触发,不是严格每两分钟无条件执行一次定时动作。

Curve25519 不是后量子算法。WireGuard 可混入预共享对称密钥作为附加防护,但这不能概括为“后量子问题不重要”或整个系统已获得所有后量子安全属性。配置接口主要使用 generic netlink,网络设备创建等还涉及常规网络接口管理。见 WireGuard 协议。

kTLS 则将受支持的 TLS 记录层发送/接收处理交给内核或硬件;握手、证书验证和协议状态管理仍主要由用户态 TLS 库负责。连接协商后需设置 TCP 的 TLS ULP,并分别配置 TX/RX 所需的密码状态,不能只复制一个 setsockopt 就当完整 TLS 客户端。

sendfile 等路径可减少用户态搬运,但软件加密、NIC 卸载、零拷贝发送是不同能力,是否可用依内核、协议、算法与驱动支持而定。见 kTLS 文档。

5. TPM:按策略使用秘密,策略本身也要管理

TPM 可以保护密钥对象,并依据 PCR 等条件约束解封或使用。典型 LUKS 集成会生成解锁材料,将其与 TPM 对象/策略和 LUKS token 关联;不意味着把整个磁盘加密密钥永远只留在 TPM 内做每次扇区加密。

PCR 是测量事件累积结果,不是 TPM 自己理解“这个内核一定良性”。选择哪些 PCR、测量链是否可信、谁签署授权策略,决定了解锁约束。允许合法升级还需要策略更新、签名 PCR policy 或恢复凭据等设计。

启动授权、测量与解封分别证明什么

Secure Boot 验证引导代码的授权关系,measured boot 记录测量,TPM sealing 依据策略限制秘密使用;三者可以配合但不能互相代替。正常解锁后的运行时漏洞也不会自动被 PCR 绑定阻止。实现可参考 systemd-cryptenroll 源码与文档。

6. 用 AF_ALG 观察一次摘要请求

下面通过 Linux AF_ALG 调用内核 SHA-256,对公开字符串做已知结果校验,不读取密钥或修改存储。需要 Python/Linux 支持 AF_ALG,内核启用对应算法用户接口。它演示内核接口,不表示一般应用应替换自己的密码库。

import socket

expected = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
with socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) as alg:
    alg.bind(('hash', 'sha256'))
    op, _ = alg.accept()
    with op:
        sent = op.send(b'abc')
        if sent != 3:
            raise RuntimeError('unexpected short input')
        digest = op.recv(32)
assert len(digest) == 32 and digest.hex() == expected
print(digest.hex())

本环境未启用 AF_ALG 时,创建 socket 会返回 EAFNOSUPPORT;这表示接口能力不可用,不能据此判断 SHA-256 算法本身有误。

若 socket 创建返回 EAFNOSUPPORT,先检查 CONFIG_CRYPTO_USER_API_HASH 等支持;不能因此判断 SHA-256 算法实现错误。

摘要匹配证明这条调用路径得到预期结果,不证明整个加密存储配置、密钥管理或硬件卸载已正确。AF_ALG 的接口限制和支持情况见内核用户态加密接口。