Linux7.0 内核加密 API 深度解析
第 1 章 内核加密 API 概述
1.1 设计目标与定位
Linux 内核加密 API(Kernel Crypto API)不仅是一个密码算法库,更是一套可扩展的密码服务框架。其核心设计围绕三个目标:
-
统一接口:为所有密码算法提供一致的 API,屏蔽底层实现差异(软件/硬件、同步/异步)。
-
算法无关性:消费者通过字符串名称引用算法,不依赖具体实现。
-
可组合性:通过模板(Template)机制,将基础算法与运算模式组合,形成复杂的复合变换。
/** * @brief 内核加密 API 服务的两类实体 * * - 消费者 (Consumer): 请求密码服务的代码,如 IPsec、dm-crypt、fscrypt 等 * - 实现者 (Implementer): 提供具体密码变换的代码,如 aes-generic、ghash-clmulni 等 * * @note API 规范同时面向消费者和实现者,但实现者可用的底层接口不在此规范讨论范围 * @see Documentation/crypto/api-intro.rst */
1.2 核心抽象:变换 (Transformation)
与常规密码库不同,内核将所有算法统称为变换 (Transformation)。因此,变换对象句柄通常命名为 tfm。
┌──────────────────────────────────┐ │ struct crypto_skcipher (tfm) │ ← 变换对象 (Transformation Object) │ ┌──────────────────────────┐ │ │ │ struct crypto_alg * │ │ ← 算法实现 (Implementation) │ │ key, iv, state ... │ │ ← 变换上下文 (Context) │ └──────────────────────────┘ │ └──────────────────────────────────┘
三个核心概念:
| 术语 | 描述 | 示例 |
|---|---|---|
| 变换实现 (Implementation) | 实现特定密码逻辑的代码或硬件接口 | aes-generic.c, aesni-intel_glue.c |
| 变换对象 (TFM) | 变换实现的实例,由消费者分配并使用 | struct crypto_skcipher *tfm |
| 变换上下文 (Context) | 与变换对象关联的私密数据 | 密钥、IV、中间状态等 |
/** * @brief 变换对象的生命周期 * * 1. 初始化 (Initialization): 通过 crypto_alloc_* 系列函数分配 TFM * 2. 操作 (Execution): 使用 TFM 执行密码操作 * 3. 析构 (Destruction): 通过 crypto_free_* 系列函数释放 TFM * * @note TFM 只能在进程上下文分配,操作可从 softirq 和进程上下文调用 */
第 2 章 API 架构分层
2.1 三层架构模型
┌──────────────────────────────────────────────┐ │ [Transform API] (用户接口层) │ ← crypto_skcipher_encrypt() 等 │ [Transform Ops] (每类变换的胶水逻辑) │ ← cipher.c, compress.c 等 │ [Algorithm API] (算法注册接口) │ ← crypto_register_alg() └──────────────────────────────────────────────┘
设计精髓:这种分层将“如何用”和“如何实现”完全解耦。最上层提供简洁的用户接口,中间层处理散列列表遍历、对齐、工作内存分配等通用逻辑,最下层则由算法实现者注册具体算法。
2.2 算法类型与模板系统
内核加密 API 支持五种主要变换类型:
| 类型 | 描述 | 代表算法 |
|---|---|---|
| AEAD | 带关联数据的认证加密 | GCM(aes), CCM(aes) |
| Block Cipher | 多块密码(实际包含流密码) | cbc(aes), chacha20 |
| Cipher | 单块密码 | aes, serpent |
| Compressor | 压缩算法 | lzo, lz4 |
| Hash | 散列算法 | sha256, md5 |
模板机制是内核加密 API 最精妙的设计之一。通过将基础算法与模板组合,可以用少数基础实现覆盖大量密码模式:
aes → 单块实现 ecb(aes) → ECB 模式 cbc(aes) → CBC 模式 ctr(aes) → CTR 模式 gcm(aes) → GCM 模式 (需要 AEAD 接口) hmac(sha256) → HMAC 模式 authenc(hmac(sha1),cbc(aes)) → 复合认证加密
/** * @brief 通过 /proc/crypto 查看系统中的算法 * * 每个条目显示通用名 (name)、驱动名 (driver)、优先级 (priority)、 * 类型 (type)、块大小 (blocksize)、密钥大小 (keysize) 等信息。 * * @note 模板和基础算法的组合数量可能远多于 /proc/crypto 中列出的条目, * 因为内核会动态实例化模板。 */
2.3 优先级与算法选择
当多个实现提供相同的算法名时(如通用 aes 与硬件加速的 aesni),内核通过优先级 (priority) 选择实现:
/** * @brief 算法选择规则 * * 1. 消费者使用通用名 (如 "aes") 分配 TFM 时,内核选择优先级最高的实现。 * 2. 消费者可通过驱动名 (driver, 如 "aes-aesni") 指定特定实现。 * 3. 优先级数字越大越优先,软件实现通常使用 100,硬件加速可达 200~400。 * * @see /proc/crypto 中的 priority 字段 */
场景调试:当怀疑硬件加速未生效时,检查 /proc/crypto 中对应算法的 driver 和 priority。若硬件驱动的优先级意外低于软件实现,需检查 Kconfig 配置和模块加载顺序。
2.4 同步与异步操作
内核加密 API 提供两种操作模式:
| 模式 | 表现 | 适用场景 |
|---|---|---|
| 同步 (Synchronous) | 调用者等待操作完成,类似普通函数调用 | 简单、短耗时操作 |
| 异步 (Asynchronous) | 请求提交后立即返回,通过回调通知完成 | 硬件卸载、长耗时操作 |
/** * @brief 异步操作的关键约定 * * 1. 提交请求前必须注册完成回调 * 2. 回调可能在 softirq 上下文执行,需要适当的锁保护 * 3. 内核提供 crypto_wait_req() 辅助函数将异步调用转为同步等待 * * @note TFM 分配必须始终在进程上下文 */
第 3 章 散列列表 (Scatterlist) 加密 API
3.1 设计动机:零拷贝加密
传统加密 API 要求线性缓冲区,但网络栈中的数据以 sk_buff 形式存在,分散在不连续的页中。散列列表加密 API 直接操作 scatterlist,避免数据线性化开销,对 IPsec 等场景至关重要。
/** * @brief Scatterlist 加密的优势 * * - ECB 模式可实现原地加密 (in-place),零拷贝 * - 直接操作页向量,绕过中间缓冲区 * - 特定于 IPsec 设计,但已扩展为通用接口 * * @note 性能最优的情况是每个 scatterlist 条目都是算法块大小的整数倍, * 否则内核需进行跨页边界对齐拷贝。 */
3.2 典型使用流程
/**
* @brief 使用 Hash API 的典型流程
*
* 1. crypto_alloc_ahash("sha256", 0, 0) → 分配 TFM
* 2. ahash_request_alloc(tfm, GFP_KERNEL) → 分配请求对象
* 3. ahash_request_set_callback(req, ...) → 设置完成回调
* 4. ahash_request_set_crypt(req, sg, ...) → 设置数据
* 5. crypto_ahash_digest(req) → 执行操作
* 6. ahash_request_free(req) → 释放请求
* 7. crypto_free_ahash(tfm) → 释放 TFM
*
* @see crypto/async_tx/ 中的异步传输实现
*/
3.3 新增算法要求
/** * @brief 提交新算法的强制要求 * * 1. 必须包含至少一组来自已知标准(如 FIPS、NIST)的测试向量 * 2. 优先转换经过审查的现有代码 * 3. 算法必须无专利限制(如 IDEA 曾因专利延迟合入主线) * 4. 建议使用内联函数而非宏,以利用编译器优化 * * @note 如果从 LGPL 代码转换,请考虑变更许可证为 GPL */
第 4 章 开发密码算法
4.1 注册与注销变换
内核加密 API 提供三种注册函数族:通用注册、HASH 专用注册、COMPRESS 专用注册。本章聚焦通用注册。
/** * @brief 通用算法注册与注销函数 * * @param alg 指向填充好的 struct crypto_alg 的指针 * @param algs 算法描述符数组 * @param count 数组中的算法数量 * * @return 0 表示成功,负值表示 errno * * @note crypto_register_algs() 只有在成功注册所有算法时才返回 0; * 如果中途失败,会回滚所有已注册的算法。 * @note 注销函数总是成功,无需返回值。 * @note 不要尝试注销未注册的算法。 */ int crypto_register_alg(struct crypto_alg *alg); int crypto_register_algs(struct crypto_alg *algs, int count); void crypto_unregister_alg(struct crypto_alg *alg); void crypto_unregister_algs(struct crypto_alg *algs, int count);
4.2 单块对称密码 [CIPHER]
单块密码是最简单的变换类型:每次操作恰好处理一个块,块之间无任何依赖。
示例:aes, serpent, twofish
/** * @brief 单块密码注册要点 * * - struct crypto_alg 的 .cra_type 必须为空 * - .cra_u.cipher 必须填充 struct cipher_alg 回调 * * @see struct cipher_alg 定义 */
操作流程:
KEY ---. PLAINTEXT ---. v v .cia_setkey() -> .cia_encrypt() | '-----> CIPHERTEXT
设计精髓:cia_setkey() 可在任何时刻调用(但不能在加密操作进行中调用),且支持多次调用以切换不同密钥。这种设计允许消费者在每个请求中使用不同密钥,无需重新分配 TFM。
场景调试:如果加密结果与预期不符,首先检查 cia_setkey() 是否在 cia_encrypt() 之前被正确调用。单块密码不维护内部状态,错误通常是密钥或 IV 设置不当。
4.3 多块密码 (skcipher)
多块密码处理散列列表数据,是实际中最常用的对称密码接口。
示例:cbc(aes), ctr(aes), chacha20
/** * @brief 多块密码注册要点 * * - struct crypto_alg 的 .cra_type 必须指向 cpu_skcipher_type * - .cra_u.skcipher 必须填充 struct skcipher_alg 回调 * * @note 如果密码实现需要数据对齐,消费者应通过 * crypto_skcipher_alignmask() 获取对齐掩码。 * 内核 API 可处理非对齐请求,但会有额外对齐开销。 */
散列列表处理:某些硬件驱动需要使用 Generic ScatterWalk 将散列列表拆分为独立的数据块。ScatterWalk 接口在 Linux 散列列表实现中提供。
4.4 散列变换 [HASH]
示例:crc32, md5, sha1, sha256, hmac(sha256)
4.4.1 注册函数
/** * @brief HASH 算法的注册与注销 * * @note SHASH 表示同步散列,AHASH 表示异步散列 * @note crypto_register_shashes() 是批量注册版本 */ int crypto_register_ahash(struct ahash_alg *alg); int crypto_register_shash(struct shash_alg *alg); int crypto_register_shashes(struct shash_alg *algs, int count); void crypto_unregister_ahash(struct ahash_alg *alg); void crypto_unregister_shash(struct shash_alg *alg); void crypto_unregister_shashes(struct shash_alg *algs, int count);
4.4.2 HASH 操作模式
内核散列 API 支持四种操作序列,灵活度极高:
模式 I: .init() -> .update() -> .final() → HASH 模式 II: .init() -> .update() -> .finup() → HASH 模式 III: .digest() → HASH 模式 IV: .init() -> .update() -> .export() → PARTIAL_HASH ... 其他操作 ... .import() -> .update() -> .final() → HASH
/** * @brief HASH 操作模式说明 * * - .update() 可调用零次或多次,使用模式 I/II * - .digest() 一次完成所有操作(模式 III) * - .export()/.import() 支持状态保存与恢复(模式 IV), * 适用于硬件上下文切换等场景 * * @warning .init() 或 .update() 之后可以 "放弃" 请求对象, * 即不调用 .final()/.finup()/.export()。实现者必须 * 确保在 .init() 或 .update() 后不残留任何资源分配。 */
场景调试:如果 HASH 结果不匹配,检查是否遗漏了最后的 update 调用。如果在 init 和 final 之间发生了硬件卸载的中断,export/import 可用于保存和恢复中间状态。
4.4.3 异步 HASH 的散列列表处理
异步 HASH 驱动可以使用 Generic ScatterWalk 将散列列表拆分为独立块提供给硬件。输出缓冲区总是正确对齐到 .cra_alignmask。
第 5 章 代码示例
5.1 对称密钥密码操作
以下示例使用 AES-256-XTS 加密数据,展示完整的 TFM 生命周期和请求管理。
/**
* @brief 完整的对称加密示例 (AES-256-XTS)
*
* 该示例涵盖了从 TFM 分配到释放的完整流程,包括:
* - 密钥设置
* - IV 生成
* - 请求对象的分配与配置
* - 同步等待异步操作的完成
*
* @note 实际应用中应对多个操作复用同一个 TFM,以提高效率。
* @see crypto_wait_req() 函数将异步请求转换为同步等待
*/
static int test_skcipher(void)
{
struct crypto_skcipher *tfm = NULL;
struct skcipher_request *req = NULL;
u8 *data = NULL;
const size_t datasize = 512;
struct scatterlist sg;
DECLARE_CRYPTO_WAIT(wait);
u8 iv[16]; /**< AES-256-XTS 使用 16 字节 IV */
u8 key[64]; /**< AES-256-XTS 使用 64 字节密钥 */
int err;
/* 1. 分配 TFM 并设置密钥 */
tfm = crypto_alloc_skcipher("xts(aes)", 0, 0);
if (IS_ERR(tfm)) {
pr_err("Error allocating xts(aes) handle: %ld\n", PTR_ERR(tfm));
return PTR_ERR(tfm);
}
get_random_bytes(key, sizeof(key));
err = crypto_skcipher_setkey(tfm, key, sizeof(key));
if (err) {
pr_err("Error setting key: %d\n", err);
goto out;
}
/* 2. 分配请求对象 */
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!req) {
err = -ENOMEM;
goto out;
}
/* 3. 准备输入数据 */
data = kmalloc(datasize, GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto out;
}
get_random_bytes(data, datasize);
/* 4. 初始化 IV */
get_random_bytes(iv, sizeof(iv));
/* 5. 配置并执行加密请求 */
sg_init_one(&sg, data, datasize);
skcipher_request_set_callback(req,
CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, &sg, &sg, datasize, iv);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
if (err)
pr_err("Error encrypting data: %d\n", err);
out:
crypto_free_skcipher(tfm);
skcipher_request_free(req);
kfree(data);
return err;
}
设计精髓:这个看似冗长的示例体现了内核加密 API 的设计哲学——一切都是显式的。TFM、请求对象、回调、散列列表都由调用者显式分配和管理。这种设计牺牲了易用性,但换来了零拷贝、灵活的回调机制和对硬件卸载的完整支持。
场景调试:如果 crypto_wait_req 永远不返回,检查正确设置了 CRYPTO_TFM_REQ_MAY_SLEEP 标志,并确认不是在原子上下文中调用。对于纯硬件异步实现而忘记提供回调,这会是常见的死锁陷阱。
5.2 使用操作状态内存的 SHASH 示例
/**
* @brief 使用 SHASH 计算散列的完整示例
*
* 该示例展示了如何为 SHASH 操作分配包含操作状态的内存。
* struct sdesc 在 shash_desc 之后附加了算法特定的上下文空间。
*
* @note crypto_shash_descsize() 返回算法所需上下文大小
*/
struct sdesc {
struct shash_desc shash;
char ctx[]; /**< 算法特定的上下文空间 */
};
static struct sdesc *init_sdesc(struct crypto_shash *alg)
{
struct sdesc *sdesc;
int size;
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc)
return ERR_PTR(-ENOMEM);
sdesc->shash.tfm = alg;
return sdesc;
}
static int calc_hash(struct crypto_shash *alg,
const unsigned char *data, unsigned int datalen,
unsigned char *digest)
{
struct sdesc *sdesc;
int ret;
sdesc = init_sdesc(alg);
if (IS_ERR(sdesc)) {
pr_info("can't alloc sdesc\n");
return PTR_ERR(sdesc);
}
ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
kfree(sdesc);
return ret;
}
static int test_hash(const unsigned char *data, unsigned int datalen,
unsigned char *digest)
{
struct crypto_shash *alg;
char *hash_alg_name = "sha1-padlock-nano";
int ret;
alg = crypto_alloc_shash(hash_alg_name, 0, 0);
if (IS_ERR(alg)) {
pr_info("can't alloc alg %s\n", hash_alg_name);
return PTR_ERR(alg);
}
ret = calc_hash(alg, data, datalen, digest);
crypto_free_shash(alg);
return ret;
}
设计精髓:struct sdesc 在 shash_desc 之后使用灵活数组成员 (char ctx[]) 附加上下文空间。这种模式是内核中常见的变长结构体设计。
5.3 随机数生成器使用
/**
* @brief 从内核加密 RNG 获取随机字节
*
* @param buf 输出缓冲区
* @param len 请求的字节数
* @return 成功返回实际生成的字节数,失败返回负 errno
*
* @note 使用 DRBG NOPR SHA-256 作为示例
* @note crypto_rng_get_bytes() 可能返回少于请求的字节数
*/
static int get_random_numbers(u8 *buf, unsigned int len)
{
struct crypto_rng *rng = NULL;
char *drbg = "drbg_nopr_sha256"; /**< 无预测抵抗的 SHA-256 DRBG */
int ret;
if (!buf || !len) {
pr_debug("No output buffer provided\n");
return -EINVAL;
}
rng = crypto_alloc_rng(drbg, 0, 0);
if (IS_ERR(rng)) {
pr_debug("could not allocate RNG handle for %s\n", drbg);
return PTR_ERR(rng);
}
ret = crypto_rng_get_bytes(rng, buf, len);
if (ret < 0)
pr_debug("generation of random numbers failed\n");
else if (ret == 0)
pr_debug("RNG returned no data");
else
pr_debug("RNG returned %d bytes of data\n", ret);
crypto_free_rng(rng);
return ret;
}
第 6 章 内核加密 API 内部结构
6.1 通用 AEAD 密码结构
以下 ASCII 图展示了使用 GCM(AES) 时内核加密 API 的完整层间调用关系:
kernel crypto API | IPSEC Layer | +-----------+ | | | (1) | | aead | <------------------------------------ esp_output | (seqiv) | ---+ | +-----------+ | | | (2) | +-----------+ | | | | <--+ (2) | | aead | <------------------------------------ esp_input | (gcm) | ------------+ | +-----------+ | | | (3) | (5) | v v | +-----------+ +-----------+ | | | | | | | skcipher | | ahash | | | (ctr) | ---+ | (ghash) | | +-----------+ | +-----------+ | | | +-----------+ | (4) | | | <--+ | | cipher | | | (aes) | | +-----------+ |
调用序列:
-
esp_output()调用crypto_aead_encrypt()触发 AEAD 加密,SEQIV 生成 IV。 -
SEQIV 使用 GCM 密码句柄调用 AEAD 操作。
-
GCM 实现调用 CTR(AES) 的 SKCIPHER API。
-
CTR(AES) 调用 CIPHER API 使用 AES 加密单个块。
-
GCM 实现同时调用 GHASH 的 AHASH API 计算认证标签。
/** * @brief GCM 实例化时的内部依赖 * * 在 GCM 句柄实例化期间,CTR(AES) 和 GHASH 被实例化并保存句柄。 * 硬件加速实现(如 AES-NI)可能将 CTR、GHASH 和 AES 合并为 * 单一实现,此时上述分解不再适用。 * * @note 这种分层设计使单一实现可复用于多种密码模式, * 例如 AES 同时被 CTR、CBC、ECB 等模板使用。 */
6.2 通用密钥散列结构
HMAC(SHA256) 的结构更简单:
+-----------+ (1) | | | <------------------ some_function | ahash | | | (hmac) | ---+ | +-----------+ | | | (2) | +-----------+ | | | | <--+ | | shash | | | (sha256) | | +-----------+ |
调用序列:
-
AHASH API 被调用,HMAC 根据需要执行其操作。
-
当 HMAC 需要 SHA256 操作时,通过保存的句柄调用 SHASH API。
第 7 章 异步传输/变换 API (async_tx)
7.1 设计动机与谱系
async_tx API 最初为 MD-RAID5 驱动设计,使用 Intel Xscale I/O 处理器的卸载引擎进行计算。后在 dmaengine 层基础上扩展,支持内存拷贝、XOR、RAID6 P+Q 等操作。
/** * @brief async_tx API 的四大设计特性 * * 1. 隐式同步路径: 用户无需知道平台是否有卸载能力。 * 有硬件则卸载,否则在软件中执行。 * 2. 跨通道依赖链: 允许提交 xor->copy->xor 等依赖操作链。 * API 自动处理硬件通道切换。 * 3. 多客户端支持: dmaengine 支持 memcpy 之外的操作类型。 * 4. 描述符管理: 返回的 dma_async_tx_descriptor 是回收资源, * 需在依赖提交前确认(ACK)。 */
7.2 支持的操作
| 操作 | 描述 |
|---|---|
memcpy |
源缓冲区到目标缓冲区的内存拷贝 |
memset |
用字节值填充目标缓冲区 |
xor |
对多个源缓冲区进行 XOR,结果写入目标 |
xor_val |
对多个源缓冲区进行 XOR,设置零结果标志 |
pq |
从多个源缓冲区生成 RAID6 P+Q 综合征 |
pq_val |
验证 P 和/或 Q 缓冲区与给定源的一致性 |
datap |
从给定源恢复 RAID6 数据块和 P 块 |
2data |
从给定源恢复 2 个 RAID6 数据块 |
7.3 描述符管理
/** * @brief 描述符确认 (ACK) 的三种方法 * * 1. 设置 ASYNC_TX_ACK 标志(如果没有子操作) * 2. 将未确认的描述符作为依赖提交到另一个 async_tx 调用(隐式 ACK) * 3. 调用 async_tx_ack() 显式确认 * * @warning 未确认的描述符不会被卸载引擎回收, * 长期持有会导致资源耗尽。 */
7.4 操作执行与完成时机
-
执行时机:操作不会在
async_<operation>返回后立即执行。卸载引擎驱动批量处理以提高效率。可调用async_tx_issue_pending_all()强制刷新所有通道。 -
完成检测:有两种方式:
-
dma_wait_for_async_tx()— CPU 轮询等待,处理依赖链并刷新待处理操作。 -
完成回调 — 在 tasklet(中断模式)或应用程序上下文(同步模式)中执行。
-
/** * @brief 约束条件 * * 1. async_<operation> 不能在 IRQ 上下文调用。 * 2. 完成回调不能提交新操作(会导致同步路径递归 * 或异步路径自旋锁重复获取)。 */
7.5 独占硬件通道
当应用需要独占 DMA 通道时(如设备到内存操作),可使用 dma_request_channel():
struct dma_chan *dma_request_channel(dma_cap_mask_t mask, dma_filter_fn filter_fn, void *filter_param); /** * @brief filter_fn 回调函数类型 * @param chan 待评估的 DMA 通道 * @param filter_param 用户提供的参数 * @return true 表示选择该通道 * * @note 通过此接口分配的通道是独占的,直到调用 dma_release_channel()。 * 一旦通道被私有分配,即使释放后也不会再被通用分配器使用。 */
第 8 章 非对称/公钥密码学密钥类型
8.1 设计概述
非对称密钥类型是 公钥密码学密钥的容器,不强制特定密码形式或密钥形式。通过子类型定义操作和销毁方法,密钥数据甚至可不在内核存储(如 TPM)。
/** * @brief 核心设计原则 * * - 数据解析器: 负责从传入的二进制数据中提取信息, * 第一个识别格式的解析器设置密钥子类型并定义操作。 * - 密钥可来自本地二进制数据,也可指向硬件(如 TPM、UEFI)。 * - 支持的解析格式示例: OpenPGP、X.509、TPM 指针、UEFI 指针、 * PKCS#8 私钥、PKCS#5 加密私钥。 */
8.2 密钥标识与匹配
/** * @brief 非对称密钥的匹配规则 * * 1. "id:<hexdigits>" — 匹配密钥指纹的尾部 * 例: keyctl search @s asymmetric id:5acc2142 * 2. "<subtype>:<hexdigits>" — 匹配指定子类型的密钥 * 例: keyctl search @s asymmetric tpm:5acc2142 * * @note /proc/keys 显示密钥指纹的最后 8 位十六进制数字和子类型 */
8.3 签名验证操作
/** * @brief 签名验证函数 * * @param key 指向已验证密钥的指针 * @param sig 指向 public_key_signature 结构的指针 * @return 0 表示成功,-EKEYREJECTED 表示签名不匹配, * -ENOTSUPP 表示不支持该算法组合 * * 调用者必须: * 1. 预先计算数据散列值,填入 sig->digest 和 sig->digest_size * 2. 将签名的 MPI 分量填入 sig->mpi[] 并设置 sig->nr_mpi * 3. 在 sig->pkey_hash_algo 中注明散列算法 */ int verify_signature(const struct key *key, const struct public_key_signature *sig);
8.4 非对称密钥子类型
/**
* @brief 子类型定义结构体
*
* 必须实现的操作: describe(), destroy(), query()
* 可选操作: eds_op(), verify_signature()
*
* @note eds_op() 是加密、解密和签名创建的入口点
*/
struct asymmetric_key_subtype {
struct module *owner;
const char *name;
void (*describe)(const struct key *key, struct seq_file *m);
void (*destroy)(void *payload);
int (*query)(const struct kernel_pkey_params *params,
struct kernel_pkey_query *info);
int (*eds_op)(struct kernel_pkey_params *params,
const void *in, void *out);
int (*verify_signature)(const struct key *key,
const struct public_key_signature *sig);
};
8.5 数据解析器
/** * @brief 数据解析器的唯一操作 * * @param prep 指向 key_preparsed_payload 的指针 * @return 0 表示成功,-EBADMSG 表示格式不识别 * * parse() 在密钥创建期间、密钥分配之前调用, * 可提供密钥描述。解析成功时应设置: * - prep->description (密钥描述) * - prep->payload[asym_subtype] (子类型指针) * - prep->payload[asym_crypto] (初始化的子类型数据) * - prep->payload[asym_key_ids] (十六进制指纹) * - prep->quotalen (配额) */ int (*parse)(struct key_preparsed_payload *prep);
8.6 密钥环链接限制
/** * @brief 可用的限制选项字符串 * * - "builtin_trusted": 仅允许被内核内置信任密钥签名的密钥链接 * - "builtin_and_secondary_trusted": 扩展搜索到二级信任密钥环 * - "key_or_keyring:<serial>[:chain]": 仅允许被指定密钥/密钥环 * 中的密钥签名的密钥链接。chain 选项启用证书链验证。 * * @note 如果验证失败: -ENOKEY (未找到父证书), * -EKEYREJECTED (签名检查失败或密钥被拉黑) */
第 9 章 用户空间接口 (AF_ALG)
9.1 设计概述
用户空间接口通过 AF_ALG 套接字 提供。关键差异:用户空间只能作为消费者,不能作为提供者。API 调用始终同步。
/** * @brief AF_ALG 宏定义 * * @note 如果系统头文件未导出,可手动定义: * #define AF_ALG 38 * #define SOL_ALG 279 */
9.2 通用操作流程
/** * @brief AF_ALG 套接字使用步骤 * * 1. socket(AF_ALG, SOCK_SEQPACKET, 0) — 创建套接字 * 2. bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) — 绑定密码 * 3. accept(sockfd, NULL, NULL) — 返回新的文件描述符用于操作 * 4. send/write + read/recv — 执行密码操作 * 5. close(opfds) — 关闭操作描述符和套接字 * * @warning 步骤 3 返回的文件描述符才是实际操作的句柄 */
9.3 消息摘要 API
/**
* @brief 消息摘要的 sockaddr_alg 填充
*/
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "hash", /**< 选择散列逻辑 */
.salg_name = "sha1" /**< 密码名称 */
};
/**
* @brief 消息摘要操作的标志
*
* - MSG_MORE: 类似 update(),不立即计算最终散列
* - 不设置 MSG_MORE: 立即计算最终散列
* - 读取时若缓冲区太小,MSG_TRUNC 被内核设置
* - ALG_SET_KEY 通过 setsockopt 设置 HMAC 密钥
*/
9.4 对称密码 API
/**
* @brief 对称密码的 sockaddr_alg 填充
*/
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "skcipher", /**< 选择对称密码 */
.salg_name = "cbc(aes)" /**< 密码名称 */
};
/**
* @brief sendmsg 中的 cmsghdr 控制信息
*
* - ALG_OP_ENCRYPT / ALG_OP_DECRYPT — 加密/解密
* - ALG_SET_IV — 设置初始化向量 (IV)
* - MSG_MORE — 更多数据预期到达
*
* @note 内核报告 -EINVAL 用于任何意外数据。
* 调用者必须确保数据符合 /proc/crypto 中的约束。
*/
9.5 AEAD 密码 API
/** * @brief AEAD 的内存布局 * * 加密输入: AAD || plaintext * 解密输入: AAD || ciphertext || authentication tag * 加密输出: ciphertext || authentication tag * 解密输出: plaintext * * @note 在使用 sendmsg 前必须通过 setsockopt 设置: * - ALG_SET_KEY (密钥) * - ALG_SET_AEAD_AUTHSIZE (认证标签大小) * * @note 认证解密失败时返回 -EBADMSG */
9.6 随机数生成器 API
/**
* @brief RNG 的 sockaddr_alg 填充
*/
struct sockaddr_alg sa = {
.salg_family = AF_ALG,
.salg_type = "rng", /**< 选择随机数生成器 */
.salg_name = "drbg_nopr_sha256" /**< RNG 名称 */
};
/**
* @brief RNG 操作说明
*
* - 种子通过 setsockopt 设置(使用 ALG_SET_KEY)
* - read/recvmsg 获取随机数,每次最多 128 字节
* - accept() 可多次调用,返回的文件描述符共享相同状态
*
* @warning 需要 CAP_SYS_ADMIN 权限来设置 DRBG 熵
*/
9.7 零拷贝接口
/** * @brief 使用 splice/vmsplice 的零拷贝操作 * * int pipes[2]; * pipe(pipes); * vmsplice(pipes[1], iov, iovlen, SPLICE_F_GIFT); * splice(pipes[0], NULL, opfd, NULL, ret, 0); * read(opfd, out, outlen); * * @note 数据必须在页边界对齐。非对齐数据可用但会降低性能。 * 一次零拷贝操作的最大大小为 16 页。 */
9.8 setsockopt 选项
/** * @brief SOL_ALG 级别的 setsockopt 选项 * * - ALG_SET_KEY: 设置密钥 (skcipher/hash/AEAD/RNG) * - ALG_SET_AEAD_AUTHSIZE: 设置 AEAD 认证标签大小 * - ALG_SET_DRBG_ENTROPY: 设置 DRBG 熵 (需要 CAP_SYS_ADMIN) * * @note 所有选项必须在对应的 send/write 操作之前设置 */
第 10 章 加密引擎 (Crypto Engine)
10.1 设计概述
加密引擎 API 是一个密码队列管理器,专为硬件卸载设计。它管理异步请求队列,允许硬件驱动按顺序处理请求。
/**
* @brief 加密引擎在变换上下文中的位置
*
* 必须在你的 TFM 上下文结构体的起始位置嵌入 struct crypto_engine:
*
* struct your_tfm_ctx {
* struct crypto_engine engine; // 必须是第一个成员
* ...
* };
*
* @note 引擎无法通过 container_of 访问外层上下文,
* 因此要求 struct crypto_engine 必须是第一个成员。
*/
10.2 生命周期管理
/** * @brief 引擎生命周期 * * 1. crypto_engine_alloc_init() — 分配并初始化引擎 * 2. crypto_engine_start() — 启动引擎 * 3. [运行期间处理请求] * 4. crypto_engine_stop() — 停止引擎 * 5. crypto_engine_exit() — 销毁引擎 */
10.3 必须实现的回调函数
| 回调函数 | 描述 |
|---|---|
prepare_crypt_hardware |
在所有 prepare 函数之前调用一次 |
unprepare_crypt_hardware |
在所有 unprepare 函数之后调用一次 |
prepare_cipher_request / prepare_hash_request |
每个请求执行前调用 |
unprepare_cipher_request / unprepare_hash_request |
每个请求处理后调用 |
cipher_one_request / hash_one_request |
处理当前请求的实际操作 |
10.4 请求传输与完成
/** * @brief 请求传输函数 * * 当驱动收到 crypto_request 时,必须通过以下函数之一传输到引擎: * - crypto_transfer_aead_request_to_engine() * - crypto_transfer_akcipher_request_to_engine() * - crypto_transfer_hash_request_to_engine() * - crypto_transfer_kpp_request_to_engine() * - crypto_transfer_skcipher_request_to_engine() * * @brief 请求完成函数 * * 在请求处理结束时调用对应的完成函数: * - crypto_finalize_aead_request() * - crypto_finalize_akcipher_request() * - crypto_finalize_hash_request() * - crypto_finalize_kpp_request() * - crypto_finalize_skcipher_request() */
/** * @brief 从异步请求恢复原始请求的宏 * * container_of(areq, struct yourrequesttype_request, base); * * @note areq 是 struct crypto_async_request 类型 */
第 11 章 API 编程接口参考
内核为每种变换类型提供了专用编程接口文档,具体结构如下:
11.1 接口文档索引
| 接口文档 | 对应头文件 | 描述 |
|---|---|---|
api-skcipher |
<crypto/skcipher.h> |
对称密钥密码 API |
api-aead |
<crypto/aead.h> |
AEAD 密码 API |
api-digest |
<crypto/hash.h> |
消息摘要 API (AHASH/SHASH) |
api-rng |
<crypto/rng.h> |
随机数生成器 API |
api-akcipher |
<crypto/akcipher.h> |
非对称密码 API |
api-kpp |
<crypto/kpp.h> |
密钥协商协议原语 API |
11.2 密钥大小约定
/** * @brief 密钥大小由密钥长度决定 * * 对称密码通常支持多种密钥大小(如 AES-128/192/256)。 * 内核加密 API 不提供单独选择密钥大小的方式, * 而是通过提供的密钥数据长度自动确定。 * * @note 因此,调用 crypto_*_setkey() 时必须提供正确长度的密钥, * 否则操作将返回 -EINVAL。 */
11.3 分配类型与掩码
/** * @brief type 和 mask 参数的含义 * * type 标志: 指定请求的密码算法类型。通常使用 0(默认处理)。 * 可选值: * CRYPTO_ALG_TYPE_CIPHER - 单块密码 * CRYPTO_ALG_TYPE_COMPRESS - 压缩 * CRYPTO_ALG_TYPE_AEAD - AEAD * CRYPTO_ALG_TYPE_KPP - 密钥协商 * CRYPTO_ALG_TYPE_HASH - 原始散列 * CRYPTO_ALG_TYPE_SHASH - 同步多块散列 * CRYPTO_ALG_TYPE_AHASH - 异步多块散列 * CRYPTO_ALG_TYPE_RNG - 随机数生成 * CRYPTO_ALG_TYPE_AKCIPHER - 非对称密码 * CRYPTO_ALG_TYPE_PCOMPRESS - 分段压缩(替代 COMPRESS) * * mask 标志: 限制密码类型。唯一允许的标志: * CRYPTO_ALG_ASYNC - 限制查找为异步密码 * * @warning 错误使用 type/mask 可能导致明明存在的算法被跳过。 */
第 12 章 历史参考:快速可移植 DES 实现
12.1 设计目标
/** * @brief desCore 软件包的设计目标 * * 1. 尽可能高的加密/解密性能 * 2. 可移植到任何字节寻址、具有 32 位无符号 C 类型的主机 * 3. 作为 KERBEROS 低层例程的即插即用替代品 * * @author Dana L. How <how@isl.stanford.edu> * @date 1992 * @license GNU Library General Public License v2 */
12.2 性能优化策略
DES 实现在表中存储预计算的 S 盒结果以加速运算。Quick 模式使用 64KB 表,Small 模式使用 2KB 表:
Quick (64KB 表): 约 30μs 每次加密(无 IP/FP),33μs(FIPS 位序) Small (2KB 表): 约 45μs 每次加密(无 IP/FP),48μs(FIPS 位序) 密钥设置: 约 275μs(使用 1KB 密钥表)
/** * @brief 性能与可移植性的权衡 * * - 表大小: Quick ≈ 64KB, Small ≈ 2KB * - Quick 比 Small 快约 50% * - 但 Quick 需要 32 倍的内存 * * @note 所有表都是机器无关的,不依赖字节序。 */
12.3 可移植性假设
/** * @brief 代码的可移植性假设 * * 1. 一切都是字节寻址的,字节为 8 位(无字节序依赖) * 2. word 类型为 32 位无符号整数 * 3. 字指针可与 char 指针自由转换 * 4. 始终使用 unsigned char 以防高位被设置 */
12.4 可选性能优化
/** * @brief 平台特定的性能优化宏 * * 通过定义以下宏之一选择最接近目标机的配置: * __i386__, __vax__, __mc68000__, __sparc__ * * ROR/ROL 宏可使用汇编实现旋转指令以节省 2 条指令/次使用。 * GCC 通常足够智能,可将 ROR/ROL 翻译为机器旋转指令。 */
12.5 API 使用示例
/** * @brief DES 数据类型说明 * * DesData: 指向 8 字节区域的指针,用于保存密钥和输入/输出块 * DesKeys: 指向 128 字节区域的指针,用于保存完整 768 位密钥, * 必须长字对齐 * * @note 768 位格式的最低两位用于加速某些平台上的加密/解密。 * 不应直接使用 768 位格式,应通过 DesMethod() 转换。 */ /** * @brief 主要函数调用序列 * * 1. DesQuickInit() - 生成 64KB 表(使用 Quick 系列前必须调用) * 2. DesMethod(m, k) - 从标准 56 位密钥生成 768 位密钥 * 3. DesQuickCoreEncrypt() - 执行加密 (无 IP/FP) * DesQuickFipsEncrypt() - 执行加密 (FIPS 标准 IP/FP) * 4. DesQuickDone() - 释放表 * * @note Fips 模式比 Core 模式慢约 10% */
12.6 与 Kerberos 的集成
/** * @brief Kerberos 兼容接口 * * 通过 desUtil.c 中的 des_key_sched() 和 des_ecb_encrypt() * 提供 Kerberos 兼容接口。 * * 将 desCore.a 放在 Kerberos 兼容库之前链接即可替换。 * 无需包含 desCore.h,只需包含 Kerberos 提供的头文件。 */
内核加密框架所体现的几个核心设计原则:算法与使用的解耦、模板化组合、硬件加速的优先级管理、以及对性能与安全性的双重追求。从基础的同步块密码到复杂的异步 AEAD 操作,从内核态的消费者到用户空间的 AF_ALG 套接字,这套 API 在统一框架下提供了完整的密码服务能力。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)