OpenSSL之PEM用法

什么是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-----

你可能感兴趣的:(OpenSSL之PEM用法)