OpenSSL

密码学和 SSL/TLS 工具包

ossl-guide-libcrypto-introduction

名称

ossl-guide-libcrypto-introduction, crypto - OpenSSL 指南:Libcrypto 简介

简介

OpenSSL 密码库 (libcrypto) 提供对各种互联网标准中使用的广泛密码算法的访问。该库提供的服务被 OpenSSL 的 TLS 和 CMS 实现使用,并且也被用于实现许多其他第三方产品和协议。

其功能包括对称加密、公钥密码学、密钥协商、证书处理、密码哈希函数、密码伪随机数生成器、消息认证码 (MAC)、密钥派生函数 (KDF) 和各种实用程序。

算法

诸如 SHA256 摘要或 AES 加密之类的密码原语在 OpenSSL 中被称为“算法”。每个算法可能有多种实现可供使用。例如,RSA 算法可作为适用于一般用途的“默认”实现使用,以及已针对 FIPS 140 标准验证过的“fips”实现(在需要的情况下)。第三方也可以添加其他实现,例如在硬件安全模块 (HSM) 中。

算法在提供程序中实现。有关提供程序的信息,请参阅 ossl-guide-libraries-introduction(7)

操作

不同的算法可以根据其目的进行分组。例如,有用于加密的算法,也有用于对数据进行摘要的算法。在 OpenSSL 中,这些不同的组被称为“操作”。每个操作都有一组与其关联的不同函数。例如,要使用 AES(或任何其他加密算法)执行加密操作,可以使用 EVP_EncryptInit(3) 页面上详细说明的加密函数。或者,要使用 SHA256 执行摘要操作,可以使用 EVP_DigestInit(3) 页面上的摘要函数。

算法获取

为了使用算法,必须首先“获取”其实现。获取是指查看可用的实现、应用选择标准(通过属性查询字符串)以及最终选择要使用的实现的过程。

OpenSSL 支持两种类型的获取 - “显式获取”“隐式获取”

显式获取

显式获取涉及直接调用特定 API 从提供程序中获取算法实现。然后可以将此获取的对象传递给其他 API。这些显式获取函数通常具有 APINAME_fetch 的名称,其中 APINAME 是操作的名称。例如,EVP_MD_fetch(3) 可用于显式获取摘要算法实现。用户负责使用 APINAME_free 在不再需要时释放从 APINAME_fetch 函数返回的对象。

这些获取函数遵循相当常见的模式,其中传递了三个参数

库上下文

有关更详细的说明,请参阅 OSSL_LIB_CTX(3)。这可以是 NULL,表示默认(全局)库上下文,也可以是用户创建的上下文。获取函数只考虑在此库上下文中加载的提供程序(请参阅 OSSL_PROVIDER_load(3))。如果在此库上下文中未加载任何提供程序,则将加载默认提供程序作为后备(请参阅 OSSL_PROVIDER-default(7))。

标识符

对于所有当前实现的获取函数,这是算法名称。每个提供程序都支持算法实现列表。有关每个提供程序中可用算法实现的信息,请参阅提供程序特定文档:“OSSL_PROVIDER-default(7) 中的“操作和算法”“OSSL_PROVIDER-FIPS(7) 中的“操作和算法”“OSSL_PROVIDER-legacy(7) 中的“操作和算法” 以及 “OSSL_PROVIDER-base(7) 中的“操作和算法”

请注意,虽然提供程序可以使用带有冒号分隔名称列表的字符串来注册算法列表中的算法,但目前不支持使用该格式获取算法。

属性查询字符串

用于指导算法实现选择的属性查询字符串。请参阅 “ossl-guide-libraries-introduction(7) 中的“属性查询字符串”

然后,获取的算法实现可用于使用它们的各种其他函数。例如,EVP_DigestInit_ex(3) 函数以 EVP_MD 对象作为参数,该对象可能已从先前对 EVP_MD_fetch(3) 的调用中返回。

隐式获取

OpenSSL 有许多函数返回没有关联实现的算法对象,例如 EVP_sha256(3)EVP_aes_128_cbc(3)EVP_get_cipherbyname(3)EVP_get_digestbyname(3)。这些函数是为了与 3.0 版之前的 OpenSSL 保持兼容,在该版本中无法使用显式获取。

当它们与 EVP_DigestInit_ex(3)EVP_CipherInit_ex(3) 等函数一起使用时,将使用默认搜索条件隐式获取要使用的实际实现(该条件对库上下文和属性查询字符串使用 NULL)。

在某些情况下,当提供 NULL 算法参数时,也会发生隐式获取。在这种情况下,使用默认搜索条件和与使用上下文一致的算法名称隐式获取算法实现。

使用 EVP_PKEY_CTXEVP_PKEY(3) 的函数,例如 EVP_DigestSignInit(3),都隐式获取实现。通常,要获取的算法是根据使用的密钥类型以及已调用的函数来确定的。

性能

如果您使用相同的算法多次执行相同的操作,建议显式获取一次算法,然后在每次后续使用该算法时重用该显式获取的算法。这通常比每次使用该算法时都隐式获取该算法更快。有关显式获取的示例,请参阅 “在应用程序中使用算法”

在 OpenSSL 3.0 之前,使用诸如 EVP_sha256() 之类的函数(这些函数返回“const”对象)直接指示在各种函数调用中要使用的算法。如果您将其中一个便利函数的返回值传递给操作,则您正在使用隐式获取。如果您正在转换在 OpenSSL 3.0 之前的 OpenSSL 版本中有效的应用程序,请考虑将隐式获取的实例更改为显式获取。

如果未将显式获取的对象传递给操作,则任何隐式获取都将使用内部缓存的预获取对象,但它仍然比直接传递显式获取的对象慢。

以下函数可用于显式获取

EVP_MD_fetch(3)

获取消息摘要/哈希算法实现。

EVP_CIPHER_fetch(3)

获取对称密码算法实现。

EVP_KDF_fetch(3)

获取密钥派生函数 (KDF) 算法实现。

EVP_MAC_fetch(3)

获取消息认证码 (MAC) 算法实现。

EVP_KEM_fetch(3)

获取密钥封装机制 (KEM) 算法实现

OSSL_ENCODER_fetch(3)

获取编码器算法实现(例如,将密钥编码为特定格式)。

OSSL_DECODER_fetch(3)

获取解码器算法实现(例如,从特定格式解码密钥)。

EVP_RAND_fetch(3)

获取伪随机数生成器 (PRNG) 算法实现。

有关可以获取的算法名称列表,请参阅 “OSSL_PROVIDER-default(7) 中的“操作和算法”“OSSL_PROVIDER-FIPS(7) 中的“操作和算法”“OSSL_PROVIDER-legacy(7) 中的“操作和算法” 以及 “OSSL_PROVIDER-base(7) 中的“操作和算法”

获取示例

以下部分提供了一些获取算法实现的示例。

在默认上下文中获取 SHA2-256 的任何可用实现。请注意,某些算法具有别名。因此,“SHA256”和“SHA2-256”是同义词

EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", NULL);
...
EVP_MD_free(md);

在默认上下文中获取 AES-128-CBC 的任何可用实现

EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, "AES-128-CBC", NULL);
...
EVP_CIPHER_free(cipher);

在默认上下文中从默认提供程序中获取 SHA2-256 的实现

EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", "provider=default");
...
EVP_MD_free(md);

在默认上下文中获取不是来自默认提供程序的 SHA2-256 的实现

EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", "provider!=default");
...
EVP_MD_free(md);

在默认上下文中获取最好来自 FIPS 提供程序的 SHA2-256 的实现

EVP_MD *md = EVP_MD_fetch(NULL, "SHA2-256", "provider=?fips");
...
EVP_MD_free(md);

在指定的库上下文中从默认提供程序中获取 SHA2-256 的实现

EVP_MD *md = EVP_MD_fetch(libctx, "SHA2-256", "provider=default");
...
EVP_MD_free(md);

将 legacy 提供程序加载到默认上下文中,然后从该提供程序中获取 WHIRLPOOL 的实现

/* This only needs to be done once - usually at application start up */
OSSL_PROVIDER *legacy = OSSL_PROVIDER_load(NULL, "legacy");

EVP_MD *md = EVP_MD_fetch(NULL, "WHIRLPOOL", "provider=legacy");
...
EVP_MD_free(md);

请注意,在上面的示例中,属性字符串“provider=legacy”是可选的,因为假设未加载其他提供程序,那么“whirlpool”算法的唯一实现是在“legacy”提供程序中。另请注意,如果需要默认提供程序以及其他提供程序,则应显式加载默认提供程序

/* This only needs to be done once - usually at application start up */
OSSL_PROVIDER *legacy = OSSL_PROVIDER_load(NULL, "legacy");
OSSL_PROVIDER *default = OSSL_PROVIDER_load(NULL, "default");

EVP_MD *md_whirlpool = EVP_MD_fetch(NULL, "whirlpool", NULL);
EVP_MD *md_sha256 = EVP_MD_fetch(NULL, "SHA2-256", NULL);
...
EVP_MD_free(md_whirlpool);
EVP_MD_free(md_sha256);

在应用程序中使用算法

通过使用“EVP” API,密码算法可供应用程序使用。各种操作(例如加密、摘要、消息认证码等)中的每一个都有一组可用于执行操作的 EVP 函数调用。有关更多详细信息,请参阅 evp(7) 页面。

大多数函数都遵循相同的模式。首先创建“上下文”对象。例如,对于摘要操作,您将使用 EVP_MD_CTX,而对于加密/解密操作,您将使用 EVP_CIPHER_CTX。然后通过“init”函数初始化操作,使其可以立即使用 - 可选地传入一组参数(使用 OSSL_PARAM(3) 类型)来配置操作的执行方式。接下来,通过一系列“update”调用将数据输入操作。操作使用“final”调用完成,该调用通常会提供某种输出。最后,上下文将被清理并释放。

以下显示了使用 SHA256 对数据进行摘要的此过程的完整示例。对于其他操作(如加密/解密、签名、消息认证码等),该过程类似。在 OpenSSL 演示中可以找到其他示例(请参阅 “ossl-guide-libraries-introduction(7) 中的“演示应用程序”)。

#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>

int main(void)
{
    EVP_MD_CTX *ctx = NULL;
    EVP_MD *sha256 = NULL;
    const unsigned char msg[] = {
        0x00, 0x01, 0x02, 0x03
    };
    unsigned int len = 0;
    unsigned char *outdigest = NULL;
    int ret = 1;

    /* Create a context for the digest operation */
    ctx = EVP_MD_CTX_new();
    if (ctx == NULL)
        goto err;

    /*
     * Fetch the SHA256 algorithm implementation for doing the digest. We're
     * using the "default" library context here (first NULL parameter), and
     * we're not supplying any particular search criteria for our SHA256
     * implementation (second NULL parameter). Any SHA256 implementation will
     * do.
     * In a larger application this fetch would just be done once, and could
     * be used for multiple calls to other operations such as EVP_DigestInit_ex().
     */
    sha256 = EVP_MD_fetch(NULL, "SHA256", NULL);
    if (sha256 == NULL)
        goto err;

   /* Initialise the digest operation */
   if (!EVP_DigestInit_ex(ctx, sha256, NULL))
       goto err;

    /*
     * Pass the message to be digested. This can be passed in over multiple
     * EVP_DigestUpdate calls if necessary
     */
    if (!EVP_DigestUpdate(ctx, msg, sizeof(msg)))
        goto err;

    /* Allocate the output buffer */
    outdigest = OPENSSL_malloc(EVP_MD_get_size(sha256));
    if (outdigest == NULL)
        goto err;

    /* Now calculate the digest itself */
    if (!EVP_DigestFinal_ex(ctx, outdigest, &len))
        goto err;

    /* Print out the digest result */
    BIO_dump_fp(stdout, outdigest, len);

    ret = 0;

 err:
    /* Clean up all the resources we allocated */
    OPENSSL_free(outdigest);
    EVP_MD_free(sha256);
    EVP_MD_CTX_free(ctx);
    if (ret != 0)
       ERR_print_errors_fp(stderr);
    return ret;
}

编码和解码密钥

许多算法需要使用密钥。可以使用 EVP API 动态生成密钥(例如,请参阅 EVP_PKEY_Q_keygen(3))。但是,通常需要将密钥(或其关联参数)保存到或从某些外部格式(如 PEM 或 DER)中加载(请参阅 openssl-glossary(7))。OpenSSL 使用编码器和解码器来执行此任务。

编码器和解码器与 OpenSSL 中的任何其他算法实现一样,只是算法实现。它们由提供程序实现。OpenSSL 编码器和解码器在默认提供程序中可用。它们也在基本提供程序中复制。

有关编码器的信息,请参见 OSSL_ENCODER_CTX_new_for_pkey(3)。有关解码器的信息,请参见 OSSL_DECODER_CTX_new_for_pkey(3)

除了直接使用编码器/解码器之外,还有一些辅助函数可用于某些众所周知且常用的格式。例如,请参见 PEM_read_PrivateKey(3)PEM_write_PrivateKey(3),以了解有关从 PEM 编码文件读取和写入密钥数据的详细信息。

进一步阅读

请参见 ossl-guide-libssl-introduction(7),以了解使用 libssl 的介绍。

另请参见

openssl(1)ssl(7)evp(7)OSSL_LIB_CTX(3)openssl-threads(7)property(7)OSSL_PROVIDER-default(7)OSSL_PROVIDER-base(7)OSSL_PROVIDER-FIPS(7)OSSL_PROVIDER-legacy(7)OSSL_PROVIDER-null(7)openssl-glossary(7)provider(7)

版权所有 2000-2024 OpenSSL 项目作者。保留所有权利。

根据 Apache 许可证 2.0 版(“许可证”)授权。除非符合许可证,否则您不得使用此文件。您可以在源代码分发中的 LICENSE 文件或 https://www.openssl.org/source/license.html 中获取副本。