OpenSSL

密碼學和 SSL/TLS 工具包

ossl-guide-libcrypto-introduction

名稱

ossl-guide-libcrypto-introduction、crypto - OpenSSL 指南:libcrypto 簡介

簡介

OpenSSL 密碼學函式庫 (libcrypto) 可存取各種不同的密碼學演算法,這些演算法用於各種網際網路標準。此函式庫提供的服務由 OpenSSL 的 TLS 和 CMS 實作使用,也已用於實作許多其他第三方產品和通訊協定。

功能包括對稱式加密、公開金鑰密碼學、金鑰協商、憑證處理、密碼學雜湊函數、密碼學偽亂數產生器、訊息驗證碼 (MAC)、金鑰衍生函數 (KDF) 和各種公用程式。

演算法

在 OpenSSL 中,SHA256 摘要或 AES 加密等密碼學基本元件稱為「演算法」。每個演算法可能有多個實作可供使用。例如,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(),來指示在各種函式呼叫中要使用的演算法。如果您將這些便利函式之一的傳回值傳遞給某個操作,則表示您正在使用隱式擷取。如果您正在轉換與 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);

將舊版提供者載入預設內容,然後從中擷取 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 示範中找到其他範例(請參閱 "DEMO APPLICATIONS" in 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 The OpenSSL Project Authors。保留所有權利。

根據 Apache 授權條款 2.0(「授權條款」)授權。您不得使用此檔案,除非符合授權條款。您可以在原始程式碼散佈中的 LICENSE 檔案或 https://www.openssl.org/source/license.html 取得副本。