第 1 章 内核加密 API 概述

1.1 设计目标与定位

Linux 内核加密 API(Kernel Crypto API)不仅是一个密码算法库,更是一套可扩展的密码服务框架。其核心设计围绕三个目标:

  1. 统一接口:为所有密码算法提供一致的 API,屏蔽底层实现差异(软件/硬件、同步/异步)。

  2. 算法无关性:消费者通过字符串名称引用算法,不依赖具体实现。

  3. 可组合性:通过模板(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 中对应算法的 driverpriority。若硬件驱动的优先级意外低于软件实现,需检查 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 调用。如果在 initfinal 之间发生了硬件卸载的中断,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 sdescshash_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)   |                                   |
+-----------+                                   |

调用序列

  1. esp_output() 调用 crypto_aead_encrypt() 触发 AEAD 加密,SEQIV 生成 IV。

  2. SEQIV 使用 GCM 密码句柄调用 AEAD 操作。

  3. GCM 实现调用 CTR(AES) 的 SKCIPHER API。

  4. CTR(AES) 调用 CIPHER API 使用 AES 加密单个块。

  5. 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) |                      |
+-----------+                      |

调用序列

  1. AHASH API 被调用,HMAC 根据需要执行其操作。

  2. 当 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() 强制刷新所有通道。

  • 完成检测:有两种方式:

    1. dma_wait_for_async_tx() — CPU 轮询等待,处理依赖链并刷新待处理操作。

    2. 完成回调 — 在 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 在统一框架下提供了完整的密码服务能力。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐