用户空间接口

介绍

内核加密 API 在内核空间可见的概念也完全适用于用户空间接口。因此,关于内核内用例的内核加密 API 高层讨论也适用于此处。

然而,主要区别在于用户空间只能作为变换或密码算法的消费者,而不能作为提供者。

以下内容涵盖了内核加密 API 导出的用户空间接口。此描述的一个工作示例是 libkcapi,可从 [1] 获取。该库可供需要内核加密服务的用户空间应用程序使用。

然而,内核内加密 API 的某些方面不适用于用户空间。这包括同步调用和异步调用之间的区别。用户空间 API 调用是完全同步的。

[1] https://www.chronox.de/libkcapi.html

用户空间 API 概述

内核加密 API 可从用户空间访问。目前,以下密码算法可访问:

  • 消息摘要,包括带密钥消息摘要 (HMAC, CMAC)

  • 对称密码

  • AEAD 密码

  • 随机数生成器

接口通过 AF_ALG 类型的套接字提供。此外,setsockopt 选项类型为 SOL_ALG。如果用户空间头文件尚未导出这些标志,请使用以下宏:

#ifndef AF_ALG
#define AF_ALG 38
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif

密码算法的访问名称与内核内 API 调用相同。这包括密码算法的通用与唯一命名方案,以及对通用名称的优先级强制执行。

要与内核加密 API 交互,用户空间应用程序必须创建一个套接字。用户空间使用 send()/write() 系统调用系列调用密码操作。密码操作的结果通过 read()/recv() 系统调用系列获取。

以下 API 调用假设套接字描述符已由用户空间应用程序打开,并且只讨论内核加密 API 特定的调用。

要初始化套接字接口,消费者必须执行以下序列:

  1. 创建一个 AF_ALG 类型的套接字,并使用下面为不同密码类型指定的 `struct sockaddr_alg` 参数。

  2. 使用套接字描述符调用 bind

  3. 使用套接字描述符调用 accept。accept 系统调用返回一个新的文件描述符,该描述符将用于与特定密码实例交互。当调用 send/write 或 recv/read 系统调用向内核发送数据或从内核获取数据时,必须使用 accept 返回的文件描述符。

就地密码操作

就像内核加密 API 的内核内操作一样,用户空间接口允许就地进行密码操作。这意味着用于 send/write 系统调用的输入缓冲区和用于 read/recv 系统调用的输出缓冲区可以是同一个。这对于对称密码操作尤其重要,因为可以避免将输出数据复制到最终目的地。

另一方面,如果消费者希望将明文和密文保存在不同的内存位置,消费者只需为加密和解密操作提供不同的内存指针即可。

消息摘要 API

用于密码操作的消息摘要类型在调用 bind 系统调用时选择。bind 要求调用者提供一个已填充的 `struct sockaddr` 数据结构。该数据结构必须按如下方式填充:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "hash", /* this selects the hash logic in the kernel */
    .salg_name = "sha1" /* this is the cipher name */
};

`salg_type` 值“hash”适用于消息摘要和带密钥消息摘要。不过,带密钥消息摘要由相应的 `salg_name` 引用。请参阅下面的 setsockopt 接口,其中解释了如何为带密钥消息摘要设置密钥。

使用 send() 系统调用,应用程序提供应使用消息摘要处理的数据。send 系统调用允许指定以下标志:

  • MSG_MORE:如果设置此标志,send 系统调用将像消息摘要更新函数一样运行,此时最终哈希尚未计算。如果未设置此标志,send 系统调用将立即计算最终消息摘要。

使用 recv() 系统调用,应用程序可以从内核加密 API 读取消息摘要。如果缓冲区对于消息摘要来说太小,内核会设置 MSG_TRUNC 标志。

为了设置消息摘要密钥,调用应用程序必须使用 setsockopt() 的 ALG_SET_KEY 或 ALG_SET_KEY_BY_KEY_SERIAL 选项。如果未设置密钥,则 HMAC 操作将在没有密钥导致的初始 HMAC 状态更改的情况下执行。

对称密码 API

操作与消息摘要的讨论非常相似。在初始化期间,`struct sockaddr` 数据结构必须按如下方式填充:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "skcipher", /* this selects the symmetric cipher */
    .salg_name = "cbc(aes)" /* this is the cipher name */
};

在可以使用 write/send 系统调用系列向内核发送数据之前,消费者必须设置密钥。密钥设置在下面的 setsockopt 调用中描述。

使用 sendmsg() 系统调用,应用程序提供应进行加密或解密处理的数据。此外,IV 通过 sendmsg() 系统调用提供的数据结构指定。

`sendmsg` 系统调用的 `struct msghdr` 参数嵌入到 `struct cmsghdr` 数据结构中。有关 `cmsghdr` 数据结构如何与 send/recv 系统调用系列一起使用的更多信息,请参阅 recv(2) 和 cmsg(3)。该 `cmsghdr` 数据结构包含以下由单独头实例指定的信息:

  • 使用以下标志之一指定密码操作类型:

    • ALG_OP_ENCRYPT - 数据加密

    • ALG_OP_DECRYPT - 数据解密

  • 指定带有 ALG_SET_IV 标志的 IV 信息

send 系统调用系列允许指定以下标志:

  • MSG_MORE:如果设置此标志,send 系统调用将像密码更新函数一样运行,后续的 send 系统调用预计会有更多输入数据。

注意:对于任何意外数据,内核会报告 -EINVAL。调用者必须确保所有数据都与 /proc/crypto 中为所选密码指定的约束匹配。

使用 recv() 系统调用,应用程序可以从内核加密 API 读取密码操作的结果。输出缓冲区的大小必须至少足以容纳所有加密或解密数据块。如果输出数据大小较小,则只会返回能放入该输出缓冲区大小的数据块。

AEAD 密码 API

操作与对称密码的讨论非常相似。在初始化期间,`struct sockaddr` 数据结构必须按如下方式填充:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "aead", /* this selects the symmetric cipher */
    .salg_name = "gcm(aes)" /* this is the cipher name */
};

在可以使用 write/send 系统调用系列向内核发送数据之前,消费者必须设置密钥。密钥设置在下面的 setsockopt 调用中描述。

此外,在可以使用 write/send 系统调用系列向内核发送数据之前,消费者必须设置认证标签大小。要设置认证标签大小,调用者必须使用下面描述的 setsockopt 调用。

使用 sendmsg() 系统调用,应用程序提供应进行加密或解密处理的数据。此外,IV 通过 sendmsg() 系统调用提供的数据结构指定。

`sendmsg` 系统调用的 `struct msghdr` 参数嵌入到 `struct cmsghdr` 数据结构中。有关 `cmsghdr` 数据结构如何与 send/recv 系统调用系列一起使用的更多信息,请参阅 recv(2) 和 cmsg(3)。该 `cmsghdr` 数据结构包含以下由单独头实例指定的信息:

  • 使用以下标志之一指定密码操作类型:

    • ALG_OP_ENCRYPT - 数据加密

    • ALG_OP_DECRYPT - 数据解密

  • 指定带有 ALG_SET_IV 标志的 IV 信息

  • 使用 ALG_SET_AEAD_ASSOCLEN 标志指定相关认证数据 (AAD)。AAD 与明文/密文一起发送到内核。内存结构请参见下文。

send 系统调用系列允许指定以下标志:

  • MSG_MORE:如果设置此标志,send 系统调用将像密码更新函数一样运行,后续的 send 系统调用预计会有更多输入数据。

注意:对于任何意外数据,内核会报告 -EINVAL。调用者必须确保所有数据都与 /proc/crypto 中为所选密码指定的约束匹配。

使用 recv() 系统调用,应用程序可以从内核加密 API 读取密码操作的结果。输出缓冲区的大小必须至少与下面内存结构中定义的大小相同。如果输出数据大小较小,则不执行密码操作。

认证解密操作可能指示完整性错误。这种完整性破坏以 -EBADMSG 错误代码标记。

AEAD 内存结构

AEAD 密码通过以下信息运行,这些信息在用户和内核空间之间作为单个数据流进行通信:

  • 明文或密文

  • 相关认证数据 (AAD)

  • 认证标签

AAD 和认证标签的大小通过 sendmsg 和 setsockopt 调用提供(请参见那里)。由于内核知道整个数据流的大小,因此现在能够计算数据流中数据组件的正确偏移量。

用户空间调用者必须按以下顺序安排上述信息:

  • AEAD 加密输入:AAD || 明文

  • AEAD 解密输入:AAD || 密文 || 认证标签

用户空间调用者提供的输出缓冲区必须至少足够大以容纳以下数据:

  • AEAD 加密输出:密文 || 认证标签

  • AEAD 解密输出:明文

随机数生成器 API

同样,操作与其他 API 非常相似。在初始化期间,`struct sockaddr` 数据结构必须按如下方式填充:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "rng", /* this selects the random number generator */
    .salg_name = "drbg_nopr_sha256" /* this is the RNG name */
};

根据 RNG 类型,RNG 必须进行播种。种子通过 setsockopt 接口设置密钥来提供。例如,ansi_cprng 需要种子。DRBG 不需要种子,但可以播种。在 NIST SP 800-90A 标准中,种子也称为“个性化字符串”(Personalization String)。

使用 read()/recvmsg() 系统调用,可以获取随机数。内核在一次调用中最多生成 128 字节。如果用户空间需要更多数据,则必须进行多次 read()/recvmsg() 调用。

警告:用户空间调用者可以多次调用最初提及的 accept 系统调用。在这种情况下,返回的文件描述符具有相同的状态。

当内核使用 CRYPTO_USER_API_RNG_CAVP 选项构建时,以下 CAVP 测试接口将被启用:

  • 熵 (Entropy) 和 随机数 (Nonce) 的串联可以通过 ALG_SET_DRBG_ENTROPY setsockopt 接口提供给 RNG。设置熵需要 CAP_SYS_ADMIN 权限。

  • 附加数据 (Additional Data) 可以使用 send()/sendmsg() 系统调用提供,但只能在熵设置之后。

零拷贝接口

除了 send/write/read/recv 系统调用系列之外,AF_ALG 接口还可以通过 splice/vmsplice 的零拷贝接口访问。顾名思义,内核尝试避免将数据复制到内核空间。

零拷贝操作要求数据在页边界对齐。也可以使用非对齐数据,但这可能需要内核进行更多操作,从而抵消从零拷贝接口获得的速度增益。

单个零拷贝操作的系统固有大小限制为 16 页。如果需要向 AF_ALG 发送更多数据,用户空间必须将输入数据分割成最大为 16 页的段。

零拷贝可以与以下代码示例一起使用(libkcapi 提供了完整的可工作示例):

int pipes[2];

pipe(pipes);
/* input data in iov */
vmsplice(pipes[1], iov, iovlen, SPLICE_F_GIFT);
/* opfd is the file descriptor returned from accept() system call */
splice(pipes[0], NULL, opfd, NULL, ret, 0);
read(opfd, out, outlen);

Setsockopt 接口

除了 read/recv 和 send/write 系统调用处理以发送和检索受密码操作影响的数据之外,消费者还需要设置密码操作的附加信息。此附加信息通过 setsockopt 系统调用设置,该调用必须使用打开密码的文件描述符(即由 accept 系统调用返回的文件描述符)进行调用。

每次 setsockopt 调用都必须使用 SOL_ALG 级别。

setsockopt 接口允许使用上述 `optname` 设置以下数据:

  • ALG_SET_KEY -- 设置密钥。密钥设置适用于:

    • skcipher 密码类型(对称密码)

    • hash 密码类型(带密钥消息摘要)

    • AEAD 密码类型

    • RNG 密码类型以提供种子

  • ALG_SET_KEY_BY_KEY_SERIAL -- 通过密钥环 key_serial_t 设置密钥。

    此操作与 ALG_SET_KEY 行为相同。解密数据从密钥环密钥中复制,并使用该数据作为对称加密的密钥。

    传入的 key_serial_t 必须设置 KEY_(POS|USR|GRP|OTH)_SEARCH 权限,否则返回 -EPERM。支持的密钥类型有:user、logon、encrypted 和 trusted。

  • ALG_SET_AEAD_AUTHSIZE -- 为 AEAD 密码设置认证标签大小。对于加密操作,将生成给定大小的认证标签。对于解密操作,假定提供的密文包含给定大小的认证标签(参见下面关于 AEAD 内存布局的部分)。

  • ALG_SET_DRBG_ENTROPY -- 设置随机数生成器的熵。此选项仅适用于 RNG 密码类型。

用户空间 API 示例

请参阅 [1] 获取 libkcapi,它提供了对上述 Netlink 内核接口的易用封装。[1] 还包含一个调用所有 libkcapi API 的测试应用程序。

[1] https://www.chronox.de/libkcapi.html