版权声明:本文根据DragonKing牛,E-Mail:wzhah@263.NET发布在https://openssl.126.com的系列文章整理修改而成(这个网站已经不能访问了),我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!
1 PEM编码文件结构介绍
PEM全称是Privacy Enhanced Mail,该标准定义了加密一个准备要发送邮件的标准,主要用来将各种对象保存成PEM格式,并将PEM格式的各种对象读取到相应的结构中。它的基本流程是这样的:
- 信息转换为ASCII码或其它编码方式;
- 使用对称算法加密转换了的邮件信息;
- 使用BASE64对加密后的邮件信息进行编码;
使用一些头定义对信息进行封装,这些头信息格式如下(不一定都需要,可选的):
`Proc-Type,4:ENCRYPTED` `DEK-Info: cipher-name, ivec`
- 第一个头信息标注了该文件是否进行了加密,该头信息可能的值包括:
- ENCRYPTED——信息已经加密和签名
- MIC-ONLY——信息经过数字签名但没有加密
- MIC-CLEAR——信息经过数字签名但是没有加密、也没有进行编码,可使用非PEM格式阅读
- CLEAR——信息没有签名和加密并且没有进行编码,该项好象是openssl自身的扩展,但是并没有真正实现
- 第二个头信息标注了加密的算法以及使用的ivec参量,ivec其实在这儿提供的应该是一个随机产生的数据序列,与块加密算法中要使用到的初始化变量(IV)不一样。
- 第一个头信息标注了该文件是否进行了加密,该头信息可能的值包括:
在这些信息的前面加上如下形式头标注信息:
—–BEGIN PRIVACY-ENHANCED MESSAGE—–
在这些信息的后面加上如下形式尾标注信息:
—–END PRIVACY-ENHANCED MESSAGE—–
上面是openssl的PEM文件的基本结构,需要注意的是,Openssl并没有实现PEM的全部标准,它只是对openssl中需要使用的一些选项做了实现,详细的PEM格式,请参考RFC1421-1424。
下面是一个PEM编码的经过加密的DSA私钥的例子:
—–BEGIN DSA PRIVATE KEY—–
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,F80EEEBEEA7386C4
GZ9zgFcHOlnhPoiSbVi/yXc9mGoj44A6IveD4UlpSEUt6Xbse3Fr0KHIUyQ3oGnSm
ClKoAp/eOTb5Frhto85SzdsxYtac+X1v5XwdzAMy2KowHVk1N8A5jmE2OlkNPNtof
132MNlo2cyIRYaa35PPYBGNCmUm7YcYS8O90YtkrQZZTf4+2C4kllhMcdkQwkrFWS
WC8YOQ7w0LHb4cX1FejHHom9Nd/0PN3vn3UyySvfOqoR7nbXkrpHXmPIr0hxXRcF0
aXcV/CzZ1/nfXWQf4o3+oD0T22SDoVcZY60IzI0oIc3pNCbDV3uKNmgekrFdqOUJ+
QW8oWp7oefRx62iBfIeC8DZunohMXaWAQCU0sLQOR4yEdeUCnzCSywe0bG1diD0KY
aEe+Yub1BQH4aLsBgDjardgpJRTQLq0DUvw0/QGO1irKTJzegEDNVBKrVnV4AHOKT
1CUKqvGNRP1UnccUDTF6miOAtaj/qpzra7sSk7dkGBvIEeFoAg84kfh9hhVvF1Yyz
C9bwZepruoqoUwke/WdNIR5ymOVZ/4Liw0JdIOcq+atbdRX08niqIRkfdsZrUj4le
o3zdefYUQ7w4N2Ns37yDFq7
—–END DSA PRIVATE KEY—–
有时候PEM编码的东西并没有经过加密,只是简单进行了BASE64编码,下面是一个没有加密的证书请求的例子:
—–BEGIN CERTIFICATE REQUEST—–
MIICVTCCAhMCAQAwUzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxI
TAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEMMAoGA1UEAxMDUENBMI
IBtTCCASkGBSsOAwIMMIIBHgKBgQCnP26Fv0FqKX3wn0cZMJCaCR3aajMexT2GlrM
V4FMuj+BZgnOQPnUxmUd6UvuF5NmmezibaIqEm4fGHrV+hktTW1nPcWUZiG7OZq5r
iDb77Cjcwtelu+UsOSZL2ppwGJU3lRBWI/YV7boEXt45T/23Qx+1pGVvzYAR5HCVW
1DNSQIVAPcHMe36bAYD1YWKHKycZedQZmVvAoGATd9MA6aRivUZb1BGJZnlaG8w42
nh5bNdmLsohkj83pkEP1+IDJxzJA0gXbkqmj8YlifkYofBe3RiU/xhJ6h6kQmdtvF
NnFQPWAbuSXQHzlV+I84W9srcWmEBfslxtU323DQph2j2XiCTs9v15AlsQReVkusB
tXOlan7YMu0OArgDgYUAAoGBAKbtuR5AdW+ICjCFe2ixjUiJJzM2IKwe6NZEMXg39
+HQ1UTPTmfLZLps+rZfolHDXuRKMXbGFdSF0nXYzotPCzi7GauwEJTZyr27ZZjA1C
6apGSQ9GzuwNvZ4rCXystVEagAS8OQ4H3D4dWS17Zg31ICb5o4E5r0z09o/Uz46u0
VoAAwCQYFKw4DAhsFAAMxADAuAhUArRubTxsbIXy3AhtjQ943AbNBnSICFQCu+g1i
W3jwF+gOcbroD4S/ZcvB3w==
—–END CERTIFICATE REQUEST—–
可以看到,该文件没有了前面两个头信息。大家如果经常使用openssl的应用程序,就对这些文件格式很熟悉了。
2 PEM类型和实现结构介绍
openssl中定义的PEM相关结构体如下(openssl\pem.h),这些结构体是所有PEM系列函数的基础。
下面定义的是PEM一个高层应用结构,该结构通过PEM_SealInit进行初始化,最后使用PEM_SealFinal进行释放,该结构定义了PEM中要使用的编码算法、信息摘要算法以及加密算法。
typedef struct PEM_Encode_Seal_st
{
EVP_ENCODE_CTX encode;
EVP_MD_CTX md;
EVP_CIPHER_CTX cipher;
} PEM_ENCODE_SEAL_CTX;
下面定义了PEM_CTX中的一个子结构,用来保存用户的信息
typedef struct pem_recip_st
{
char *name;
X509_NAME *dn;
int cipher;
int key_enc;
} PEM_USER;
下面是PEM主结构体PEM_CTX结构的定义,我们将在注释里面对必要的参数进行说明。
typedef struct pem_ctx_st
{
int type; //结构类型
struct
{
int version; //版本号
int mode; //编码方式
} proc_type; //Proc_Type字段信息,包括版本号和编码方式
char *domain;
struct
{
int cipher;
} DEK_info; //定义了PEM中DEK_info字段的信息
PEM_USER *originator;
int num_recipient;
PEM_USER **recipient;
#ifndef OPENSSL_NO_STACK
STACK *x509_chain; //保存证书链
#else
char *x509_chain; //保存证书链
#endif
EVP_MD *md; //签名算法类型,指定了信息摘要算法和签名算法
int md_enc; //信息摘要算法是否进行了加密(签名)
int md_len; //摘要信息的长度
char *md_data; //摘要信息,可以是经过了加密(签名)的信息
EVP_CIPHER *dec; //数据加密算法
int key_len; //密钥长度
unsigned char *key; //加密密钥
int data_enc; //数据是否加密标志
int data_len; //数据长度
unsigned char *data; //数据
} PEM_CTX;
下面我们对PEM_CTX结构体中一些重要的参数做详细的说明
- int type参数——该参数指明了PEM_CTX结构的类型,目前包括了以下定义的类型:
#define PEM_OBJ_UNDEF 0 //未定义
#define PEM_OBJ_X509 1 //x509证书
#define PEM_OBJ_X509_REQ 2 //x509证书请求
#define PEM_OBJ_CRL 3 //吊销
#define PEM_OBJ_SSL_SESSION 4 //ssl会话
#define PEM_OBJ_PRIV_KEY 10 //私钥
#define PEM_OBJ_PRIV_RSA 11 //RSA私钥
#define PEM_OBJ_PRIV_DSA 12 //DSA私钥
#define PEM_OBJ_PRIV_DH 13 //DH私钥
#define PEM_OBJ_PUB_RSA 14 //RSA公钥
#define PEM_OBJ_PUB_DSA 15 //DSA公钥
#define PEM_OBJ_PUB_DH 16 //DH公钥
#define PEM_OBJ_DHPARAMS 17 //DH参数
#define PEM_OBJ_DSAPARAMS 18 //DSA公钥
#define PEM_OBJ_PRIV_RSA_PUBLIC 19 //RSA私钥证书
可以看到,这些类型基本上包括了所有openssl中要使用的基本结构
- struct proc_type参数——该参数是保存了PEM标准中Proc_Type字段的信息,可以看到,该结构包括两个字段,第一个字段version是版本号,第二个字段mode是信息的编码方式,目前定义了四种,如下:
#define PEM_TYPE_ENCRYPTED 10 //信息已经加密和签名
#define PEM_TYPE_MIC_ONLY 20 //信息经过数字签名但没有加密
#define PEM_TYPE_MIC_CLEAR 30 //信息经过数字签名但是没有加密、没有进行编码,可使用非PEM格式阅读
#define PEM_TYPE_CLEAR 40 //信息没有签名和加密并且没有进行编码,好象是openssl自身的扩展,但并没有真正实现
值得注意是,在openssl实现的PEM文件中,最后一个PEM_TYPE_CLEAR其实并没有用到。
- struct DEK_info参数——该参数定义了PEM中DEK_info字段的信息,本来该参数应该含有两个字段,包括加密算法和IV。但是由于历史原因,openssl中原有的非标准的IV字段在新版的openssl中取消了,所以就剩下一个算法定义了,目前支持的算法如下述的定义:
#define PEM_DEK_DES_CBC 40
#define PEM_DEK_IDEA_CBC 45
#define PEM_DEK_DES_EDE 50
#define PEM_DEK_DES_ECB 60
#define PEM_DEK_RSA 70
#define PEM_DEK_RSA_MD2 80
#define PEM_DEK_RSA_MD5 90
3 函数概览
#include <openssl/pem.h>
//------------------------------------------------------------------------------
//私钥 读写
EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x,pem_password_cb *cb, void *u);
int PEM_write_bio_PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc,
unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x,pem_password_cb *cb, void *u);
int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,
unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
int PEM_write_bio_PKCS8PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc,
char *kstr, int klen, pem_password_cb *cb, void *u);
int PEM_write_bi