什么是PEM格式?
OpenSSL使用PEM(Privacy Enhanced Mail)格式来存放各种信息,它是OpenSSL默认采用的信息存放方式。OpenSSL中的PEM文件一般包含如下信息:
内容类型:
表明本文件存放的是什么信息内容,它的形式为“-------BEGIN XXXX ------”,与结尾的“------END XXXX------”对应。
头信息:
表明数据是如何被处理和存放,OpenSSL中用的最多的是加密信息,比如加密算法以及初始化向量iv。
信息体:
为BASE64编码后的数据。
举例如下:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,68BA6A10C03BF060
UBTbISKc5ohA7vOoOTDGH8NWUqTGP9F5c4/s17cX8cO6l6d7VjVJKFR6gysA3vhJ
EvECCiZAuyfygLFKmxFCwmNi4jqHo4yCV+NvLY+CHlCB4SZNi9gELG42ARlwbzzi
Q23UMEW+eK5pOOn4+5Ds9GnA2ilbSOlMnU+l2i8tuK9RlqpxGzk9kRQLYT5XFfU4
RsFWliRYQMd2Ii4BwZdMnwVuqxdLsLstyFFJc8POvWVA3FwPOB3aqS57LfFtZKOr
UmJlX/rKJM1sPeYHFqOe9cvYBvsSq7JEhoDumzUb1D35Sp5qEZWWOSG32+knssXg
398tTycZQl6KK3RyY1MK+JBkvm9DyxDOXGM7lqO07+0oEbXRQOktdSuaagfmVHhh
WQu/CEbFRSqaDFVTZdGZesUkpG4K9ROqrRckA0fizk0=
-----END RSA PRIVATE KEY-----
这是后面使用举例代码生成的一个RSA私钥,以PEM格式加密存放,采用了对称加密算法。其中,“-----BEGIN RSA PRIVATE KEY-----”表明了本文件是一个RSA私钥;DES-EDE3-CBC为对称加密算法,68BA6A10C03BF060为对称算法初始化向量iv。
OpenSSL生成PEM格式文件的大致过程如下:
1)将各种数据DER编码。
2)若有需要,对DER编码数据进行加密。
3)对加密后的DER编码数据进行BASE64编码。
4)构造PEM头部格式,包装好BASE64编码后的DER数据,并写入文件。
相应地,OpenSSL读取PEM格式文件的大致过程如下:
1)读取PEM文件内容,解析出文件类型、头部信息、以及BASE64编码后的DER数据。
2)对DER数据进行BASE64解码。
3)查看头部信息,如有需要则要根据加密类型对BASE64解码后的数据进行解密。
4)对解密后的DER编码数据转换为内存结构体。
本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
PEM相关的头文件在pem.h中、源文件在crypto/pem目录中。
主要函数:
int PEM_read_bio_ex(BIO *bp, char **name, char **header,
unsigned char **data, long *len, unsigned int flags);
从bp中读取PEM文件类型(如RSA PRIVATE KEY)、头部缓冲、base64解码后的DER数据缓冲。
从源码实现来看,name、header、data、len不能为NULL。
成功返回1,失败返回0。
int PEM_read_bio(BIO *bp, char **name, char **header,
unsigned char **data, long *len);
PEM_read_bio_ex()的封装版本。
其内部实现如下:
{
return PEM_read_bio_ex(bp, name, header, data, len, PEM_FLAG_EAY_COMPATIBLE);
}
int PEM_read(FILE *fp, char **name, char **header,
unsigned char **data, long *len);
PEM_read_bio()的FILE版本。
其内部实现如下:
{
BIO *b;
int ret;
if ((b = BIO_new(BIO_s_file())) == NULL) {
PEMerr(PEM_F_PEM_READ, ERR_R_BUF_LIB);
return 0;
}
BIO_set_fp(b, fp, BIO_NOCLOSE);
ret = PEM_read_bio(b, name, header, data, len);
BIO_free(b);
return ret;
}
int PEM_write_bio(BIO *bp, const char *name, const char *hdr, const unsigned char *data, long len);
这个函数负责写bp写入PEM文件格式,包括PEM文件类型(如RSA PRIVATE KEY)、头部信息、和BASE64编码后的data内容。
成功返回写入的BASE64编码后的data内容长度,失败返回0。
int PEM_write(FILE *fp, const char *name, const char *hdr, const unsigned char *data, long len);
PEM_write_bio()的FILE版本。
以下不再对FILE版本的内部实现做介绍。
int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher);
根据头部缓冲解析头部加密信息。
成功返回1,失败返回0。
int PEM_do_header(EVP_CIPHER_INFO *cipher, unsigned char *data, long *len, pem_password_cb *callback, void *u);
根据头部加密信息解密DER数据缓冲。
callback表示口令回调函数,u表示口令回调用户参数。可以为callback传入NULL,表示使用默认的回调,如需静默提供口令,u必须为有效的C风格字符串,否则默认回调将要求用户输入口令。
成功返回1,失败返回0。
以上函数一般不直接调用,而是调用其它封装函数。
callback回调函数定义:
typedef int pem_password_cb (char *buf, int size, int rwflag, void *userdata);
buf参数表示PEM_do_header()为回调提供的口令接收缓冲区。
size参数为口令缓冲区的长度,通常为1024字节。
rwflag参数为加解密选项,1表示加密,0表示解密。
userdata为回调参数,由PEM_do_header()传入。
该回调函数要求返回实际口令的长度。
默认的回调函数实现为:
int PEM_def_callback(char *buf, int num, int rwflag, void *userdata)
{
int i, min_len;
const char *prompt;
/* We assume that the user passes a default password as userdata */
if (userdata) {
i = strlen(userdata);
i = (i > num) ? num : i;
memcpy(buf, userdata, i);
return i;
}
prompt = EVP_get_pw_prompt();
if (prompt == NULL)
prompt = "Enter PEM pass phrase:";
/*
* rwflag == 0 means decryption
* rwflag == 1 means encryption
*
* We assume that for encryption, we want a minimum length, while for
* decryption, we cannot know any minimum length, so we assume zero.
*/
min_len = rwflag ? MIN_LENGTH : 0;
i = EVP_read_pw_string_min(buf, min_len, num, prompt, rwflag);
if (i != 0) {
PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
memset(buf, 0, (unsigned int)num);
return -1;
}
return strlen(buf);
}
int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm, const char *name, BIO *bp, pem_password_cb *cb, void *u);
从bp中读取PEM格式内容,解析头部信息,提取有效的DER数据。
pdata和plen为输出的DER数据。
pnm为输出的PEM文件类型(如RSA PRIVATE KEY),可以传NULL,表示不关心输出。
name指定期待的PEM格式。
cb可以为NULL,表示使用默认口令回调。
u为口令C风格字符串,如果传入NULL,将在命令行提示输入。
成功返回1,失败返回0。
它等同于以下3个函数的封装:
PEM_read_bio_ex();
PEM_get_EVP_CIPHER_INFO();
PEM_do_header();
附name指定的PEM格式取值:
# define PEM_STRING_X509_OLD "X509 CERTIFICATE"
# define PEM_STRING_X509 "CERTIFICATE"
# define PEM_STRING_X509_TRUSTED "TRUSTED CERTIFICATE"
# define PEM_STRING_X509_REQ_OLD "NEW CERTIFICATE REQUEST"
# define PEM_STRING_X509_REQ "CERTIFICATE REQUEST"
# define PEM_STRING_X509_CRL "X509 CRL"
# define PEM_STRING_EVP_PKEY "ANY PRIVATE KEY"
# define PEM_STRING_PUBLIC "PUBLIC KEY"
# define PEM_STRING_RSA "RSA PRIVATE KEY"
# define PEM_STRING_RSA_PUBLIC "RSA PUBLIC KEY"
# define PEM_STRING_DSA "DSA PRIVATE KEY"
# define PEM_STRING_DSA_PUBLIC "DSA PUBLIC KEY"
# define PEM_STRING_PKCS7 "PKCS7"
# define PEM_STRING_PKCS7_SIGNED "PKCS #7 SIGNED DATA"
# define PEM_STRING_PKCS8 "ENCRYPTED PRIVATE KEY"
# define PEM_STRING_PKCS8INF "PRIVATE KEY"
# define PEM_STRING_DHPARAMS "DH PARAMETERS"
# define PEM_STRING_DHXPARAMS "X9.42 DH PARAMETERS"
# define PEM_STRING_SSL_SESSION "SSL SESSION PARAMETERS"
# define PEM_STRING_DSAPARAMS "DSA PARAMETERS"
# define PEM_STRING_ECDSA_PUBLIC "ECDSA PUBLIC KEY"
# define PEM_STRING_ECPARAMETERS "EC PARAMETERS"
# define PEM_STRING_ECPRIVATEKEY "EC PRIVATE KEY"
# define PEM_STRING_PARAMETERS "PARAMETERS"
# define PEM_STRING_CMS "CMS"
void *PEM_ASN1_read_bio(d2i_of_void *d2i, const char *name, BIO *bp, void **x, pem_password_cb *cb, void *u);
这个函数表示从bp中读取数据内容,并解析头部、对内容进行BASE64解码、以及对内容按头部信息进行解密(若有需要的话),并将DER数据格式转换为二进制结构。
成功返回有效的二进制结构指针,失败返回NULL。
其内部实现如下:
{
const unsigned char *p = NULL;
unsigned char *data = NULL;
long len;
char *ret = NULL;
if (!PEM_bytes_read_bio(&data, &len, NULL, name, bp, cb, u))
return NULL;
p = data;
ret = d2i(x, &p, len);
if (ret == NULL)
PEMerr(PEM_F_PEM_ASN1_READ_BIO, ERR_R_ASN1_LIB);
OPENSSL_free(data);
return ret;
}
void *PEM_ASN1_read(d2i_of_void *d2i, const char *name, FILE *fp, void **x,
pem_password_cb *cb, void *u);
PEM_ASN1_read_bio()的FILE版本。
int PEM_ASN1_write_bio(i2d_of_void *i2d, const char *name, BIO *bp, void *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
这个函数负责将二进制结构进行DER编码,并根据加密信息生成生成PEM格式内容,输出到bp中。
成功返回1,失败返回0。
name表示PEM文件类型(如RSA PRIVATE KEY)。
enc表示加密方式,若指定为NULL,表示不加密。
enc开启加密时,kstr和klen传入口令,若kstr为空,将由回调输入口令。
int PEM_ASN1_write(i2d_of_void *i2d, const char *name, FILE *fp, void *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *callback, void *u);
PEM_ASN1_write_bio()的FILE版本。
int PEM_SignInit(EVP_MD_CTX *ctx, EVP_MD *type);
int 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);
这几个函数是EVP签名计算的包装,但是对最终签名加入了BASE64编码。
成功返回1,失败返回0。
助记宏函数:
宏声明函数:
# define DECLARE_PEM_read_bio(name, type) \
type *PEM_read_bio_##name(BIO *bp, type **x, pem_password_cb *cb, void *u);
# define DECLARE_PEM_read_fp(name, type) \
type *PEM_read_##name(FILE *fp, type **x, pem_password_cb *cb, void *u);
这2个宏定义了从指定BIO/FILE读取PEM格式数据,并转换为二进制结构。
# define DECLARE_PEM_write_bio(name, type) \
int PEM_write_bio_##name(BIO *bp, type *x);
# define DECLARE_PEM_write_fp(name, type) \
int PEM_write_##name(FILE *fp, type *x);
这2个宏定义了向指定的BIO/FILE生成对应二进制结构的PEM格式数据。
不加密版本。
# define DECLARE_PEM_write_cb_bio(name, type) \
int PEM_write_bio_##name(BIO *bp, type *x, const EVP_CIPHER *enc, \
unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
# define DECLARE_PEM_write_cb_fp(name, type) \
int PEM_write_##name(FILE *fp, type *x, const EVP_CIPHER *enc, \
unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
这2个宏定义了向指定的BIO/FILE生成对应二进制结构的PEM格式数据。
加密版本。
# define DECLARE_PEM_read(name, type) \
DECLARE_PEM_read_bio(name, type) \
DECLARE_PEM_read_fp(name, type)
# define DECLARE_PEM_write(name, type) \
DECLARE_PEM_write_bio(name, type) \
DECLARE_PEM_write_fp(name, type)
# define DECLARE_PEM_write_cb(name, type) \
DECLARE_PEM_write_cb_bio(name, type) \
DECLARE_PEM_write_cb_fp(name, type)
# define DECLARE_PEM_rw(name, type) \
DECLARE_PEM_read(name, type) \
DECLARE_PEM_write(name, type)
# define DECLARE_PEM_rw_cb(name, type) \
DECLARE_PEM_read(name, type) \
DECLARE_PEM_write_cb(name, type)
以上这几个宏是进一步包装。
宏定义函数实现:
# define IMPLEMENT_PEM_read_bio(name, type, str, asn1) \
type *PEM_read_bio_##name(BIO *bp, type **x, pem_password_cb *cb, void *u)\
{ \
return PEM_ASN1_read_bio((d2i_of_void *)d2i_##asn1, str,bp,(void **)x,cb,u); \
}
# define IMPLEMENT_PEM_read_fp(name, type, str, asn1) \
type *PEM_read_##name(FILE *fp, type **x, pem_password_cb *cb, void *u)\
{ \
return PEM_ASN1_read((d2i_of_void *)d2i_##asn1, str,fp,(void **)x,cb,u); \
}
这2个宏实现了从BIO/FILE读取PEM格式,并转换为二进制结构。
# define IMPLEMENT_PEM_write_bio(name, type, str, asn1) \
int PEM_write_bio_##name(BIO *bp, type *x) \
{ \
return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,x,NULL,NULL,0,NULL,NULL); \
}
# define IMPLEMENT_PEM_write_fp(name, type, str, asn1) \
int PEM_write_##name(FILE *fp, type *x) \
{ \
return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,x,NULL,NULL,0,NULL,NULL); \
}
这2个宏定义了向指定的BIO/FILE生成对应二进制结构的PEM格式数据。
不加密版本。
# define IMPLEMENT_PEM_write_cb_bio(name, type, str, asn1) \
int PEM_write_bio_##name(BIO *bp, type *x, const EVP_CIPHER *enc, \
unsigned char *kstr, int klen, pem_password_cb *cb, void *u) \
{ \
return PEM_ASN1_write_bio((i2d_of_void *)i2d_##asn1,str,bp,x,enc,kstr,klen,cb,u); \
}
# define IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1) \
int PEM_write_##name(FILE *fp, type *x, const EVP_CIPHER *enc, \
unsigned char *kstr, int klen, pem_password_cb *cb, void *u) \
{ \
return PEM_ASN1_write((i2d_of_void *)i2d_##asn1,str,fp,x,enc,kstr,klen,cb,u); \
}
这2个宏定义了向指定的BIO/FILE生成对应二进制结构的PEM格式数据。
加密版本。
# define IMPLEMENT_PEM_read(name, type, str, asn1) \
IMPLEMENT_PEM_read_bio(name, type, str, asn1) \
IMPLEMENT_PEM_read_fp(name, type, str, asn1)
# define IMPLEMENT_PEM_write(name, type, str, asn1) \
IMPLEMENT_PEM_write_bio(name, type, str, asn1) \
IMPLEMENT_PEM_write_fp(name, type, str, asn1)
# define IMPLEMENT_PEM_write_cb(name, type, str, asn1) \
IMPLEMENT_PEM_write_cb_bio(name, type, str, asn1) \
IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1)
# define IMPLEMENT_PEM_rw(name, type, str, asn1) \
IMPLEMENT_PEM_read(name, type, str, asn1) \
IMPLEMENT_PEM_write(name, type, str, asn1)
# define IMPLEMENT_PEM_rw_cb(name, type, str, asn1) \
IMPLEMENT_PEM_read(name, type, str, asn1) \
IMPLEMENT_PEM_write_cb(name, type, str, asn1)
这几个为最终的助记宏。
使用举例:
下面这个例子演示了公私钥PEM文件的生成和读取,其中私钥生成和读取时要求输入口令。
#include
#include
#include
#include
#include
#include
namespace dakuang {}
int main(int argc, char* argv[])
{
{
RSA* r = RSA_new();
BIGNUM* bne = BN_new();
BN_set_word(bne, RSA_3);
int ret = RSA_generate_key_ex(r, 512, bne, NULL);
printf("RSA_generate_key_ex ret:[%d] \n", ret);
BN_free(bne);
BIO* bioPri = BIO_new_file("private.pem", "w");
ret = PEM_write_bio_RSAPrivateKey(bioPri, r, EVP_des_ede3_cbc(), NULL, 0, NULL, NULL);
printf("PEM_write_bio_RSAPrivateKey ret:[%d] \n", ret);
BIO_free(bioPri);
BIO* bioPub = BIO_new_file("public.pem", "w");
ret = PEM_write_bio_RSAPublicKey(bioPub, r);
printf("PEM_write_bio_RSAPublicKey ret:[%d] \n", ret);
BIO_free(bioPub);
RSA_free(r);
}
{
BIO* bioPri = BIO_new_file("private.pem", "r");
RSA* rPri = PEM_read_bio_RSAPrivateKey(bioPri, NULL, NULL, NULL);
printf("PEM_read_bio_RSAPrivateKey ret:[%p] \n", rPri);
BIO_free(bioPri);
RSA_free(rPri);
BIO* bioPub = BIO_new_file("public.pem", "r");
RSA* rPub = PEM_read_bio_RSAPublicKey(bioPub, NULL, NULL, NULL);
printf("PEM_read_bio_RSAPublicKey ret:[%p] \n", rPub);
BIO_free(bioPub);
RSA_free(rPub);
}
return 0;
}
输出:
RSA_generate_key_ex ret:[1]
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
PEM_write_bio_RSAPrivateKey ret:[1]
PEM_write_bio_RSAPublicKey ret:[1]
Enter PEM pass phrase:
PEM_read_bio_RSAPrivateKey ret:[0xac7480]
PEM_read_bio_RSAPublicKey ret:[0xaab010]
生成的私钥文件内容:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,68BA6A10C03BF060
UBTbISKc5ohA7vOoOTDGH8NWUqTGP9F5c4/s17cX8cO6l6d7VjVJKFR6gysA3vhJ
EvECCiZAuyfygLFKmxFCwmNi4jqHo4yCV+NvLY+CHlCB4SZNi9gELG42ARlwbzzi
Q23UMEW+eK5pOOn4+5Ds9GnA2ilbSOlMnU+l2i8tuK9RlqpxGzk9kRQLYT5XFfU4
RsFWliRYQMd2Ii4BwZdMnwVuqxdLsLstyFFJc8POvWVA3FwPOB3aqS57LfFtZKOr
UmJlX/rKJM1sPeYHFqOe9cvYBvsSq7JEhoDumzUb1D35Sp5qEZWWOSG32+knssXg
398tTycZQl6KK3RyY1MK+JBkvm9DyxDOXGM7lqO07+0oEbXRQOktdSuaagfmVHhh
WQu/CEbFRSqaDFVTZdGZesUkpG4K9ROqrRckA0fizk0=
-----END RSA PRIVATE KEY-----
生成的公钥文件内容:
-----BEGIN RSA PUBLIC KEY-----
MEYCQQC8R80MraazuvA0S//2YoKBsjpeM5F8Osh0pWAaaWbhk7PqardfWuGk4gND
JDEckYsHxRGgCt6jTKQHzzk57UgHAgED
-----END RSA PUBLIC KEY-----