用户空间接口

简介

内核加密 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 标志。

为了设置消息摘要密钥,调用应用程序必须使用 ALG_SET_KEY 或 ALG_SET_KEY_BY_KEY_SERIAL 的 setsockopt() 选项。如果未设置密钥,则执行 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() 系统调用提供的数据结构指定。

struct msghdr 的 sendmsg 系统调用参数嵌入到 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() 系统调用提供的数据结构指定。

struct msghdr 的 sendmsg 系统调用参数嵌入到 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 标准中也称为 *个性化字符串*。

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

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

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

  • 随机数的拼接可以通过 ALG_SET_DRBG_ENTROPY setsockopt 接口提供给 RNG。设置熵需要 CAP_SYS_ADMIN 权限。

  • 可以使用 send()/sendmsg() 系统调用提供附加数据,但前提是熵已经被设置。

零拷贝接口

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

零拷贝操作要求数据在页边界对齐。也可以使用未对齐的数据,但这可能需要内核执行更多操作,这将抵消从零拷贝接口获得的加速效果。

一个零拷贝操作的系统固有限制是 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