亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Openssl之PEM系列

發布時間:2020-08-07 05:19:26 來源:ITPUB博客 閱讀:411 作者:hdgara1 欄目:網絡安全

Openssl之PEM系列

1.PEM編碼文件結構介紹

PEM全稱是Privacy Enhanced Mail,該標準定義了加密一個準備要發送郵件的標準,主要用來將各種對象保存成PEM格式,并將PEM格式的各種對象讀取到相應的結構中。它的基本流程是這樣的:

  1. 信息轉換為ASCII碼或其它編碼方式;
  2. 使用對稱算法加密轉換了的郵件信息;
  3. 使用BASE64對加密后的郵件信息進行編碼;
  4. 使用一些頭定義對信息進行封裝,這些頭信息格式如下(不一定都需要,可選的):

Proc-Type,4:ENCRYPTED

DEK-Info: cipher-name, ivec其中,第一個頭信息標注了該文件是否進行了加密,該頭信息可能的值包括ENCRYPTED(信息已經加密和簽名)、MIC-ONLY(信息經過數字簽名但沒有加密)、MIC-CLEAR(信息經過數字簽名但是沒有加密、也沒有進行編碼,可使用非PEM格式閱讀)以及CLEAR(信息沒有簽名和加密并且沒有進行編碼,該項好象是openssl自身的擴展,但是并沒有真正實現);;第二個頭信息標注了加密的算法以及使用的ivec參量,ivec其實在這兒提供的應該是一個隨機產生的數據序列,與塊加密算法中要使用到的初始化變量(IV)不一樣。

  1. 在這些信息的前面加上如下形式頭標注信息:

—–BEGIN PRIVACY-ENHANCED MESSAGE—–

在這些信息的后面加上如下形式尾標注信息:

—–END PRIVACY-ENHANCED MESSAGE—–

上面是openssl的PEM文件的基本結構,需要注意的是,Openssl并沒有實現PEM的全部標準,它只是對openssl中需要使用的一些選項做了實現,詳細的PEM格式,請參考RFC1421-1424。

下面是一個PEM編碼的經過加密的DSA私鑰的例子:

—–BEGIN DSA PRIVATE KEY—–

Proc-Type: 4,ENCRYPTED

DEK-Info: DES-EDE3-CBC,F80EEEBEEA7386C4

GZ9zgFcHOlnhPoiSbVi/yXc9mGoj44A6IveD4UlpSEUt6Xbse3Fr0KHIUyQ3oGnSmClKoAp/eOTb5Frhto85SzdsxYtac+X1v5XwdzAMy2KowHVk1N8A5jmE2OlkNPNtof132MNlo2cyIRYaa35PPYBGNCmUm7YcYS8O90YtkrQZZTf4+2C4kllhMcdkQwkrFWSWC8YOQ7w0LHb4cX1FejHHom9Nd/0PN3vn3UyySvfOqoR7nbXkrpHXmPIr0hxXRcF0aXcV/CzZ1/nfXWQf4o3+oD0T22SDoVcZY60IzI0oIc3pNCbDV3uKNmgekrFdqOUJ+QW8oWp7oefRx62iBfIeC8DZunohMXaWAQCU0sLQOR4yEdeUCnzCSywe0bG1diD0KYaEe+Yub1BQH4aLsBgDjardgpJRTQLq0DUvw0/QGO1irKTJzegEDNVBKrVnV4AHOKT1CUKqvGNRP1UnccUDTF6miOAtaj/qpzra7sSk7dkGBvIEeFoAg84kfh9hhVvF1YyzC9bwZepruoqoUwke/WdNIR5ymOVZ/4Liw0JdIOcq+atbdRX08niqIRkfdsZrUj4leo3zdefYUQ7w4N2Ns37yDFq7

—–END DSA PRIVATE KEY—–

有時候PEM編碼的東西并沒有經過加密,只是簡單進行了BASE64編碼,下面是一個沒有加密的證書請求的例子:

—–BEGIN CERTIFICATE REQUEST—–

MIICVTCCAhMCAQAwUzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEMMAoGA1UEAxMDUENBMIIBtTCCASkGBSsOAwIMMIIBHgKBgQCnP26Fv0FqKX3wn0cZMJCaCR3aajMexT2GlrMV4FMuj+BZgnOQPnUxmUd6UvuF5NmmezibaIqEm4fGHrV+hktTW1nPcWUZiG7OZq5riDb77Cjcwtelu+UsOSZL2ppwGJU3lRBWI/YV7boEXt45T/23Qx+1pGVvzYAR5HCVW1DNSQIVAPcHMe36bAYD1YWKHKycZedQZmVvAoGATd9MA6aRivUZb1BGJZnlaG8w42nh6bNdmLsohkj83pkEP1+IDJxzJA0gXbkqmj8YlifkYofBe3RiU/xhJ6h7kQmdtvFNnFQPWAbuSXQHzlV+I84W9srcWmEBfslxtU323DQph3j2XiCTs9v15AlsQReVkusBtXOlan7YMu0OArgDgYUAAoGBAKbtuR5AdW+ICjCFe2ixjUiJJzM2IKwe6NZEMXg39+HQ1UTPTmfLZLps+rZfolHDXuRKMXbGFdSF0nXYzotPCzi7GauwEJTZyr27ZZjA1C6apGSQ9GzuwNvZ4rCXystVEagAS8OQ4H3D4dWS17Zg31ICb5o4E5r0z09o/Uz46u0VoAAwCQYFKw4DAhsFAAMxADAuAhUArRubTxsbIXy3AhtjQ943AbNBnSICFQCu+g1iW3jwF+gOcbroD4S/ZcvB3w==

—–END CERTIFICATE REQUEST—–

可以看到,該文件沒有了前面兩個頭信息。大家如果經常使用openssl的應用程序,就對這些文件格式很熟悉了。

2.PEM類型和實現結構介紹

openssl中定義的PEM相關結構體如下(openssl\pem.h),這些結構體是所有PEM系列函數的基礎。

下面定義的是PEM一個高層應用結構,該結構通過PEM_SealInit進行初始化,最后使用PEM_SealFinal進行釋放,該結構定義了PEM中要使用的編碼算法、信息摘要算法以及加密算法。

typedef struct PEM_Encode_Seal_st

{

EVP_ENCODE_CTX encode;

EVP_MD_CTX md;

EVP_CIPHER_CTX cipher;

} PEM_ENCODE_SEAL_CTX;

下面定義了PEM_CTX中的一個子結構,用來保存用戶的信息

typedef struct pem_recip_st

{

char *name;

X509_NAME *dn;

int cipher;

int key_enc;

} PEM_USER;

下面是PEM主結構體PEM_CTX結構的定義,我們將在注釋里面對必要的參數進行說明。

typedef struct pem_ctx_st

{

int type;//結構類型

struct

{

int version;//版本號

int mode;//編碼方式

} proc_type;//Proc_Type字段信息,包括版本號和編碼方式

char *domain;

struct

{

int cipher;

} DEK_info;//定義了PEM中DEK_info字段的信息

PEM_USER *originator;

int num_recipient;

PEM_USER **recipient;

#ifndef OPENSSL_NO_STACK

STACK *x509_chain;//保存證書鏈

#else

char *x509_chain; //保存證書鏈

#endif

EVP_MD *md; //簽名算法類型,指定了信息摘要算法和簽名算法

int md_enc; //信息摘要算法是否進行了加密(簽名)

int md_len; //摘要信息的長度

char *md_data; //摘要信息,可以是經過了加密(簽名)的信息

EVP_CIPHER *dec;//數據加密算法

int key_len; //密鑰長度

unsigned char *key; //加密密鑰

int data_enc; //數據是否加密標志

int data_len; //數據長度

unsigned char *data; //數據

} PEM_CTX;

下面我們對PEM_CTX結構體中一些重要的參數做詳細的說明

2.1 int type參數

該參數指明了PEM_CTX結構的類型,目前包括了以下定義的類型:

#define PEM_OBJ_UNDEF 0

#define PEM_OBJ_X509 1

#define PEM_OBJ_X509_REQ 2

#define PEM_OBJ_CRL 3

#define PEM_OBJ_SSL_SESSION 4

#define PEM_OBJ_PRIV_KEY 10

#define PEM_OBJ_PRIV_RSA 11

#define PEM_OBJ_PRIV_DSA 12

#define PEM_OBJ_PRIV_DH 13

#define PEM_OBJ_PUB_RSA 14

#define PEM_OBJ_PUB_DSA 15

#define PEM_OBJ_PUB_DH 16

#define PEM_OBJ_DHPARAMS 17

#define PEM_OBJ_DSAPARAMS 18

#define PEM_OBJ_PRIV_RSA_PUBLIC 19

可以看到,這些類型基本上包括了所有openssl中要使用的基本結構

2.2 struct proc_type參數

該參數是保存了PEM標準中Proc_Type字段的信息(參考《openssl之PEM系列之1》),可以看到,該結構包括兩個字段,第一個字段version是版本號,第二個字段mode是信息的編碼方式,目前定義了四種,如下:

#define PEM_TYPE_ENCRYPTED 10

#define PEM_TYPE_MIC_ONLY 20

#define PEM_TYPE_MIC_CLEAR 30

#define PEM_TYPE_CLEAR 40

這四個值的意義可以參考《openssl之PEM系列之1》。值得注意是,在openssl實現的PEM文件中,最后一個PEM_TYPE_CLEAR其實并沒有用到。

2.3 struct DEK_info參數

該參數定義了PEM中DEK_info字段的信息,本來該參數應該含有兩個字段,包括加密算法和IV。但是由于歷史原因,openssl中原有的非標準的IV字段在新版的openssl中取消了,所以就剩下一個算法定義了,目前支持的算法如下述的定義:

#define PEM_DEK_DES_CBC 40

#define PEM_DEK_IDEA_CBC 45

#define PEM_DEK_DES_EDE 50

#define PEM_DEK_DES_ECB 60

#define PEM_DEK_RSA 70

#define PEM_DEK_RSA_MD2 80

#define PEM_DEK_RSA_MD5 90

3.PEM系列函數通用參數介紹

PEM系列函數中很多參數是相同意義的,也就是說通用的。本節將對這些通用參數的意義進行介紹,以便于后述章節能夠更方便流暢地進行PEM系列函數的介紹。

3.1 bp參數

如果函數有該參數,則定義了進行數據讀寫BIO接口。

3.2 fp參數

如果函數包含了該參數,則定義了進行數據讀寫的FILE指針。

3.3 TYPE類型參數

PEM讀操作的系列函數都有TYPE **x 和返回TYEP *指針的參數。這里的TYPE可以為任何函數要使用的結構體,如DSA或X509之類的。如果參數x是NULL,那么該參數將被忽略。如果x不是NULL,但是*x是NULL,那么返回的結構體就會寫入到*x里面。如果x和*x都不是NULL,那么函數就試圖重用*x中的結構體。這中函數總是返回一個執行結構體的指針(x的值),如果出錯,就返回NULL。

3.4 enc參數

enc參數定義了PEM函數寫私鑰的時候采用的加密算法。加密是在PEM層進行的。如果該參數為NULL,那么私鑰就會以不加密的形式寫入相應的接口。

3.5 cb參數

cb參數定義了回調函數,該回調函數在加密PEM結構體(一般來說是私鑰)需要口令的時候使用。

3.6 kstr參數

主要在PEM寫系列函數里面使用,如果該參數不為NULL,那么kstr中klen字節數據就用來作為口令,此時,cb參數就被忽略了。

3.7 u參數

如果cb參數為NULL,而u參數不為NULL,那么u參數就是一個以NULL結束的字符串用作口令。如果cb和u參數都是NULL,那么缺省的回調函數就會并使用,該函數一般在當前的終端提示輸入口令,并且關掉了回顯功能。

3.8回調函數callback函數介紹

因為缺省的回調函數基于終端的,有時候不適合使用(如GUI程序),所以可以使用替換的回調函數。回到函數的形式如下:

int cb(char *buf, int size, int rwflag, void *u);

在該函數中,buf是保存口令的參數。size是考慮最大的長度(如buf的長度)。rwflag是一個讀寫標志,0的時候為讀操作,1的時候為寫操作。當rwflag為1的時候,典型的函數一般會要求用戶驗證口令(如輸入兩次)。u參數跟上述PEM函數的u參數意義是一樣的,它允許應用程序使用固定的數據作為參數傳給回調函數。回調函數必須返回口令字符的數目,如果出錯返回0。

4.PEM結構信息處理函數

本次介紹的函數是處理PEM結構里面一些字段信息的函數,這些函數在一般應用中可能不會用到,但是深入一點的應用,恐怕就避免不了。此外,了解這些應用,對于加深對PEM結構的理解也是很有好處的。下面是其中相關一些函數的定義(openssl\pem.h):

int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher);

int PEM_do_header (EVP_CIPHER_INFO *cipher, unsigned char *data,long *len,pem_password_cb *callback,void *u);

void PEM_proc_type(char *buf, int type);

void PEM_dek_info(char *buf, const char *type, int len, char *str);

4.1 PEM_proc_type

該函數是通過給定參數type返回一個標準的PEM文件的Proc-Type字段信息。返回的信息寫入到buf參數里面去,所以要求buf分配的內存空間必須足夠大。事實上,該函數返回的字符串不外乎下面四種結果:

當type為PEM_TYPE_ENCRYPTED,返回字符串為”Proc-Type: 4,ENCRYPTED\n”

當type為PEM_TYPE_MIC_CLEAR,返回字符串為”Proc-Type: 4,MIC-CLEAR\n”

當type為PEM_TYPE_MIC_ONLY,返回字符串為”Proc-Type: 4,MIC-ONLY\n”

當type為其它值時,返回字符串為 “Proc-Type: 4,BAD-TYPE\n”

事實上,雖然上字段信息中有MIC(信息摘要)選項,但openssl的PEM庫并沒有實現MIC計算的功能。當然,可以通過使用RSA-MD系列函數將PEM的數據信息進行摘要并將該結果作為PEM的MIC。你可以通過PEM_dek_info函數產生MIC-info頭信息,然后寫入到PEM結構中,不過據openssl的說明,這需要的時間可能會比較長,大概5分鐘左右。

4.2 PEM_dek_info

該函數跟上述函數相似,是根據type參數生成DEK-info字段的信息,返回并寫入到buf里面。參數str里應該是提供了ivec變量的值,參數len是str的長度(單位是字節)。在這里,參數type應該為加密算法的名字,原則上這個字符串可以是任意的,但是為了其它程序能夠正確解釋該字段,你可以先得到算法相應的NID,然后通過調用nid2sn得到該算法的簡稱作為type參數。例如我們需要在PEM_ASN1_write_bio中使用算法結構enc,那么可以調用下面函數:

objstr=OBJ_nid2sn(EVP_CIPHER_nid(enc));

此時objstr就是一個包含了算法enc的簡稱的字符串。然后我們就可以通過下面的語句在PEM_dek_info函數中使用這個字符串了:

PEM_dek_info(buf,objstr,8,(char *)iv);

4.3 PEM_do_header

該函數并非顧名思義,事實上它完成了對一個PEM編碼對象的的解密工作(如果該PEM對象需要進行解密),該函數通常是被PEM_read_bio所調用的。在調用該函數之前,應該已經將PEM文件的一些頭信息得到,以便于正確進行解密操作。其中,DEK-info字段的信息應該在調用本函數之前進行正確的處理,從而通過該字段的名字和ivec得到相應的EVP_CIPHER結構信息和IV變量,作為本函數的cipher參數。

如果PEM文件沒有DEK-info字段,那么該函數簡單返回1,操作成功,因為不需要進行解密操作。如果不是的話,那么該函數就需要一個口令來進行解密。首先,它會試圖從callback參數(一個回調函數)中得到該口令。回調函數的格式如下:

callback(buffer, blen, verify)

其中,參數buffer是保存返回口令的地方,blen是buffer的最大長度,verify參數是指明是否需要口令驗證(就是要求用戶輸入兩次相同的口令),默認的是0。

如果callback參數為NULL,而u參數不為NULL,那么u參數就會以NULL為結束符的字符串作為口令寫入到buffer中;如果callback和u參數都為NULL,那么就會調用缺省的callback函數(關于u的具體意義,請參考《openssl之PEM系列之3》)。PEM_do_header函數得到口令后,就使用該口令(包括長度信息)跟cipher參數種的ivec變量一起對數據進行解密。解密后的數據保存在data中,長度信息保存在plen中。該函數操作成功返回1,否則返回0。

4.4 PEM_get_EVP_CIPHER_INFO

該函數一般也被PEM_read_bio函數調用。在調用該函數之前,PEM的Proc-Type頭信息應該已經作為明文被讀入到header參數中。如果header為NULL,那么函數成功返回1,因為沒有什么頭信息要處理。如果不為NULL,那么該函數首先確定header信息是否以“Proc-Type:4,ENCRYPTED”開頭,如果是其它形式的,該函數將返回0,不進行處理。之后,函數開始讀取DEK-info字段的信息,然后函數通過該字段的加密算法名字使用EVP_get_cihperbyname得到一個EVP_CIPHER結構,并保存在參數cipher->cipher中;然后函數再通過調用內部的函數得到ivec的值,并保存在cipher->iv中。成功操作返回1,否則返回0。

需要注意的是,因為該函數調用了EVP_get_cipherbyname,所以在調用本函數前,應該先調用EVP_add_cipher和EVP_add_alias,或者調用SSLeay_add_all_algorithms,從而將所有加密算法的信息載入到程序中。具體的情況請參考《openssl之EVP系列》相關章節。

5.PEM信息封裝加密系列函數

該系列函數完成了對PEM對象以及相關密鑰和IV向量的加密編碼工作,以便于數據的保存和傳送,主要包括以下函數(openssl\pem.h):

int PEM_SealInit(PEM_ENCODE_SEAL_CTX *ctx, EVP_CIPHER *type,EVP_MD *md_type, unsigned char **ek, int *ekl,unsigned char *iv, EVP_PKEY **pubk, int npubk);

void PEM_SealUpdate(PEM_ENCODE_SEAL_CTX *ctx, unsigned char *out, int *outl,unsigned char *in, int inl);

int PEM_SealFinal(PEM_ENCODE_SEAL_CTX *ctx, unsigned char *sig,int *sigl,unsigned char *out, int *outl, EVP_PKEY *priv);

void PEM_SignInit(EVP_MD_CTX *ctx, EVP_MD *type);

void PEM_SignUpdate(EVP_MD_CTX *ctx,unsigned char *d,unsigned int cnt);

int PEM_SignFinal(EVP_MD_CTX *ctx, unsigned char *sigret,unsigned int *siglen, EVP_PKEY *pkey);

void ERR_load_PEM_strings();

其中,PEM_Seal*系列函數完成了對PEM對象、密鑰和IV變量的加密編碼工作,PEM_Sign系列函數完成了對PEM進行數字簽名的工作。

5.1 PEM_SealInit函數

該函數為后續的PEM_SealUpdate和PEM_SealFinal函數做初始化工作。首先,該函數使用參數md_type調用函數EVP_SignInit對信息摘要結構ctx->md進行初始化。然后,該函數通過參數type找到相應的EVP_CIPHER結構,產生適用于該算法的密鑰和ivec變量并保存在該算法結構中,然后使用參數pubk的公鑰調用函數EVP_SealInit對該密鑰進行加密。加密后的秘鑰保存在參數ek里面,其長度保存在ekl里面,這些數據都是調用了EVP_EncodeUpdate函數經過了BASE64編碼的。因為密鑰和IV已經保存在ctx->cipher中,所以,可以被后續的函數用來對PEM對象進行加密處理。該函數成功操作返回正值,否則返回0或-1。

需要注意的是,因為本函數也使用了加密算法名字查找算法結構,所以在調用本函數之前必須加載該靜態算法結構棧。

5.2 PEM_SealUpdate函數

該函數用來完成對PEM對象信息體的加密和編碼,使用的加密密鑰是PEM_SealInit函數產生的。該函數對參數in中的inl個字節的數據采用ctx->cipher提供的對稱加密算法結構(已經包含了密鑰和IV)進行加密操作,然后調用EVP_EncodeUpdate進行BASE64編碼后保存在參數out里面,outl是out里有效數據的長度信息。在此同時,該函數也調用函數EVP_SignUpdate函數使用ctx->md的摘要算法結構對參數in里的數據進行了信息摘要操作,不過暫時沒有輸出,等調用了PEM_SealFinal函數的時候進行輸出。

需要注意的是,該函對輸入的信息in的長度做了限制,不能大于1200字節,否則將超過1200字節的信息簡單丟棄。

5.3 PEM_SealFinal函數

該函數完成整個PEM_Seal系列的操作。首先,它完成了之前使用PEM_SealUpdate函數進行處理的數據的對稱加密工作,將數據進行BASE64編碼并輸出到參數out,outl保存了out數據的有效長度。同時,該函數還完成了信息摘要工作,并使用參數priv的私鑰對該信息進行簽名(加密),將結果經過BASE64編碼后輸出到參數sig,sigl是sig有效數據的長度信息。該函數成功操作返回1,否則返回0。

需要注意的是,該函數運行完后,就將ctx->md和ctx->cipher結構釋放清除掉了,所以如果你想保存對稱加密算法使用的密鑰和IV的話,你需要在調用本函數之前就保存一個備份。當然,一般情況下是不會這么做的,因為這些密鑰應該是臨時密鑰,只用來加密一個信息。

5.4 PEM_Seal操作總結

完成上述三個函數的操作之后,你就得到了加密后的密鑰、IV(從PEM_SealInit函數)以及PEM對象信息體,并且這些都是經過BASE64編碼的。然后,你就可以將這些信息發送給接受方了。對方接受到這些信息后,使用他自己的私鑰以及你的公鑰,就能進行正確的數據解密和驗證。

5.5 PEM_SignInit,PEM_SignUpdate和PEM_SignFinal函數

這三個函數完成的功能跟EVP_Sign系列函數是一樣的,其實,前面兩個函數就簡單調用了EVP_SignInit和EVP_SignUpdate函數。PEM_SignFinal則調用EVP_SignFinal函數完成信息摘要和簽名(使用參數pkey的私鑰)之后,調用了EVP_EncodeBlock對簽名信息進行了BASE64編碼,然后將編碼后的簽名信息保存在參數sigret,siglen保存了sigret有效數據的長度。PEM_SignFinal函數成功返回1,否則返回0。

5.6 ERR_load_PEM_strings函數

該函數使用了PEM庫的錯誤代碼信息對錯誤處理庫進行初始化,必須在使用任何PEM系列函數之前調用該函數。

6.PEM底層IO函數

PEM提供了一系列底層的進行數據讀寫操作的IO函數,在后面章節敘述到的PEM對象的IO函數都是這些函數的宏定義,所以雖然一般不要直接調用這些函數,做一個清楚的了解還是必要的。這些函數定義如下(openssl\pem.h):

int PEM_read_bio(BIO *bp, char **name, char **header,unsigned char **data,long *len);

int PEM_write_bio(BIO *bp,const char *name,char *hdr,unsigned char *data,long len);

int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm, const char *name, BIO *bp,pem_password_cb *cb, void *u);

char *PEM_ASN1_read_bio(char *(*d2i)(),const char *name,BIO *bp,char **x,pem_password_cb *cb, void *u);

int PEM_ASN1_write_bio(int (*i2d)(),const char *name,BIO *bp,char *x,const EVP_CIPHER *enc,unsigned char *kstr,int klen,pem_password_cb *cb, void *u);

STACK_OF(X509_INFO) *PEM_X509_INFO_read_bio(BIO *bp, STACK_OF(X509_INFO) *sk, pem_password_cb *cb, void *u);

int PEM_X509_INFO_write_bio(BIO *bp,X509_INFO *xi, EVP_CIPHER *enc,unsigned char *kstr, int klen, pem_password_cb *cd, void *u);

int PEM_read(FILE *fp, char **name, char **header,unsigned char **data,long *len);

int PEM_write(FILE *fp,char *name,char *hdr,unsigned char *data,long len);

char *PEM_ASN1_read(char *(*d2i)(),const char *name,FILE *fp,char **x,pem_password_cb *cb, void *u);

int PEM_ASN1_write(int (*i2d)(),const char *name,FILE *fp,char *x,const EVP_CIPHER *enc,unsigned char *kstr,int klen,pem_password_cb *callback, void *u);

STACK_OF(X509_INFO) *PEM_X509_INFO_read(FILE *fp, STACK_OF(X509_INFO) *sk,pem_password_cb *cb, void *u);

可以看到,這些函數中有很多參數在第3部分介紹過,在此將不再詳細介紹。

6.1 PEM_read函數

該函數從文件fp里面讀取一個PEM編碼的信息。該函數將文件里BEIGIN后面的字符作為對象名保存在參數name里面;將BEGIN所在行和下一個空白行之間的所有信息都讀入到參數header里面,如果之間沒有信息,就將header設置為NULL;然后將信息體進行BASE64解碼放置到data參數里面,len是data參數的有效數據長度。該函數成功返回1,失敗返回0。

6.2 PEM_read_bio函數

該函數完成了跟PEM_read相同的功能,只不過讀取對象是BIO。事實上,PEM_read是通過調用本函數完成其功能的。該函數成功返回1,失敗返回0。

6.3 PEM_write函數

該函數將name參數的數據放在BEGIN頭的后面,寫入到fp文件;之后將參數hdr信息寫入到文件,并在后面寫入一個空白行;最后將data參數len字節的數據進行BASE64編碼,寫入到文件中,并最后加上END頭信息,返回PEM信息體的長度,失敗返回0。

6.4 PEM_write_bio函數

該函數跟PEM_write函數功能一樣,只是操作對象是BIO。事實上,PEM_write函數就是調用本函數完成其功能的。成功返回PEM信息體的長度,失敗返回0。

6.5 PEM_ASN1_read函數

該函數先調用PEM_read函數讀取PEM編碼的對象信息,然后調用PEM_get_EVP_CIPHER_INFO函數處理PEM格式中的DEK-info字段信息,以決定信息采用的加密算法和ivec值;加入PEM信息是加密了的,接下來就調用PEM_do_header函數解密信息體(參考第4部分),然后調用d2i函數將它進行DER解碼轉換成內部定義個類型,保存在x參數中。成功返回指向x的指針,否則返回NULL。

注意,參數name必須是BEIGIN頭后面的PEM文件數據。因為函數調用了PEM_get_EVP_CIPHER_INFO函數,所以為了函數能成功執行,必須在調用本函數前加載算法。雖然事實上任何類型數據都可以進行加密,但一般來說只有RSA私鑰需要加密。本函數可以從一個文件中讀取一些列對象。

6.6 PEM_ASN1_read_bio函數

該函數功能跟PEM_ASN1_read函數一樣,不過操作對象是BIO。事實上,PEM_ASN1_read函數是調用本函數完成其功能的。成功返回指向x的指針,否則返回NULL。

6.7 PEM_ASN1_write函數

該函數將對象x使用i2d參數提供的函數轉換城DER編碼的數據,接下來,如果enc參數不為NULL,就使用enc的加密算法加密這些數據。參數kstr是用來產生加密密鑰的,klen是kstr的有效長度。如果enc不是NULL,但是kstr是NULL,那么就會使用callback函數提示用戶輸入口令并獲取加密數據;如果此時callback為NULL,但是u不為NULL,那么就是使用u作為產生加密密鑰的字符串,假定u應該是NULL結束的字符串;如果callback和u都為NULL,那就會使用缺省的callback函數獲取口令。然后數據就被進行BASE64編碼寫入到fp文件中,加上BEIGIN開始頭信息、END結束頭信息、Type-Proc字段和DEK-info字段(如果數據被加密了)。加密密鑰在函數調用完之后就被清除了。成功操作返回1,否則返回0。

6.8 PEM_ASN1_wirte_bio函數

該函數實現的功能跟PEM_ASN1_write一樣,不過操作對象是BIO。事實上PEM_ASN1_write函數是調用本函數完成其功能的。成功操作返回1,否則返回0。

6.9 PEM_X509_INFO_read函數

該函數完成的功能跟PEM_ASN1_read是一樣的,除了它自動根據BEGIN頭信息調用了相應的d2i系列函數,目前支持的類型d2i_X509、d2i_X509_AUX、d2i_X509_CRL、d2i_RSAPrivateKey和d2i_DSAPrivateKey。該函數會對文件中的所有對象進行處理直到出錯或處理完畢。所有被處理好的對象都保存在堆棧sk中。因為有可能有些對象是加密的,所以提供了參數cb和u。參數cb和u的意義參照第3部分。成功返回處理好的堆棧指針,否則返回NULL。

6.10 PEM_X509_INFO_read_bio函數

該函數完成的功能跟PEM_X509_INFO_read函數一樣,除了操作對象是BIO之外。事實上,PEM_X509_INFO_read函數是調用本函數完成其功能的。成功返回處理好的堆棧指針,否則返回NULL。

6.11 PEM_X509_INFO_write_bio函數

該函數完成的功能也跟PEM_ASN1_write_bio一樣。除了它從參數xi中讀取每一部分對象,分別使用參數xi->x_pkey和xi->x509并使用相應的i2d函數進行PEM編碼成獨立的信息,并寫入到bio中。同樣,可能要求用戶輸入口令生成加密密鑰,相關的參數cb、enc、kstr、klen以及u的意義參考前面的函數以及第3部分。該函數成功返回1,否則返回0。

7.PEM對象讀寫IO函數(一)

openssl基本上為其定義的每種對象都提供了用PEM格式進行讀寫的IO函數。在這種意義上說,PEM格式只是包含了頭信息的BASE64編碼的數據而已。這些函數基本上是基于第6部分所介紹的函數實現的,也就是說,他們多大部分只是這些函數的宏定義而已。因為我們在第3部分已經詳細介紹了PEM系列函數的通用參數,所以本文對這些通用參數不再作詳細的說明。

對于每個對象,openssl一般提供了四個函數,比如名為Name的對象,提供的四個函數名就如下形式:

PEM_read_bio_Name()

PEM_read_Name()

PEM_write_bio_Name()

PEM_write_Name()

可以看到,有兩個是讀操作函數,兩個是寫操作函數。其中,兩個讀操作函數或兩個寫操作函數都是功能相同的,不過就是對象一個為文件句柄,一個為BIO罷了。此外,所有對象的讀函數如果操作成功,返回相應對象的指針,否則返回NULL;而寫函數則成功操作返回非0值,失敗返回0。下面我們對這些函數簡單分類介紹。

7.1 私鑰對象PrivateKey的IO

EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x,pem_password_cb *cb, void *u);

EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x,pem_password_cb *cb, void *u);

int PEM_write_bio_PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

這些函數用PEM格式對一個EVP_PKEY結構的私鑰進行讀寫操作。寫操作函數可以處理RSA或DSA類型的私鑰。讀操作函數還能透明的處理用PKCS#8格式加密和解密的私鑰。

1.往文件中寫入不加密的私鑰的例子

if (!PEM_write_PrivateKey(fp, key, NULL, NULL, 0, 0, NULL))

{

/* 錯誤處理代碼 */

}

2.往BIO中寫入一個私鑰,采用3DES加密,加密口令提示輸入的例子

if (!PEM_write_bio_PrivateKey(bp, key, EVP_des_ede3_cbc(), NULL, 0, 0, NULL))

{

/* 錯誤處理代碼 */

}

3.從BIO重讀取一個私鑰,使用”hello”作為解密口令的例子

key = PEM_read_bio_PrivateKey(bp, NULL, 0, “hello”);

if (key == NULL)

{

/* 錯誤處理代碼 */

}

4.從BIO中讀取一個私鑰,并使用回調函數獲得解密口令的例子

key = PEM_read_bio_PrivateKey(bp, NULL, pass_cb, “My Private Key”);

if (key == NULL)

{

/* 錯誤處理代碼 */

}

8.PEM對象讀寫IO函數(二)

本文繼續介紹PEM對象的讀寫IO函數,請參看第7部分以便更好理解本文。

8.1符合PKCS#8和PKCS#5 v2.0標準的私鑰對象PKCS8PrivateKey的IO

int PEM_write_bio_PKCS8PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc,char *kstr, int klen,pem_password_cb *cb, void *u);

int PEM_write_PKCS8PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,char *kstr, int klen,pem_password_cb *cb, void *u);

這兩個函數使用PKCS#8標準保存EVP_PKEY里面的私鑰到文件或者BIO中,并采用PKCS#5 v2.0的標準加密私鑰。enc參數定義了使用的加密算法。跟其他PEM的IO函數不一樣的是,本函數的加密是基于PKCS#8層次上的,而不是基于PEM信息字段的,所以這兩個函數也是單獨實現的函數,而不是宏定義函數。如果enc參數為NULL,那么就不會執行加密操作,只是使用PKCS#8私鑰信息結構。成功執行返回大于0 的數,否則返回0。

使用這兩個函數保存的PEM對象可以使用上篇文章介紹的PEM_read_bio_PrivateKey或PEM_read_PrivateKey讀出來。

下面是一個將私鑰保存為PKCS#8格式,并使用3DES算法進行加密,使用的口令是”hello”的例子

if (!PEM_write_bio_PKCS8PrivateKey(bp, key, EVP_des_ede3_cbc(), NULL, 0, 0, “hello”))

{

/*出錯處理代碼*/

}

8.2符合PKCS#8和PKCS#5 v1.5或PKCS#12標準的私鑰對象PKCS8PrivateKey的IO

int PEM_write_bio_PKCS8PrivateKey_nid(BIO *bp, EVP_PKEY *x, int nid,char *kstr, int klen,pem_password_cb *cb, void *u);

int PEM_write_PKCS8PrivateKey_nid(FILE *fp, EVP_PKEY *x, int nid,char *kstr, int klen,pem_password_cb *cb, void *u);

這兩個函數也是單獨實現的函數,而不是宏定義函數。他們也是將私鑰保存成PKCS#8格式,但是采用的方式是PKCS#5 v1.5或者PKCS#12進行私鑰的加密。nid參數指定了相應的加密算法,其值應該為相應對象的NID。成功執行返回大于0 的數,否則返回0。

使用這兩個函數保存的PEM對象可以使用上篇文章介紹的PEM_read_bio_PrivateKey或PEM_read_PrivateKey讀出來。

8.3公鑰對象PUBKEY的IO

EVP_PKEY *PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x,pem_password_cb *cb, void *u);

EVP_PKEY *PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x,pem_password_cb *cb, void *u);

int PEM_write_bio_PUBKEY(BIO *bp, EVP_PKEY *x);

int PEM_write_PUBKEY(FILE *fp, EVP_PKEY *x);

這四個函數對EVP_PKEY結構的公鑰進行PEM格式的讀寫處理。公鑰是作為SubjectPublicKeyInfo存儲結構進行編碼的。

8.4 RSA私鑰對象RSAPrivateKey的IO

RSA *PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);

RSA *PEM_read_RSAPrivateKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);

int PEM_write_bio_RSAPrivateKey(BIO *bp, RSA *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

int PEM_write_RSAPrivateKey(FILE *fp, RSA *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

這四個函數對RSA結構的RSA私鑰進行PEM格式的讀寫處理。它使用跟PrivateKey相同的函數進行處理,但如果私鑰類型不是RSA,就會返回錯誤信息。

8.5 RSA公鑰對象RSAPublicKey的IO

RSA *PEM_read_bio_RSAPublicKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);

RSA *PEM_read_RSAPublicKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);

int PEM_write_bio_RSAPublicKey(BIO *bp, RSA *x);

int PEM_write_RSAPublicKey(FILE *fp, RSA *x);

這四個函數是對RSA結構的公鑰進行PEM格式的讀寫處理。本函數使用PKCS#1 RSAPublicKey結構標準對RSA公鑰進行編碼操作。

8.6 RSA公鑰對象RSA_PUBKEY的IO

RSA *PEM_read_bio_RSA_PUBKEY(BIO *bp, RSA **x,pem_password_cb *cb, void *u);

RSA *PEM_read_RSA_PUBKEY(FILE *fp, RSA **x,pem_password_cb *cb, void *u);

int PEM_write_bio_RSA_PUBKEY(BIO *bp, RSA *x);

int PEM_write_RSA_PUBKEY(FILE *fp, RSA *x);

這四個函數也是對RSA結構的公鑰進行PEM格式的讀寫處理。但是本函數使用SubjectPublicKeyInfo結構標準對RSA公鑰進行編碼操作,如果公鑰類型不是RSA,就出錯返回失敗信息。

9.PEM對象讀寫IO函數(三)

本文繼續介紹PEM對象的讀寫IO函數,請參看第7部分和第8部分以便更好理解本文。

9.1 DSA私鑰對象DSAPrivateKey的IO函數

DSA *PEM_read_bio_DSAPrivateKey(BIO *bp, DSA **x,pem_password_cb *cb, void *u);

DSA *PEM_read_DSAPrivateKey(FILE *fp, DSA **x,pem_password_cb *cb, void *u);

int PEM_write_bio_DSAPrivateKey(BIO *bp, DSA *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

int PEM_write_DSAPrivateKey(FILE *fp, DSA *x, const EVP_CIPHER *enc,unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

這些函數對以DSA結構存儲的DSA私鑰進行PEM格式的IO讀寫。它們使用的處理格式跟PrivateKey系列函數是相同的,但是如果私鑰不是DSA類型的,則出錯返回。

9.2 DSA公鑰對象DSA_PUBKEY的IO函數

DSA *PEM_read_bio_DSA_PUBKEY(BIO *bp, DSA **x,pem_password_cb *cb, void *u);

DSA *PEM_read_DSA_PUBKEY(FILE *fp, DSA **x,pem_password_cb *cb, void *u);

int PEM_write_bio_DSA_PUBKEY(BIO *bp, DSA *x);

int PEM_write_DSA_PUBKEY(FILE *fp, DSA *x);

這些函數對以DSA結構存儲的DSA公鑰進行PEM格式的IO讀寫。該公鑰是以SubjectPublicKeyInfo結構進行編碼的,如果公鑰不是DSA類型,則將會出錯返回。

9.3 DSA參數對象DSAParams的IO函數

DSA *PEM_read_bio_DSAparams(BIO *bp, DSA **x, pem_password_cb *cb, void *u);

DSA *PEM_read_DSAparams(FILE *fp, DSA **x, pem_password_cb *cb, void *u);

int PEM_write_bio_DSAparams(BIO *bp, DSA *x);

int PEM_write_DSAparams(FILE *fp, DSA *x);

這些函數對以DSA結構存儲的DSA參數進行PEM格式的IO讀寫操作。

9.4 DH參數對象DHParams的IO函數

DH *PEM_read_bio_DHparams(BIO *bp, DH **x, pem_password_cb *cb, void *u);

DH *PEM_read_DHparams(FILE *fp, DH **x, pem_password_cb *cb, void *u);

int PEM_write_bio_DHparams(BIO *bp, DH *x);

int PEM_write_DHparams(FILE *fp, DH *x);

這些函數對以DH結構保存的DH參數進行PEM格式的IO讀寫操作,這些參數采用了PKCS#3的DH參數結構進行編碼。

9.5 X509證書對象X509的IO函數

X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);

X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u);

int PEM_write_bio_X509(BIO *bp, X509 *x);

int PEM_write_X509(FILE *fp, X509 *x);

這些函數對以X509結構保存的X509證書進行PEM格式的IO讀寫操作,這些函數也可以對信任X509證書進行相同的操作,但是信任設置信息會丟失。

9.6 X509信任證書對象X509_AUX的IO函數

X509 *PEM_read_bio_X509_AUX(BIO *bp, X509 **x, pem_password_cb *cb, void *u);

X509 *PEM_read_X509_AUX(FILE *fp, X509 **x, pem_password_cb *cb, void *u);

int PEM_write_bio_X509_AUX(BIO *bp, X509 *x);

int PEM_write_X509_AUX(FILE *fp, X509 *x);

這些函數對以X509結構保存的信任X509證書進行PEM格式的IO讀寫操作。

9.7 X509證書請求對象X509_REQ的IO函數

X509_REQ *PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x,pem_password_cb *cb, void *u);

X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x,pem_password_cb *cb, void *u);

int PEM_write_bio_X509_REQ(BIO *bp, X509_REQ *x);

int PEM_write_X509_REQ(FILE *fp, X509_REQ *x);

int PEM_write_bio_X509_REQ_NEW(BIO *bp, X509_REQ *x);

int PEM_write_X509_REQ_NEW(FILE *fp, X509_REQ *x);

這些函數對以X509_REQ結構存儲的符合PKCS#10標準的證書請求進行PEM格式的IO讀寫操作。不同的是,X509_REQ系列寫函數使用CERTIFICATE REQUEST作為頭,而X509_REQ_NEW系列寫函數則采用NEW CERTIFICATE REQUEST作為頭(一些CA要求這種格式)。而X509_REQ讀函數對這兩種情況都能處理,所以沒有X509_REQ_NEW的讀函數了。

9.8 X509吊銷列表對象X509_CRL的IO函數

X509_CRL *PEM_read_bio_X509_CRL(BIO *bp, X509_CRL **x,pem_password_cb *cb, void *u);

X509_CRL *PEM_read_X509_CRL(FILE *fp, X509_CRL **x,pem_password_cb *cb, void *u);

int PEM_write_bio_X509_CRL(BIO *bp, X509_CRL *x);

int PEM_write_X509_CRL(FILE *fp, X509_CRL *x);

這些函數對以X509_CRL結構存儲的X509 CRL進行PEM格式的IO讀寫操作。

9.9 PKCS#7編碼內容對象的PKCS7的IO函數

PKCS7 *PEM_read_bio_PKCS7(BIO *bp, PKCS7 **x, pem_password_cb *cb, void *u);

PKCS7 *PEM_read_PKCS7(FILE *fp, PKCS7 **x, pem_password_cb *cb, void *u);

int PEM_write_bio_PKCS7(BIO *bp, PKCS7 *x);

int PEM_write_PKCS7(FILE *fp, PKCS7 *x);

這些漢森對以PKCS7結構存儲的PKCS#7內容信息進行PEM格式的IO讀寫操作。

9.10 Netscape證書序列對象NETSCAPE_CERT_SEQUENCE的IO函數

NETSCAPE_CERT_SEQUENCE *PEM_read_bio_NETSCAPE_CERT_SEQUENCE(BIO *bp,NETSCAPE_CERT_SEQUENCE **x,pem_password_cb *cb, void *u);

NETSCAPE_CERT_SEQUENCE *PEM_read_NETSCAPE_CERT_SEQUENCE(FILE *fp,NETSCAPE_CERT_SEQUENCE **x,pem_password_cb *cb, void *u);

int PEM_write_bio_NETSCAPE_CERT_SEQUENCE(BIO *bp, NETSCAPE_CERT_SEQUENCE *x);

int PEM_write_NETSCAPE_CERT_SEQUENCE(FILE *fp, NETSCAPE_CERT_SEQUENCE *x);

這些函數對以NETSCAPE_CERT_SEQUENCE結構存儲的Netscape證書序列進行PEM格式的IO讀寫操作。

10.回調函數例子
10.1獲取口令回調函數的例子

在PEM讀寫的過程中,特別對于私鑰文件,可能經常要使用到獲取口令的回調函數,在簽名我們介紹的一些列函數也可以看出,基本上都是帶有回調函數的參數的。openssl缺省的回調函數是基于命令行的,在許多情況下可能并不適應,這就要求用戶自己定義回調函數。在前面的相關章節,我們已經介紹了該回調函數的格式,現在我們給出一個回調函數的實現例子。

int pass_cb(char *buf, int size, int rwflag, void *u);

{

int len;

char *tmp;

/* rwflag是一個標準,如果為1,可能還需要作些別的處理工作*/

printf(“輸入口令: \”%s\”\n”, u);

/* 這里應該是得到口令的代碼*/

tmp = “hello”;

len = strlen(tmp);

if (len <= 0) return 0;

/* 如果口令超出給定長度,就把多余的刪掉 */

if (len > size) len = size;

memcpy(buf, tmp, len);

return len;

}

10.2使用PEM系列函數常犯的一個錯誤

PEM系列函數的格式和參數基本相同,下面是一個常犯的導致錯誤的用法。

X509 *x;

PEM_read_bio_X509(bp, &x, 0, NULL);

這樣的用法會導致出現不可預測的錯誤,因為x并沒有進行初始化,分配內存空間,而接下來調用的函數卻會往x里面寫入數據,導致內存非法操作。這也是openssl本身沒有處理好的一個BUG.

Openssl之PEM系列

全球可信CA機構

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

武城县| 青海省| 洛浦县| 遵化市| 淮北市| 崇义县| 栾城县| 中西区| 永川市| 桐乡市| 玉山县| 敦化市| 滁州市| 蓬安县| 壶关县| 博罗县| 乃东县| 阿拉善右旗| 石泉县| 新民市| 万安县| 兴宁市| 鲁甸县| 宝坻区| 珲春市| 色达县| 芒康县| 淳化县| 和林格尔县| 徐汇区| 彩票| 五家渠市| 隆林| 巍山| 灵台县| 玛沁县| 阳春市| 武义县| 大埔区| 邵武市| 巴彦县|