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_constraintslong ex_pcpathlen;unsigned long ex_flags;//通过“与”计算存放各种标记unsigned long ex_kusage;//密钥用法,对应扩展项为NID_key_usageunsigned long ex_xkusage;//扩展密钥用法,对应扩展项为NID_ext_key_usageunsigned long ex_nscert;//Netscape证书类型,对应扩展项为NID_netscape_cert_typeASN1_OCTET_STRING *skid;//主体密钥标识,对应扩展项为NID_subject_key_identifierstruct AUTHORITY_KEYID_st *akid;//颁发者密钥标识,对应扩展项为NID_authority_key_identifierX509_POLICY_CACHE *policy_cache;//各种策略缓存,对应的策略为NID_policy_constraints、NID_certificate_policies、NID_policy_mappings和NID_inhibit_any_policy#ifndef OPENSSL_NO_RFC3779STACK_OF(IPAddressFamily) *rfc3779_addr;struct ASIdentifiers_st *rfc3779_asid;#endif#ifndef OPENSSL_NO_SHAunsigned char sha1_hash[SHA_DIGEST_LENGTH];//存放证书的sha1摘要值#endifX509_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) STACKtypedef 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#include #include #include #include #include #include #include #include #include #include #include extern "C"{#include }#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;}