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_CTX 或 EVP_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 取得副本。