PKCS#12標準描述了個人標識信息的語法,一種交換數字證書的加密標準,包括用戶公鑰、私鑰、證書等。Openssl提供了API供我們解析pfx/p12文件,提取我們需要的信息。
首先我們需要了解幾個數據結構,由於Openssl文檔裏面有些介紹的不是很詳細,在這裏列舉一下:
1、X509 struct
typedef struct x509_st X509; struct x509_st { X509_CINF *cert_info;//證書主體信息 X509_ALGOR *sig_alg;//簽名算法信息 ASN1_BIT_STRING *signature;//CA對證書的簽名值 int valid;//是否是合法證書,1爲合法,0爲未知 int references;//引用次數,被引用一次則加一 char *name;//證書持有者信息,內容形式爲/C=CN/O=ourinfo,該內容在調用d2i_X509的過程中,通過回調函數x509_cb(crypto/asn1/x_x509.c)調用X509_NAME_oneline來設置 CRYPTO_EX_DATA ex_data;//擴展數據結構,用於存放用戶自定義的信息 /* These contain copies of various extension values */ long ex_pathlen;//證書路徑長度,對應擴展項爲NID_basic_constraints long ex_pcpathlen; unsigned long ex_flags;//通過“與”計算存放各種標記 unsigned long ex_kusage;//密鑰用法,對應擴展項爲NID_key_usage unsigned long ex_xkusage;//擴展密鑰用法,對應擴展項爲NID_ext_key_usage unsigned long ex_nscert;//Netscape證書類型,對應擴展項爲NID_netscape_cert_type ASN1_OCTET_STRING *skid;//主體密鑰標識,對應擴展項爲NID_subject_key_identifier struct AUTHORITY_KEYID_st *akid;//頒發者密鑰標識,對應擴展項爲NID_authority_key_identifier X509_POLICY_CACHE *policy_cache;//各種策略緩存,對應的策略爲NID_policy_constraints、NID_certificate_policies、NID_policy_mappings和NID_inhibit_any_policy #ifndef OPENSSL_NO_RFC3779 STACK_OF(IPAddressFamily) *rfc3779_addr; struct ASIdentifiers_st *rfc3779_asid; #endif #ifndef OPENSSL_NO_SHA unsigned char sha1_hash[SHA_DIGEST_LENGTH];//存放證書的sha1摘要值 #endif X509_CERT_AUX *aux;//輔助信息 } /* X509 */;
2、X509_CINF struct
typedef struct x509_cinf_st { ASN1_INTEGER *version;/* 證書版本,0代表V1,1代表V2 */ ASN1_INTEGER *serialNumber;//證書序列號 X509_ALGOR *signature;//簽名算法 X509_NAME *issuer;//頒發者信息 X509_VAL *validity;//有效期 X509_NAME *subject;//擁有者信息 X509_PUBKEY *key;//擁有者的公鑰 ASN1_BIT_STRING *issuerUID;/* [ 1 ] optional in v2 */ ASN1_BIT_STRING *subjectUID;/* [ 2 ] optional in v2 */ STACK_OF(X509_EXTENSION) *extensions;/* [ 3 ] optional in v3 */ } X509_CINF;
3、EVP_PKEY struct
typedef struct evp_pkey_st EVP_PKEY; typedef struct evp_pkey_st { int type; int save_type; int references; union { char *ptr;//存放密鑰結構地址 #ifndef OPENSSL_NO_RSA struct rsa_st *rsa; /* RSA */ #endif #ifndef OPENSSL_NO_DSA struct dsa_st *dsa; /* DSA */ #endif #ifndef OPENSSL_NO_DH struct dh_st *dh; /* DH */ #endif } pkey; int save_parameters; STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */ //存放密鑰屬性 }EVP_PKEY;
4、RSA struct
typedef struct rsa_st RSA; struct rsa_st { /* The first parameter is used to pickup errors where * this is passed instead of aEVP_PKEY, it is set to 0 */ int pad; long version; const RSA_METHOD *meth;//RSA_METHOD結構,指明瞭本RSA密鑰的各種運算函數地址 /* functional reference if 'meth' is ENGINE-provided */ ENGINE *engine;//硬件引擎 BIGNUM *n; //public modulus BIGNUM *e; //public exponent BIGNUM *d; //private exponent BIGNUM *p; //secret prime factor BIGNUM *q; //secret prime factor BIGNUM *dmp1; //d mod (p-1) BIGNUM *dmq1; //d mod (q-1) BIGNUM *iqmp; //q^(-1) mod p /* be careful using this if the RSA structure is shared */ CRYPTO_EX_DATA ex_data;//擴展數據結構,用於存放用戶數據 int references;//RSA結構引用數 int flags; /* Used to cache montgomery values */ BN_MONT_CTX *_method_mod_n; BN_MONT_CTX *_method_mod_p; BN_MONT_CTX *_method_mod_q; /* all BIGNUM values are actually in the following data, if it is not * NULL */ char *bignum_data; BN_BLINDING *blinding; BN_BLINDING *mt_blinding; };
5、BIGNUM struct
typedef struct bignum_st BIGNUM; struct bignum_st { BN_ULONG *d;//BN_ULONG(應系統而異,win32下爲4個字節)數組指針首地址,大數就存放在這裏面,不過是倒放的 int top;//用來指明大數佔多少個BN_ULONG空間 int dmax;//d數組的大小 int neg;//是否爲負數,如果爲1,則是負數,爲0,則爲正數 int flags;//用於存放一些標記,比如flags含有BN_FLG_STATIC_DATA時,表明d的內存是靜態分配的;含有BN_FLG_MALLOCED時,d的內存是動態分配的 };
6、STACK_OF struct
#define STACK_OF(type) STACK typedef struct stack_st { int num;//堆棧中存放數據的個數 char **data;//用於存放數據地址,每個數據地址存放在data[0]到data[num-1]中 int sorted;//堆棧是否已排序,如果排序則值爲1,否則爲0,堆棧數據一般是無序的,只有當用戶調用了sk_sort操作,其值才爲1 int num_alloc;// int (*comp)(const char * const *, const char * const *);//堆棧內存放數據的比較函數地址,此函數用於排序和查找操作 } STACK;
7、PKCS12 struct
typedef struct { ASN1_INTEGER *version; PKCS12_MAC_DATA *mac; PKCS7 *authsafes; } PKCS12;
version爲版本,mac用於存放MAC信息以及對稱密鑰相關的信息,authsafes爲pkcs7結構,用於存放的證書、crl以及私鑰等各種信息
下面是測試代碼,先從PEM中提取信息保存到P12文件中,然後從p12中提取密鑰用來加密和解密數據:
#include <stdio.h> #include <stdlib.h> #include <openssl/md5.h> #include <stdio.h> #include <openssl/rsa.h> #include <openssl/evp.h> #include <openssl/objects.h> #include <openssl/x509.h> #include <openssl/err.h> #include <openssl/pem.h> #include <openssl/pkcs12.h> #include <openssl/ssl.h> extern "C" { #include <openssl/applink.c> } #pragma comment(lib, "libeay32.lib") #pragma comment(lib, "ssleay32.lib") #define CERTS_FILE "mmm.pem" #define CLNT_KEY CERTS_FILE //使用*.p12作爲擴展名會使得Windows操作系統正確認識和處理它 #define PKCS12_FILE "mao1.p12" void print(const char *promptStr,unsigned char *data,int len ) { int i; printf("n==%s[輸出長度=%d]=====n",promptStr,len); for(i = 0; i < len; i++) printf("%02x", data[i]); printf("n=======================n"); } int main(int argc, char *argv[]) { X509 *cert_clnt,*cert_tmp; EVP_PKEY *pkey; EVP_PKEY *pkey_frompkcs12; STACK_OF(X509) *ca_chain=NULL; PKCS12 *pkcs12; FILE *fp; char* pass="123456"; char* name="Client Private/Publication Key"; char plainText[]="[For a test to read/write pkcs12 object]"; unsigned char encData[512]; char decData[512]; int len=0; OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); //seed_prng(); //讀入私鑰對象 if ( NULL == (fp = fopen(CLNT_KEY, "r")) || NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL))) { printf("讀客戶端私鑰出錯"); return -1; } rewind(fp); //讀入證書鏈 ca_chain = sk_X509_new_null(); while(1) { //文件指針被移動,所以循環可以讀取所有證書 if ( NULL==(cert_tmp = PEM_read_X509(fp, NULL, NULL, NULL))) { break; } sk_X509_push(ca_chain, cert_tmp); if( 1==ca_chain->num ) { cert_clnt=cert_tmp;//客戶端證書 } } fclose(fp); printf("%d個證書在證書文件%s中n",ca_chain->num,CERTS_FILE); if(ca_chain->num==0) { printf("沒有證書在%sn",CERTS_FILE); return -1; } //ca_chain=NULL; //創建PKCS12對象 pkcs12=PKCS12_create( pass, //對象保護口令 name, //對象名稱 pkey, //要保護的私鑰 cert_clnt, //對應私鑰的證書對象 ca_chain, //用於驗證證書的證書鏈 0,0,0,0,0 //其它缺省或者未指定參數 ); if( NULL==pkcs12) {printf("創建PKCS12對象時出錯"); return -1; } //將對象寫入文件 if ( NULL == (fp = fopen(PKCS12_FILE, "w")) ) { printf("以寫方式打開文件%s時出錯n",PKCS12_FILE); return -1; } if ( i2d_PKCS12_fp(fp,pkcs12) != 1) {printf("將pkcs12對象寫入文件時出錯n"); return -1; } fclose(fp); if ( NULL == (fp = fopen(PKCS12_FILE, "r")) ) { printf("以讀方式打開文件%s時出錯n",PKCS12_FILE); return -1; } if( NULL==(pkcs12=d2i_PKCS12_fp(fp, NULL)) ) {printf("從文件中讀pkcs12對象時出錯"); return -1; } sk_X509_pop_free(ca_chain,X509_free); // EVP_PKEY_free(pkey);//釋放該公鑰對象 ca_chain=NULL; cert_clnt=NULL; //從PKCS12對象中解析出私鑰,證書鏈,以及證書。 //當然由於pkcs12對象受密碼保護,所以要輸入保護密碼 if( PKCS12_parse(pkcs12,pass, &pkey_frompkcs12,&cert_clnt,&ca_chain)!=1) { printf("解析pkcs12對象時出錯"); return -1; } // PKCS7 *authsafebag = pkcs12->authsafes; // STACK_OF(PKCS12_SAFEBAG)* pkcs12_safebag = PKCS12_unpack_p7data(authsafebag); // char* temp = sk_value(pkcs12_safebag,1); // // PKCS12_SAFEBAG* pkcs12_temp = (PKCS12_SAFEBAG*)temp; // ASN1_OBJECT* type = pkcs12_temp->type; // // int nsize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->n); // unsigned char* ndata = new unsigned char[nsize]; // // int ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->n,ndata); // // int esize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->e); // unsigned char* edata = new unsigned char[esize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->e,edata); // int dsize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->d); // unsigned char* ddata = new unsigned char[dsize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->d,ddata); // int psize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->p); // unsigned char* pdata = new unsigned char[psize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->p,pdata); // int qsize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->q); // unsigned char* qdata = new unsigned char[qsize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->q,qdata); // int dmp1size = BN_num_bytes(pkey_frompkcs12->pkey.rsa->dmp1); // unsigned char* dmp1data = new unsigned char[dmp1size]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->dmp1,dmp1data); // int dmqsize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->dmq1); // unsigned char* dmq1data = new unsigned char[dmqsize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->dmq1,dmq1data); // int idmpsize = BN_num_bytes(pkey_frompkcs12->pkey.rsa->iqmp); // unsigned char* iqmpdata = new unsigned char[idmpsize]; // // ret = BN_bn2bin(pkey_frompkcs12->pkey.rsa->iqmp,iqmpdata); // // delete[] ndata; // delete[] edata; // delete[] ddata; // delete[] pdata; // delete[] qdata; // delete[] dmp1data; // delete[] dmq1data; // delete[] iqmpdata; printf("讀取並解析pkcs12對象成功n pkcs 文件: n" " %sn 證書編號:%dn 證書名:%sn", PKCS12_FILE,ca_chain->num,cert_clnt->name); //利用解析出來的私鑰去加密和解密 len=EVP_PKEY_encrypt(encData,(const unsigned char*)plainText, sizeof(plainText),pkey_frompkcs12); if(len==-1) {printf("EVP_PKEY_encrypt失敗"); return -1; } print("加密後的數據是:",encData,len); //用公鑰解密 len=EVP_PKEY_decrypt((unsigned char*) decData,encData,len,pkey_frompkcs12); if(len==-1) {printf("EVP_PKEY_decrypt 失敗"); return -1; } //print("解密後的數據是:",(unsigned char *)decData,len); printf("n 明文是[長度=%d]:%sn",len,decData); printf("n click any key to continue.");getchar(); return 0; }