内核加密API架构

密码算法类型

内核加密API为以下密码类型提供不同的API调用:

  • 对称密码

  • AEAD密码

  • 消息摘要,包括带密钥的消息摘要

  • 随机数生成

  • 用户空间接口

密码和模板

内核加密API提供单块密码和消息摘要的实现。此外,内核加密API还提供许多“模板”,可以与单块密码和消息摘要结合使用。模板包括所有类型的块链模式、HMAC机制等。

单块密码和消息摘要可以直接被调用者使用,也可以与模板一起调用,以形成多块密码或带密钥的消息摘要。

一个单块密码甚至可以与多个模板一起调用。但是,模板不能在没有单块密码的情况下使用。

参见/proc/crypto并搜索“name”。例如:

  • aes

  • ecb(aes)

  • cmac(aes)

  • ccm(aes)

  • rfc4106(gcm(aes))

  • sha1

  • hmac(sha1)

  • authenc(hmac(sha1),cbc(aes))

在这些示例中,“aes”和“sha1”是密码,所有其他都是模板。

同步和异步操作

内核加密API提供同步和异步API操作。

当使用同步API操作时,调用者会调用一个密码操作,该操作由内核加密API同步执行。这意味着,调用者会等待直到密码操作完成。因此,内核加密API调用就像常规函数调用一样工作。对于同步操作,API调用的集合很小,并且在概念上类似于任何其他加密库。

异步操作由内核加密API提供,这意味着密码操作的调用将几乎立即完成。该调用会触发密码操作,但它不会发出完成信号。在调用密码操作之前,调用者必须提供一个回调函数,内核加密API可以调用该函数来发出密码操作完成的信号。此外,调用者必须通过对其数据应用适当的锁定来确保其可以处理此类异步事件。内核加密API不执行任何特殊的序列化操作来保护调用者的数据完整性。

加密API密码引用和优先级

调用者通过字符串引用密码。该字符串具有以下语义:

template(single block cipher)

其中“template”和“single block cipher”分别是前面提到的模板和单块密码。如果适用,其他模板可以包含其他模板,例如:

template1(template2(single block cipher)))

内核加密API可能提供模板或单块密码的多种实现。例如,在较新的Intel硬件上,AES有以下实现:AES-NI、汇编器实现或纯C。现在,当使用字符串“aes”与内核加密API时,会使用哪种密码实现?这个问题的答案是内核加密API为每个密码实现分配的优先级数字。当调用者在初始化密码句柄时使用该字符串引用密码时,内核加密API会查找所有提供该名称实现的实现,并选择优先级最高的实现。

现在,调用者可能需要引用特定的密码实现,因此不想依赖基于优先级的选择。为了适应这种情况,内核加密API允许密码实现除了通用名称外,还注册一个唯一名称。当使用该唯一名称时,调用者因此始终可以确保引用预期的密码实现。

/proc/crypto中列出了可用密码列表。然而,该列表并未指定模板和密码的所有可能组合。/proc/crypto中列出的每个块可能包含以下信息——如果以下列出的某个组件不适用于密码,则不显示:

  • name:密码的通用名称,受优先级选择的影响——该名称可用于密码分配API调用(上面列出的所有名称都是此类通用名称的示例)

  • driver:密码的唯一名称——该名称可用于密码分配API调用

  • module:提供密码实现的内核模块(静态链接密码则为“kernel”)

  • priority:密码实现的优先级值

  • refcnt:相应密码的引用计数(即当前使用该密码的消费者数量)

  • selftest:指定密码的自检是否通过

  • type

    • skcipher 用于对称密钥密码

    • cipher 用于可与附加模板一起使用的单块密码

    • shash 用于同步消息摘要

    • ahash 用于异步消息摘要

    • aead 用于AEAD密码类型

    • compression 用于压缩类型转换

    • rng 用于随机数生成器

    • kpp 用于密钥协商协议原语 (KPP) 密码,如ECDH或DH实现

  • blocksize:密码的块大小(以字节为单位)

  • keysize:密钥大小(以字节为单位)

  • ivsize:IV大小(以字节为单位)

  • seedsize:随机数生成器所需的种子数据大小

  • digestsize:消息摘要的输出大小

  • geniv:IV生成器(已废弃)

密钥大小

当分配密码句柄时,调用者只指定密码类型。然而,对称密码通常支持多种密钥大小(例如AES-128 vs. AES-192 vs. AES-256)。这些密钥大小由提供的密钥长度决定。因此,内核加密API不提供单独的方法来选择特定的对称密码密钥大小。

密码分配类型和掩码

不同的密码句柄分配函数允许指定类型和掩码标志。这两个参数具有以下含义(因此在后续部分中不再赘述)。

类型标志指定密码算法的类型。当调用者希望默认处理时,通常提供0。否则,调用者可以提供以下与上述密码类型匹配的选择:

  • CRYPTO_ALG_TYPE_CIPHER 单块密码

  • CRYPTO_ALG_TYPE_AEAD 带关联数据的认证加密 (MAC)

  • CRYPTO_ALG_TYPE_KPP 密钥协商协议原语 (KPP),如ECDH或DH实现

  • CRYPTO_ALG_TYPE_HASH 原始消息摘要

  • CRYPTO_ALG_TYPE_SHASH 同步多块哈希

  • CRYPTO_ALG_TYPE_AHASH 异步多块哈希

  • CRYPTO_ALG_TYPE_RNG 随机数生成

  • CRYPTO_ALG_TYPE_AKCIPHER 非对称密码

  • CRYPTO_ALG_TYPE_SIG 非对称签名

  • CRYPTO_ALG_TYPE_PCOMPRESS CRYPTO_ALG_TYPE_COMPRESS 的增强版本,允许分段压缩/解压缩,而不是仅在一个段上执行操作。一旦现有消费者完成转换,CRYPTO_ALG_TYPE_PCOMPRESS 旨在取代 CRYPTO_ALG_TYPE_COMPRESS。

掩码标志限制密码的类型。唯一允许的标志是CRYPTO_ALG_ASYNC,用于将密码查找功能限制为异步密码。通常,调用者为掩码标志提供0。

当调用者提供掩码和类型规范时,它限制了内核加密API可以为给定密码名称执行的合适密码实现的搜索。这意味着,即使调用者在初始化调用期间使用的密码名称存在,内核加密API也可能由于使用的类型和掩码字段而不会选择它。

内核加密API的内部结构

内核加密API具有内部结构,其中密码实现可能使用许多层和间接方式。本节将帮助澄清内核加密API如何使用各种组件来实现完整的密码。

以下小节根据现有密码实现解释内部结构。第一节解决了最复杂的场景,而所有其他场景都构成其逻辑子集。

通用AEAD密码结构

以下ASCII艺术图分解了使用带有自动IV生成的AEAD密码时内核加密API的层。所示示例由IPSEC层使用。

对于AEAD密码的其他用例,ASCII艺术图也适用,但调用者可能不会将AEAD密码与单独的IV生成器一起使用。在这种情况下,调用者必须生成IV。

所示示例根据通用C实现(gcm.c、aes-generic.c、ctr.c、ghash-generic.c、seqiv.c)分解了GCM(AES)的AEAD密码。通用实现作为示例,展示了内核加密API的完整逻辑。

一些简化的密码实现(如AES-NI)可能会提供合并了内核加密API视图中无法再分解为层的方面的实现。在AES-NI实现的情况下,CTR模式、GHASH实现和AES密码都合并到一个注册到内核加密API的密码实现中。在这种情况下,以下ASCII艺术图描述的概念也适用。然而,内核加密API不再将GCM分解为各个子组件。

以下ASCII艺术图中的每个块都是从内核加密API获得的独立密码实例。调用者或其他块使用内核加密API为密码实现类型定义的API函数访问每个块。

下面的块指示了密码类型以及密码中实现的特定逻辑。

ASCII艺术图还指示了调用结构,即谁调用哪个组件。箭头指向被调用的块,调用者使用适用于为该块指定的密码类型的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)   |
+-----------+

当IPSEC层使用esp_output函数触发加密操作时,以下调用序列适用。在配置期间,管理员将seqiv(rfc4106(gcm(aes)))设置为ESP的密码。以下调用序列在上面的ASCII艺术图中描述:

  1. esp_output() 调用 crypto_aead_encrypt() 来触发带IV生成器的AEAD密码的加密操作。

    SEQIV生成IV。

  2. 现在,SEQIV使用AEAD API函数调用来调用关联的AEAD密码。在我们的例子中,在实例化SEQIV期间,GCM的密码句柄被提供给SEQIV。这意味着SEQIV使用GCM密码句柄调用AEAD密码操作。

    在GCM句柄实例化期间,CTR(AES)和GHASH密码被实例化。CTR(AES)和GHASH的密码句柄被保留以备后用。

    GCM实现负责以正确的方式调用CTR模式AES和GHASH密码,以实现GCM规范。

  3. GCM AEAD密码类型实现现在使用已实例化的CTR(AES)密码句柄调用SKCIPHER API。

    在CTR(AES)密码实例化期间,AES的CIPHER类型实现被实例化。AES的密码句柄被保留。

    这意味着CTR(AES)的SKCIPHER实现只实现CTR块链模式。在执行块链操作后,AES的CIPHER实现被调用。

  4. CTR(AES)的SKCIPHER现在使用AES密码句柄调用CIPHER API来加密一个块。

  5. GCM AEAD实现也通过AHASH API调用GHASH密码实现。

当IPSEC层触发esp_input()函数时,遵循相同的调用序列,唯一区别是操作从步骤(2)开始。

通用块密码结构

通用块密码遵循与上面ASCII艺术图所示相同的概念。

例如,CBC(AES)是使用cbc.c和aes-generic.c实现的。上面的ASCII艺术图也适用,区别仅在于只使用了步骤(4),并且SKCIPHER块链模式是CBC。

通用带密钥消息摘要结构

带密钥消息摘要实现再次遵循与上面ASCII艺术图所示相同的概念。

例如,HMAC(SHA256)是使用hmac.c和sha256_generic.c实现的。以下ASCII艺术图说明了该实现:

kernel crypto API            |       Caller
                             |
+-----------+         (1)    |
|           | <------------------  some_function
|   ahash   |
|   (hmac)  | ---+
+-----------+    |
                 | (2)
+-----------+    |
|           | <--+
|   shash   |
|  (sha256) |
+-----------+

当调用者触发HMAC操作时,以下调用序列适用:

  1. 调用者调用AHASH API函数。HMAC实现根据需要执行其操作。

    在HMAC密码初始化期间,SHA256的SHASH密码类型被实例化。SHA256实例的密码句柄被保留。

    HMAC实现有时需要一个SHA256操作,此时使用SHA256密码句柄。

  2. HMAC实例现在使用SHA256密码句柄调用SHASH API来计算消息摘要。