開放SSL

密碼學和 SSL/TLS 工具組

密碼學

名稱

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

簡介

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

功能包括對稱式加密、公開金鑰密碼學、金鑰協商、憑證處理、密碼學雜湊函數、密碼學偽亂數產生器、訊息驗證碼 (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)。這些函數存在於 OpenSSL 3.0 之前的版本中,當時沒有提供明確的擷取功能。

當這些函數與 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。然後透過「初始化」函式初始化操作以準備使用,並可選擇傳入一組參數(使用 OSSL_PARAM(3) 類型)來設定操作的行為方式。接著,在連續的「更新」呼叫中將資料輸入操作。使用「最終」呼叫來完成操作,這通常會提供某種輸出。最後,清除並釋放內容。

以下顯示使用 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 取得副本。