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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/pem.h>
#include <openssl/rsa.h>

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-----

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容

  • RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥,“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体...
    大匡先生阅读 8,357评论 0 1
  • hash表 数据结构:使用链表数组实现 相关接口 内存分配 内存相关数据结构 内存操作相关接口 CRYPTO_me...
    镜中无我阅读 557评论 0 0
  • 转:http://caisenchen.blog.163.com/blog/static/552865502008...
    right_33cb阅读 4,354评论 0 4
  • OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中...
    大匡先生阅读 11,363评论 0 2
  • ASN.1 – Abstract Syntax Notation dot one,抽象记法1,在大部分的书写场合会...
    大匡先生阅读 8,012评论 0 4