pem_pem文件 - CSDN
  • PEM文件格式详细解析

    2014-11-08 17:48:34
    PEM文件格式存档 Author:Roson sun sunxiao@tomonline-inc.com Time:2006-4-11 1. 描述: Openssl使用PEM(RFC 1421-1424)文档格式,如果使用其他语言包,则需要将此格式进行解码并将各个私...

    PEM文件格式存档

    AuthorRoson sun sunxiao@tomonline-inc.com

    Time2006-4-11

    1.  描述:

    Openssl使用PEM(RFC 1421-1424)文档格式,如果使用其他语言包,则需要将此格式进行解码并将各个私公钥加入。

    2.  说明:

    a)         首先使用BASE64解码,如果是非ANSI TXT格式,需要做转换。

    b)        一个RSA私钥包含一下信息(1024位):

    >openssl rsa -in key.pem -noout -text

    modulus:

    00:d5:00:b2:18:c3:04:d1:ac:80:c6:22:a0:cc:5c:

    f1:c0:4a:83:95:e5:c9:88:ae:31:64:ab:e1:15:42:

    de:1a:da:bc:f5:d2:05:05:74:9d:d3:86:94:9b:9d:

    74:bb:e5:72:a4:b8:40:27:61:88:d4:ac:20:b0:2b:

    1c:1e:d7:9b:43:c5:06:b6:3a:b4:42:f0:5a:22:38:

    23:74:99:4a:50:f1:f1:54:11:5a:44:0b:40:cf:83:

    8a:73:6c:34:15:98:0a:7d:cf:0e:e5:00:8d:07:40:

    f7:7d:fb:3f:64:35:1b:5d:a3:40:a9:51:fa:92:7d:

    34:ef:03:fe:e0:59:56:31:25                  

    数量:128

     

    publicExponent:

    01:00:01

    数量:3

     

    privateExponent:

    11:e2:a8:11:ba:36:6a:60:c0:c3:62:5e:fc:2a:05:           

    c6:ae:bb:13:d8:22:af:0e:69:69:59:a1:61:c6:a6:           

    9d:bc:a6:47:41:e6:58:09:ed:c2:b8:37:3c:45:e1:           

    6a:71:9e:c9:c4:0a:e7:03:a2:98:b1:07:61:a3:8d:           

    0d:ed:ee:c4:7f:ca:fe:7d:c1:2e:2f:ca:3d:16:81:            

    4f:bf:ad:6a:03:ca:d7:80:dc:57:03:fe:cf:1f:37:           

    05:8d:58:79:14:01:1f:66:42:e4:f1:b6:9d:f1:01:           

    37:12:f4:d8:15:c0:cc:9b:fc:ea:55:cb:2f:ba:46:           

    fd:17:11:7e:43:b5:d1:15                                 

    数量:128

     

    prime1:

    00:ed:a0:e8:25:cc:1c:aa:f5:44:e2:78:9e:54:2c:

    1d:60:cb:8f:32:b3:68:6d:b3:1d:cd:a9:8c:2a:ca:

    02:bc:7b:a7:8b:06:1d:fa:af:4f:8c:26:81:54:12:

    ec:7d:92:20:77:85:ef:6e:06:a6:8b:9c:eb:ab:6a:

    e6:a1:83:6d:a3                              

    数量:64(去掉开头的0

     

    prime2:

    00:e5:78:66:5a:84:22:51:78:2d:14:fc:5f:f8:4e:

    45:5f:e3:b2:5e:5b:50:a4:f5:55:e0:f3:0e:98:2c:

    52:61:c2:50:df:f4:b7:bc:6e:69:3e:99:ff:1c:50:

    a8:89:05:7a:2b:25:91:56:a5:a6:8f:8a:ec:80:82:

    fa:eb:09:c2:97                              

    数量:64(去掉开头的0

     

    exponent1:

    00:89:e6:26:d2:48:71:1a:84:db:44:d1:da:8f:de:

    49:ee:32:33:17:a9:25:a1:03:a0:f8:08:bc:5e:d8:

    7c:5e:05:24:65:79:57:4c:73:10:26:b4:f1:b8:68:

    82:f5:1c:27:db:34:ce:8d:7b:2e:8b:36:b5:4c:f4:

    ec:82:2e:53:21                              

    数量:64(去掉开头的0

     

    exponent2:

    6a:16:a6:e3:74:31:55:8f:04:f0:ad:d9:44:b8:13:

    14:c8:f5:5e:f0:42:b1:71:07:5a:2f:a4:f0:af:95:

    0a:c3:46:96:b3:d1:fa:58:e5:69:5e:d2:f5:e9:48:

    71:c8:c9:79:87:2d:d1:6c:56:3c:08:d3:5c:7a:b1:

    bc:d6:4f:53                                 

    数量:64

     

    coefficient:

    62:dd:3f:f4:c7:30:c7:77:5e:8c:ae:c8:11:c1:23:

    b0:6d:7d:07:54:8f:e7:12:1d:e1:00:ad:70:55:12:

    43:f6:6f:a9:d7:94:9d:33:15:66:16:2d:d1:76:13:

    33:0d:ae:6f:e3:3f:46:4b:4a:78:14:02:2e:72:66:

    59:0c:2d:6a                                 

    数量:64

    c)        C#RSAParameter结构体对应表:

    说明

    PEM

    RSAParameter

     

    Modulus

    modulus

     

    Exponent

    Exponent

     

    prime1

    P

     

    exponent1

    Q

     

    prime2

    DP

     

    exponent2

    DQ

     

    coefficient

    InverseQ

     

    privateExponent

    D

     

    d)        PEM偏移(1024位,以0为开始字符,十进制)

    说明

    PEM私钥

    PEM公钥

    长度

    Modulus

    11

    29

    128

    PublicExponent

    141

    159

    3

    PrivateExponent

    147

    ×

    128

    Prime1

    278

    ×

    64

    Prime2

    345

    ×

    64

    Exponent1

    412

    ×

    64

    Exponent2

    478

    ×

    64

    Coefficient

    545

    ×

    64



    e)         例子程序(C#):

    /// <summary>
    /// 用得到的解码值来得到相应的Parameter(BASE64->RSAParameter)
    /// </summary>
    /// <param name="hashKey">源</param>
    /// <param name="type">0私钥1公钥</param>
    /// <returns></returns>
    public RSAParameters getKeyPara(string hashKey, int type)
    {
        RSAParameters rsaP = new RSAParameters();
        byte[] tmpKeyNoB64 = Convert.FromBase64String(hashKey);
        int pemModulus = 128;
        int pemPublicExponent = 3;
        int pemPrivateExponent = 128;
        int pemPrime1 = 64;
        int pemPrime2 = 64;
        int pemExponent1 = 64;
        int pemExponent2 = 64;
        int pemCoefficient = 64;
        byte[] arrPemModulus = new byte[128];
        byte[] arrPemPublicExponent = new byte[3];
        byte[] arrPemPrivateExponent = new byte[128];
        byte[] arrPemPrime1 = new byte[64];
        byte[] arrPemPrime2 = new byte[64];
        byte[] arrPemExponent1 = new byte[64];
        byte[] arrPemExponent2 = new byte[64];
        byte[] arrPemCoefficient = new byte[64];
        if (type == 0)//私钥
        {
            //Modulus
            for (int i = 0; i < pemModulus; i++)
            {
                arrPemModulus[i] = tmpKeyNoB64[11 + i];
            }
            rsaP.Modulus = arrPemModulus;
    
            //PublicExponent
            for (int i = 0; i < pemPublicExponent; i++)
            {
                arrPemPublicExponent[i] = tmpKeyNoB64[141 + i];
            }
            rsaP.Exponent = arrPemPublicExponent;
            //PrivateExponent
            for (int i = 0; i < pemPrivateExponent; i++)
            {
                arrPemPrivateExponent[i] = tmpKeyNoB64[147 + i];
            }
            rsaP.D = arrPemPrivateExponent;
            //Prime1
            for (int i = 0; i < pemPrime1; i++)
            {
                arrPemPrime1[i] = tmpKeyNoB64[278 + i];
            }
            rsaP.P = arrPemPrime1;
            //Prime2
            for (int i = 0; i < pemPrime2; i++)
            {
                arrPemPrime2[i] = tmpKeyNoB64[345 + i];
            }
            rsaP.Q = arrPemPrime2;
            //Exponent1
            for (int i = 0; i < pemExponent1; i++)
            {
                arrPemExponent1[i] = tmpKeyNoB64[412 + i];
            }
            rsaP.DP = arrPemExponent1;
            //Exponent2
            for (int i = 0; i < pemExponent2; i++)
            {
                arrPemExponent2[i] = tmpKeyNoB64[478 + i];
            }
            rsaP.DQ = arrPemExponent2;
            //Coefficient
            for (int i = 0; i < pemCoefficient; i++)
            {
                arrPemCoefficient[i] = tmpKeyNoB64[545 + i];
            }
            rsaP.InverseQ = arrPemCoefficient;
        }
        else//公钥
        {
            for (int i = 0; i < pemModulus; i++)
            {
                arrPemModulus[i] = tmpKeyNoB64[29 + i];
            }
            rsaP.Modulus = arrPemModulus;
            for (int i = 0; i < pemPublicExponent; i++)
            {
                arrPemPublicExponent[i] = tmpKeyNoB64[159 + i];
            }
            rsaP.Exponent = arrPemPublicExponent;
        }
        return rsaP;
    }


    转自:http://blog.csdn.net/qq387732471/article/details/6800488

    展开全文
  • Pem文件的生成

    2019-09-05 11:00:29
    转载:https://blog.csdn.net/weixin_30531261/article/details/80891360 在用pcks8生成私钥的时候要输入openssl进入openssl的shell,不然会找不到pcks8这个命令。

    多种数字证书的格式都可以通过openssl 转化为pem的格式
    阿里的帮助文档介绍的很不错,就是有些命令过时了
    https://help.aliyun.com/knowledge_detail/40526.html?spm=5176.2000002.0.0.7a539951R44Df4

    转载:https://blog.csdn.net/weixin_30531261/article/details/80891360
    在用pkcs8生成私钥的时候要输入openssl进入openssl的shell,不然会找不到pkcs8这个命令。

    展开全文
  • pem文件的生成

    2018-07-09 03:16:41
    秘钥pem文件的生成

    0702pem文件的生成

    刚刚的笔记没了,因为电脑蓝屏了,我又没保存。现在再补回。

    一开始问题是出在没理解demo里面引用的pem文件。拿来测试)(在demo里先签名,然后紧接着就验签),结果发现,验签失败。
    后面想想,私钥是银联自己的,而公钥则是对方提供给银联的。因此,用银联的私钥去签名,而用对方的公钥进行验签,验签当然会失败。

    然后,就要想办法自己去生成公私钥的pem文件。一开始很排斥,不想自己去生成,感觉好麻烦,事实上,也花了好长时间去理解。

    找了很久,找到了一篇生成pem文件的文章(找了很多篇,有些生成的pem文件不对,当时被误导了很久)。(前面也花了好一些时间,才意识到自己要百度的内容:openssl如何生成pem公私钥),文章连接为:https://blog.csdn.net/m0_38080126/article/details/77609304

    一开始直接copy paste。

    为了防止链接失败,就把文章内容拷下来了:

    1、生成RSA密钥的方法 
    
    openssl genrsa -des3 -out privkey.pem 2048 
    这个命令会生成一个2048位的密钥,同时有一个des3方法加密的密码,如果你不想要每次都输入密码,可以改成: 
    openssl genrsa -out privkey.pem 2048 
    建议用2048位密钥,少于此可能会不安全或很快将不安全。 
    
    2、生成一个证书请求 
    openssl req -new -key privkey.pem -out cert.csr 
    这个命令将会生成一个证书请求,当然,用到了前面生成的密钥privkey.pem文件 
    这里将生成一个新的文件cert.csr,即一个证书请求文件,你可以拿着这个文件去数字证书颁发机构(即CA)申请一个数字证书。CA会给你一个新的文件cacert.pem,那才是你的数字证书。 
    
    如果是自己做测试,那么证书的申请机构和颁发机构都是自己。就可以用下面这个命令来生成证书: 
    openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095 
    这个命令将用上面生成的密钥privkey.pem生成一个数字证书cacert.pem 
    
    3、使用数字证书和密钥 
    有了privkey.pem和cacert.pem文件后就可以在自己的程序中使用了,比如做一个加密通讯的服务器

    一开始直接copy生成私钥pem,然后走第二步,再走第三步。没去看文字,没去理解。

    后来,理解了,第一步是生成pem文件,第二步是生成证书申请(用于向CA申请证书用的),第三步是通过私钥pem生成公钥pem。

    也才明白,公钥pem是通过私钥pem生成的。

    拿到两个pem文件就去测试了,结果出现了异常,在签名那里就死掉了。对比我自己创建的pem文件和demo里的pem文件,我发现,我的pem文件里面有多余的头和尾,如:

    公钥pem的头和尾:

    -----BEGIN CERTIFICATE-----
    -----END CERTIFICATE-----

    私钥pem的头和尾:

    -----BEGIN RSA PRIVATE KEY-----
    -----END RSA PRIVATE KEY-----

    这里卡了很久。一直想不懂,为什么我的pem文件会有头和尾的部分,而demo的pem文件里面却没有。我在想是不是我生成pem的方式不对。去掉头和尾,运行程序,出错。吓得我赶紧回头,补上头和尾。

    卡了很久,最后明白,原来没所谓,有头和尾没关系,在读取的时候,只要在代码中解析文件的时候跳过头和尾就可以了。我一开始以为直接修改文件会影响里面的内容。后来证实我是错的。可能我被那个异常信息吓到了。

    只要在读取文件时,发现第一个符号是’-‘,就跳过这一行,就可以读取到正式的内容了。

    对了,如果把头和尾都读进去,那么在签名那一步就会出错,错误信息忘了。如果把头尾去掉,那么还是会出现异常,出错的地方是在验签那里。

    还有一个问题卡了我很久,Demo里是这样读取pem文件的:

    //从私钥文件中读取私钥
    File file = new File(System.getProperty("user.dir") + "\\src\\myPrivateKey.pem");
    InputStreamReader read = new InputStreamReader(new FileInputStream(file), "UTF-8");//考虑到编码格式
    BufferedReader bufferedReader = new BufferedReader(read);
    String str = null;
    while((str = bufferedReader.readLine()) != null) {
        HttpServer.myPrivateKey += str + "\n";
    }
    read.close();

    我在想,是不是错在换行这里???是不是不应该换行,直接拼接就好。我就改了(这是银联的人提供的demo,我竟然质疑他的),结果还是会出错。顿时没有了方向,到底问题是出在哪里。

    百度的时候,有的人是通过key文件来生成两个pem文件。懵逼。。。

    又看到有人说,通过openssl genrsa -out privkey.pem 2048生成的私钥pem并不是能直接拿来用的,得把RSA私钥转换成PKCS8格式:

    pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt

    然后,控制台就输出了一个新的pem文件的内容,一看,发现,它的头尾是这样的:

    -----BEGIN PRIVATE KEY-----
    -----END PRIVATE KEY-----

    这个才是对的把。我赶紧拷贝一份,作为新的pem文件,然后测试。

    还是出错。然后百度继续查,最后查到一篇解决了这个问题的网址:

    https://blog.csdn.net/weiyuefei/article/details/76269790

    这篇生成两个pem文件的做法和开头提到的那个网址的做法不一样,这个才是有效的。

    开头的网址生成的public.pem文件,头和尾专这样的:

    -----BEGIN CERTIFICATE-----
    -----END CERTIFICATE-----

    而这篇文章生成的public.pem的头尾是这样的:

    -----BEGIN PUBLIC KEY-----
    -----END PUBLIC KEY-----

    一看到这个,我想,这次应该对了!果然!

    测试,输出:

    验签结果:true

    感想

    总算成功了。从上午10点多弄到下午5点。对pem文件,是第一次接触。对公私钥也是弄这项目才开始接触。努力吧!也算是学到了新东西啊。可能从文章上来看,好像没什么难度,但是事实上,就我而言,弄了很久。新东西,对背景什么的都不了解,所以说,以后在接触新东西之前,要理解好,理解好了再动手!!!

    事实证明,银联提供的demo是没问题的,问题是出在我生成pem文件的生成。理解好公钥私钥,demo里的公钥私钥并不是一对的,demo是银联的,那么说明私钥是银联的,公钥是与银联交互的那一方的。理解这点很重要。

    正确生成pem文件很重要,生成pem文件的操作为:

    1、生成RSA密钥的方法 
    
    openssl genrsa -des3 -out privkey.pem 2048 
    这个命令会生成一个2048位的密钥,同时有一个des3方法加密的密码,如果你不想要每次都输入密码,可以改成: 
    openssl genrsa -out privkey.pem 2048 
    建议用2048位密钥,少于此可能会不安全或很快将不安全。 
    
    2、生成一个证书请求 
    openssl req -new -key privkey.pem -out cert.csr 
    这个命令将会生成一个证书请求,当然,用到了前面生成的密钥privkey.pem文件 
    这里将生成一个新的文件cert.csr,即一个证书请求文件,你可以拿着这个文件去数字证书颁发机构(即CA)申请一个数字证书。CA会给你一个新的文件cacert.pem,那才是你的数字证书。 
    
    如果是自己做测试,那么证书的申请机构和颁发机构都是自己。就可以用下面这个命令来生成证书: 
    openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095 
    这个命令将用上面生成的密钥privkey.pem生成一个数字证书cacert.pem 
    
    3、使用数字证书和密钥 
    有了privkey.pem和cacert.pem文件后就可以在自己的程序中使用了,比如做一个加密通讯的服务器
    展开全文
  • #include &amp;lt;openssl/pem.h&amp;gt; EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, void *u); EVP_PKEY *PEM_read_PrivateK

      版权声明本文根据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_bio_PKCS8PrivateKey_nid(BIO *bp, EVP_PKEY *x, int nid,
                               char *kstr, int klen,pem_password_cb *cb, void *u);
    
    int       PEM_write_PKCS8PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,
                                     char *kstr, int klen,pem_password_cb *cb, void *u);
    int       PEM_write_PKCS8PrivateKey_nid(FILE *fp, EVP_PKEY *x, int nid,
                                     char *kstr, int klen,pem_password_cb *cb, void *u);
    //公钥 读写
    EVP_PKEY* PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_PUBKEY(BIO *bp, EVP_PKEY *x);
    
    EVP_PKEY* PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x,pem_password_cb *cb, void *u);
    int       PEM_write_PUBKEY(FILE *fp, EVP_PKEY *x);
    
    
    //RSA------------------------------------------------------------------------------
    //RSA 私钥读写
    RSA *     PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSAPrivateKey(BIO *bp, RSA *x, const EVP_CIPHER *enc,
                              unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
    
    RSA *     PEM_read_RSAPrivateKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSAPrivateKey(FILE *fp, RSA *x, const EVP_CIPHER *enc,
                            unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
    //RSA 公钥读写
    RSA *     PEM_read_bio_RSAPublicKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSAPublicKey(BIO *bp, RSA *x);
    
    RSA *     PEM_read_RSAPublicKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSAPublicKey(FILE *fp, RSA *x);
    
    
    RSA *     PEM_read_bio_RSA_PUBKEY(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSA_PUBKEY(BIO *bp, RSA *x);
    
    RSA *     PEM_read_RSA_PUBKEY(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSA_PUBKEY(FILE *fp, RSA *x);
    
    //DSA------------------------------------------------------------------------------
    //DSA私钥 读写
    DSA *     PEM_read_bio_DSAPrivateKey(BIO *bp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSAPrivateKey(BIO *bp, DSA *x, const EVP_CIPHER *enc,
                    unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
    
    DSA *     PEM_read_DSAPrivateKey(FILE *fp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_DSAPrivateKey(FILE *fp, DSA *x, const EVP_CIPHER *enc,
                       unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
    //DSA公钥 读写
    DSA *     PEM_read_bio_DSA_PUBKEY(BIO *bp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSA_PUBKEY(BIO *bp, DSA *x);
    
    DSA *     PEM_read_DSA_PUBKEY(FILE *fp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_DSA_PUBKEY(FILE *fp, DSA *x);
    //DSA参数 读写
    DSA *     PEM_read_bio_DSAparams(BIO *bp, DSA **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSAparams(BIO *bp, DSA *x);
    
    DSA *     PEM_read_DSAparams(FILE *fp, DSA **x, pem_password_cb *cb, void *u);
    int       PEM_write_DSAparams(FILE *fp, DSA *x);
    
    //DH------------------------------------------------------------------------------
    //DH 参数读写
    DH *      PEM_read_bio_DHparams(BIO *bp, DH **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_DHparams(BIO *bp, DH *x);
    
    DH *      PEM_read_DHparams(FILE *fp, DH **x, pem_password_cb *cb, void *u);
    int       PEM_write_DHparams(FILE *fp, DH *x);
    
    
    //X509------------------------------------------------------------------------------
    X509 *    PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509(BIO *bp, X509 *x);
    
    X509 *    PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_X509(FILE *fp, X509 *x);
    
    X509 *    PEM_read_bio_X509_AUX(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_AUX(BIO *bp, X509 *x);
    
    X509 *    PEM_read_X509_AUX(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_X509_AUX(FILE *fp, X509 *x);
    //x509证书请求的读写
    X509_REQ* PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_REQ(BIO *bp, X509_REQ *x);
    
    X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x,pem_password_cb *cb, void *u);
    int       PEM_write_X509_REQ(FILE *fp, X509_REQ *x);
    
    //x509证书 写一个新请求
    int       PEM_write_bio_X509_REQ_NEW(BIO *bp, X509_REQ *x);
    int       PEM_write_X509_REQ_NEW(FILE *fp, X509_REQ *x);
    //x509 撤销列表读写
    X509_CRL* PEM_read_bio_X509_CRL(BIO *bp, X509_CRL **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_CRL(BIO *bp, X509_CRL *x);
    
    X509_CRL* PEM_read_X509_CRL(FILE *fp, X509_CRL **x,pem_password_cb *cb, void *u);
    int       PEM_write_X509_CRL(FILE *fp, X509_CRL *x);
    
    //PKCS7------------------------------------------------------------------------------
    PKCS7 *   PEM_read_bio_PKCS7(BIO *bp, PKCS7 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_PKCS7(BIO *bp, PKCS7 *x);
    
    PKCS7 *   PEM_read_PKCS7(FILE *fp, PKCS7 **x, pem_password_cb *cb, void *u);
    int       PEM_write_PKCS7(FILE *fp, PKCS7 *x);
    
    //NETSCAPE_CERT_SEQUENCE--------------------------------------------------------
    NETSCAPE_CERT_SEQUENCE* PEM_read_bio_NETSCAPE_CERT_SEQUENCE(BIO*bp,NETSCAPE_CERT_SEQUENCE **x,
                                                 pem_password_cb *cb, void *u);
    int                     PEM_write_bio_NETSCAPE_CERT_SEQUENCE(BIO *bp, NETSCAPE_CERT_SEQUENCE *x);
    
    NETSCAPE_CERT_SEQUENCE* PEM_read_NETSCAPE_CERT_SEQUENCE(FILE *fp,NETSCAPE_CERT_SEQUENCE **x,
                                           pem_password_cb *cb, void *u);
    int                     PEM_write_NETSCAPE_CERT_SEQUENCE(FILE *fp, NETSCAPE_CERT_SEQUENCE *x);
    

    3.1 PEM系列函数通用参数介绍

      PEM系列函数中很多参数是相同意义的,也就是说通用的。

    • bp参数——如果函数有该参数,则定义了进行数据读写BIO接口。
    • fp参数——如果函数包含了该参数,则定义了进行数据读写的FILE指针。
    • TYPE类型参数——PEM读操作的系列函数都有TYPE **x和返回TYEP *指针的参数。这里的TYPE可以为任何函数要使用的结构体,如DSA或X509之类的。如果参数x是NULL,那么该参数将被忽略。如果x不是NULL,但是*x是NULL,那么返回的结构体就会写入到*x里面。如果x和*x都不是NULL,那么函数就试图重用*x中的结构体。这种函数总是返回一个执行结构体的指针(x的值),如果出错,就返回NULL。
    • enc参数——enc参数定义了PEM函数写私钥的时候采用的加密算法。加密是在PEM层进行的。如果该参数为NULL,那么私钥就会以不加密的形式写入相应的接口。
    • cb参数——cb参数定义了回调函数,该回调函数在加密PEM结构体(一般来说是私钥)需要口令的时候使用。
    • kstr参数主要在PEM写系列函数里面使用,如果该参数不为NULL,那么kstr中klen字节数据就用来作为口令,此时,cb参数就被忽略了。
    • u参数——如果cb参数为NULL,而u参数不为NULL,那么u参数就是一个以NULL结束的字符串用作口令。如果cb和u参数都是NULL,那么缺省的回调函数就会并使用,该函数一般在当前的终端提示输入口令,并且关掉了回显功能。
    • 回调函数callback函数介绍——因为缺省的回调函数基于终端的,有时候不适合使用(如GUI程序),所以可以使用替换的回调函数。回调函数的形式如下:
    int cb(char *buf, int size, int rwflag, void *u);

      在该函数中,buf是保存口令的参数。size是考虑最大的长度(如buf的长度)。rwflag是一个读写标志,0的时候为读操作,1的时候为写操作。当rwflag为1的时候,典型的函数一般会要求用户验证口令(如输入两次)。u参数跟上述PEM函数的u参数意义是一样的,它允许应用程序使用固定的数据作为参数传给回调函数。回调函数必须返回口令字符的数目,如果出错返回0。

    3.2 PEM结构信息处理函数

      本次介绍的函数是处理PEM结构里面一些字段信息的函数,这些函数在一般应用中可能不会用到,但是深入一点的应用,恐怕就避免不了。此外,了解这些应用,对于加深对PEM结构的理解也是很有好处的。下面是其中相关一些函数的定义:

    # include<openssl/pem.h>
    int PEM_get_EVP_CIPHER_INFO(char *header, EVP_CIPHER_INFO *cipher);
    int PEM_do_header (EVP_CIPHER_INFO *cipher, unsigned char *data,
                        long*len,pem_password_cb *callback,void *u);
    
    void PEM_proc_type(char *buf, int type);
    void PEM_dek_info(char *buf, const char *type, int len, char *str);

    3.2.1 PEM_proc_type

      该函数是通过给定参数type返回一个标准的PEM文件的Proc-Type字段信息。返回的信息写入到buf参数里面去,所以要求buf分配的内存空间必须足够大。事实上,该函数返回的字符串不外乎下面四种结果:

    • 当type为PEM_TYPE_ENCRYPTED,返回字符串为”Proc-Type: 4,ENCRYPTED\n”
    • 当type为PEM_TYPE_MIC_CLEAR,返回字符串为”Proc-Type: 4,MIC-CLEAR\n”
    • 当type为PEM_TYPE_MIC_ONLY,返回字符串为”Proc-Type: 4,MIC-ONLY\n”
    • 当type为其它值时,返回字符串为 “Proc-Type: 4,BAD-TYPE\n”

      事实上,虽然上字段信息中有MIC(信息摘要)选项,但openssl的PEM库并没有实现MIC计算的功能。当然,可以通过使用RSA-MD系列函数将PEM的数据信息进行摘要并将该结果作为PEM的MIC。你可以通过PEM_dek_info函数产生MIC-info头信息,然后写入到PEM结构中,不过据openssl的说明,这需要的时间可能会比较长,大概5分钟左右。

    3.2.2 PEM_dek_info

      该函数跟上述函数相似,是根据type参数生成DEK-info字段的信息,返回并写入到buf里面。参数str里应该是提供了ivec变量的值,参数len是str的长度(单位是字节)。在这里,参数type应该为加密算法的名字,原则上这个字符串可以是任意的,但是为了其它程序能够正确解释该字段,你可以先得到算法相应的NID,然后通过调用nid2sn得到该算法的简称作为type参数。例如我们需要在PEM_ASN1_write_bio中使用算法结构enc,那么可以调用下面函数:

    objstr=OBJ_nid2sn(EVP_CIPHER_nid(enc));

      此时objstr就是一个包含了算法enc的简称的字符串。然后我们就可以通过下面的语句在PEM_dek_info函数中使用这个字符串了:

    PEM_dek_info(buf,objstr,8,(char *)iv);

    3.2.3 PEM_do_header

      该函数并非顾名思义,事实上它完成了对一个PEM编码对象的的解密工作(如果该PEM对象需要进行解密),该函数通常是被PEM_read_bio所调用的。在调用该函数之前,应该已经将PEM文件的一些头信息得到,以便于正确进行解密操作。其中,DEK-info字段的信息应该在调用本函数之前进行正确的处理,从而通过该字段的名字和ivec得到相应的EVP_CIPHER结构信息和IV变量,作为本函数的cipher参数。
      如果PEM文件没有DEK-info字段,那么该函数简单返回1,操作成功,因为不需要进行解密操作。如果不是的话,那么该函数就需要一个口令来进行解密。首先,它会试图从callback参数(一个回调函数)中得到该口令。回调函数的格式如下:

    callback(buffer, blen, verify)

      其中,参数buffer是保存返回口令的地方,blen是buffer的最大长度,verify参数是指明是否需要口令验证(就是要求用户输入两次相同的口令),默认的是0。
      如果callback参数为NULL,而u参数不为NULL,那么u参数就会以NULL为结束符的字符串作为口令写入到buffer中;如果callback和u参数都为NULL,那么就会调用缺省的callback函数(关于u的具体意义,请参考3.1)。PEM_do_header函数得到口令后,就使用该口令(包括长度信息)跟cipher参数种的ivec变量一起对数据进行解密。解密后的数据保存在data中,长度信息保存在plen中。该函数操作成功返回1,否则返回0。

    3.2.4 PEM_get_EVP_CIPHER_INFO

      该函数一般也被PEM_read_bio函数调用。在调用该函数之前,PEM的Proc-Type头信息应该已经作为明文被读入到header参数中。如果header为NULL,那么函数成功返回1,因为没有什么头信息要处理。如果不为NULL,那么该函数首先确定header信息是否以“Proc-Type:4,ENCRYPTED”开头,如果是其它形式的,该函数将返回0,不进行处理。之后,函数开始读取DEK-info字段的信息,然后函数通过该字段的加密算法名字使用EVP_get_cihperbyname得到一个EVP_CIPHER结构,并保存在参数cipher->cipher中;然后函数再通过调用内部的函数得到ivec的值,并保存在cipher->iv中。成功操作返回1,否则返回0。
      需要注意的是,因为该函数调用了EVP_get_cipherbyname,所以在调用本函数前,应该先调用EVP_add_cipher和EVP_add_alias,或者调用SSLeay_add_all_algorithms,从而将所有加密算法的信息载入到程序中。具体的情况请参考《OpenSSL中文手册之EVP》相关章节。

    3.3 PEM信息封装加密系列函数

      该系列函数完成了对PEM对象以及相关密钥和IV向量的加密编码工作,以便于数据的保存和传送,主要包括以下函数:

    #include<openssl/pem.h>
    
    int PEM_SealInit(PEM_ENCODE_SEAL_CTX *ctx, EVP_CIPHER *type,EVP_MD *md_type, unsigned char **ek, 
                    int *ekl,unsigned char *iv, EVP_PKEY **pubk, int npubk);
    void PEM_SealUpdate(PEM_ENCODE_SEAL_CTX *ctx, unsigned char *out, 
                        int *outl,unsigned char *in, int inl);
    int PEM_SealFinal(PEM_ENCODE_SEAL_CTX *ctx, unsigned char *sig,int *sigl,
                        unsigned char *out, int *outl, EVP_PKEY *priv);
    
    void PEM_SignInit(EVP_MD_CTX *ctx, EVP_MD *type);
    void 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);
    
    void ERR_load_PEM_strings();

      其中,PEM_Seal*系列函数完成了对PEM对象、密钥和IV变量的加密编码工作,PEM_Sign系列函数完成了对PEM进行数字签名的工作。

    3.3.1 PEM_SealInit函数

      该函数为后续的PEM_SealUpdate和PEM_SealFinal函数做初始化工作。首先,该函数使用参数md_type调用函数EVP_SignInit对信息摘要结构ctx->md进行初始化。然后,该函数通过参数type找到相应的EVP_CIPHER结构,产生适用于该算法的密钥和ivec变量并保存在该算法结构中,然后使用参数pubk的公钥调用函数EVP_SealInit对该密钥进行加密。加密后的秘钥保存在参数ek里面,其长度保存在ekl里面,这些数据都是调用了EVP_EncodeUpdate函数经过了BASE64编码的。因为密钥和IV已经保存在ctx->cipher中,所以,可以被后续的函数用来对PEM对象进行加密处理。该函数成功操作返回正值,否则返回0或-1。
      需要注意的是,因为本函数也使用了加密算法名字查找算法结构,所以在调用本函数之前必须加载该静态算法结构栈。

    3.3.2 PEM_SealUpdate函数

      该函数用来完成对PEM对象信息体的加密和编码,使用的加密密钥是PEM_SealInit函数产生的。该函数对参数in中的inl个字节的数据采用ctx->cipher提供的对称加密算法结构(已经包含了密钥和IV)进行加密操作,然后调用EVP_EncodeUpdate进行BASE64编码后保存在参数out里面,outl是out里有效数据的长度信息。在此同时,该函数也调用函数EVP_SignUpdate函数使用ctx->md的摘要算法结构对参数in里的数据进行了信息摘要操作,不过暂时没有输出,等调用了PEM_SealFinal函数的时候进行输出。
      需要注意的是,该函对输入的信息in的长度做了限制,不能大于1200字节,否则将超过1200字节的信息简单丢弃

    3.3.3 PEM_SealFinal函数

      该函数完成整个PEM_Seal系列的操作。首先,它完成了之前使用PEM_SealUpdate函数进行处理的数据的对称加密工作,将数据进行BASE64编码并输出到参数out,outl保存了out数据的有效长度。同时,该函数还完成了信息摘要工作,并使用参数priv的私钥对该信息进行签名(加密),将结果经过BASE64编码后输出到参数sig,sigl是sig有效数据的长度信息。该函数成功操作返回1,否则返回0
      需要注意的是,该函数运行完后,就将ctx->md和ctx->cipher结构释放清除掉了,所以如果你想保存对称加密算法使用的密钥和IV的话,你需要在调用本函数之前就保存一个备份。当然,一般情况下是不会这么做的,因为这些密钥应该是临时密钥,只用来加密一个信息。

    3.3.4 PEM_Seal操作总结

      完成上述三个函数的操作之后,你就得到了加密后的密钥、IV(从PEM_SealInit函数)以及PEM对象信息体,并且这些都是经过BASE64编码的。然后,你就可以将这些信息发送给接受方了。对方接受到这些信息后,使用他自己的私钥以及你的公钥,就能进行正确的数据解密和验证。

    3.3.5 PEM_SignInit,PEM_SignUpdate和PEM_SignFinal函数

      这三个函数完成的功能跟EVP_Sign系列函数是一样的,其实,前面两个函数就简单调用了EVP_SignInit和EVP_SignUpdate函数。PEM_SignFinal则调用EVP_SignFinal函数完成信息摘要和签名(使用参数pkey的私钥)之后,调用了EVP_EncodeBlock对签名信息进行了BASE64编码,然后将编码后的签名信息保存在参数sigret,siglen保存了sigret有效数据的长度。PEM_SignFinal函数成功返回1,否则返回0。

    3.3.6 ERR_load_PEM_strings函数

      该函数使用了PEM库的错误代码信息对错误处理库进行初始化,必须在使用任何PEM系列函数之前调用该函数。

    3.4 PEM底层IO函数

      PEM提供了一系列底层的进行数据读写操作的IO函数,在后面章节叙述到的PEM对象的IO函数都是这些函数的宏定义,所以虽然一般不要直接调用这些函数,做一个清楚的了解还是必要的。这些函数定义如下:

    #include<openssl/pem.h>
    
    int PEM_read_bio(BIO *bp, char **name, char **header,unsigned char **data,long *len);
    int PEM_write_bio(BIO *bp,const char *name,char *hdr,unsigned char *data,long len);
    
    int PEM_read(FILE *fp, char **name, char **header,unsigned char **data,long *len);
    int PEM_write(FILE *fp,char *name,char *hdr,unsigned char *data,long len);
    
    
    int PEM_bytes_read_bio(unsigned char **pdata, long *plen, char **pnm, const char *name, 
                            BIO *bp,pem_password_cb *cb, void *u);
    
    
    STACK_OF(X509_INFO) *PEM_X509_INFO_read_bio(BIO *bp, STACK_OF(X509_INFO) *sk, 
                                                pem_password_cb *cb, void *u);
    int PEM_X509_INFO_write_bio(BIO *bp,X509_INFO *xi, EVP_CIPHER *enc,unsigned char *kstr,
                                 int klen, pem_password_cb *cd, void *u);
    STACK_OF(X509_INFO) *PEM_X509_INFO_read(FILE *fp, STACK_OF(X509_INFO)*sk,
                                                pem_password_cb *cb, void *u);
    
    
    char* PEM_ASN1_read_bio(char*(*d2i)(),const char *name,
                            BIO *bp,char **x,pem_password_cb *cb, void *u);
    int   PEM_ASN1_write_bio(int (*i2d)(),const char *name,BIO *bp,char *x,const EVP_CIPHER *enc,
                            unsigned char *kstr,int klen,pem_password_cb *cb, void *u);
    char* PEM_ASN1_read(char *(*d2i)(),const char *name,
                        FILE *fp,char **x,pem_password_cb *cb, void *u);
    int   PEM_ASN1_write(int (*i2d)(),const char *name,FILE *fp,char *x,const EVP_CIPHER *enc,
                        unsigned char *kstr,int klen,pem_password_cb *callback, void *u);
    

      可以看到,这些函数中有很多参数在第3部分介绍过,在此将不再详细介绍。

    3.4.1 PEM_read函数

      该函数从文件fp里面读取一个PEM编码的信息。该函数将文件里BEIGIN后面的字符作为对象名保存在参数name里面;将BEGIN所在行和下一个空白行之间的所有信息都读入到参数header里面,如果之间没有信息,就将header设置为NULL;然后将信息体进行BASE64解码放置到data参数里面,len是data参数的有效数据长度。该函数成功返回1,失败返回0

    3.4.2 PEM_read_bio函数

      该函数完成了跟PEM_read相同的功能,只不过读取对象是BIO。事实上,PEM_read是通过调用本函数完成其功能的。该函数成功返回1,失败返回0

    3.4.3 PEM_write函数

      该函数写入一个PEM编码的信息到文件fp,将name参数的数据放在BEGIN头的后面,写入到fp文件;之后将参数hdr信息写入到文件,并在后面写入一个空白行;最后将data参数len字节的数据进行BASE64编码,写入到文件中,并最后加上END头信息,返回PEM信息体的长度,失败返回0

    3.4.4 PEM_write_bio函数

      该函数跟PEM_write函数功能一样,只是操作对象是BIO。事实上,PEM_write函数就是调用本函数完成其功能的。成功返回PEM信息体的长度,失败返回0。

    3.4.5 PEM_ASN1_read函数

      该函数先调用PEM_read函数读取PEM编码的对象信息,然后调用PEM_get_EVP_CIPHER_INFO函数处理PEM格式中的DEK-info字段信息,以决定信息采用的加密算法和ivec值;如果PEM信息是加密了的,接下来就调用PEM_do_header函数解密信息体(参考第4部分),然后调用d2i函数将它进行DER解码转换成内部定义个类型,保存在x参数中。成功返回指向x的指针,否则返回NULL
      注意,参数name必须是BEIGIN头后面的PEM文件数据。因为函数调用了PEM_get_EVP_CIPHER_INFO函数,所以为了函数能成功执行,必须在调用本函数前加载算法。虽然事实上任何类型数据都可以进行加密,但一般来说只有RSA私钥需要加密。本函数可以从一个文件中读取一些列对象。

    3.4.6 PEM_ASN1_read_bio函数

      该函数功能跟PEM_ASN1_read函数一样,不过操作对象是BIO。事实上,PEM_ASN1_read函数是调用本函数完成其功能的。成功返回指向x的指针,否则返回NULL。

    3.4.7 PEM_ASN1_write函数

      该函数将对象x使用i2d参数提供的函数转换城DER编码的数据,接下来,如果enc参数不为NULL,就使用enc的加密算法加密这些数据。参数kstr是用来产生加密密钥的,klen是kstr的有效长度。如果enc不是NULL,但是kstr是NULL,那么就会使用callback函数提示用户输入口令并获取加密数据;如果此时callback为NULL,但是u不为NULL,那么就是使用u作为产生加密密钥的字符串,假定u应该是NULL结束的字符串;如果callback和u都为NULL,那就会使用缺省的callback函数获取口令。然后数据就被进行BASE64编码写入到fp文件中,加上BEIGIN开始头信息、END结束头信息、Type-Proc字段和DEK-info字段(如果数据被加密了)。加密密钥在函数调用完之后就被清除了。成功操作返回1,否则返回0

    3.4.8 PEM_ASN1_wirte_bio函数

      该函数实现的功能跟PEM_ASN1_write一样,不过操作对象是BIO。事实上PEM_ASN1_write函数是调用本函数完成其功能的。成功操作返回1,否则返回0。

    3.4.9 PEM_X509_INFO_read函数

      该函数完成的功能跟PEM_ASN1_read是一样的,除了它自动根据BEGIN头信息调用了相应的d2i系列函数,目前支持的类型d2i_X509、d2i_X509_AUX、d2i_X509_CRL、d2i_RSAPrivateKey和d2i_DSAPrivateKey。该函数会对文件中的所有对象进行处理直到出错或处理完毕。所有被处理好的对象都保存在堆栈sk中。因为有可能有些对象是加密的,所以提供了参数cb和u。参数cb和u的意义参照第3.1部分。成功返回处理好的堆栈指针,否则返回NULL。

    3.4.10 PEM_X509_INFO_read_bio函数

      该函数完成的功能跟PEM_X509_INFO_read函数一样,除了操作对象是BIO之外。事实上,PEM_X509_INFO_read函数是调用本函数完成其功能的。成功返回处理好的堆栈指针,否则返回NULL。

    3.4.10 PEM_X509_INFO_write_bio函数

      该函数完成的功能也跟PEM_ASN1_write_bio一样。除了它从参数xi中读取每一部分对象,分别使用参数xi->x_pkey和xi->x509并使用相应的i2d函数进行PEM编码成独立的信息,并写入到bio中。同样,可能要求用户输入口令生成加密密钥,相关的参数cb、enc、kstr、klen以及u的意义参考前面的函数以及第3部分。该函数成功返回1,否则返回0。

    3.5 PEM对象读写IO函数

      openssl基本上为其定义的每种对象都提供了用PEM格式进行读写的IO函数。在这种意义上说,PEM格式只是包含了头信息的BASE64编码的数据而已。这些函数基本上是基于第6部分所介绍的函数实现的,也就是说,他们多大部分只是这些函数的宏定义而已。因为我们在第3部分已经详细介绍了PEM系列函数的通用参数,所以本文对这些通用参数不再作详细的说明。
      对于每个对象,openssl一般提供了四个函数,比如名为Name的对象,提供的四个函数名就如下形式:

    PEM_read_bio_Name()
    PEM_write_bio_Name()
    
    PEM_read_Name()
    PEM_write_Name()

      可以看到,有两个是读操作函数,两个是写操作函数。其中,两个读操作函数或两个写操作函数都是功能相同的,不过就是对象一个为文件句柄,一个为BIO罢了。此外,所有对象的读函数如果操作成功,返回相应对象的指针,否则返回NULL;而写函数则成功操作返回非0值,失败返回0。下面我们对这些函数简单分类介绍。

    3.5.1 私钥对象PrivateKey的IO

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

      这些函数用PEM格式对一个EVP_PKEY结构的私钥进行读写操作。写操作函数可以处理RSA或DSA类型的私钥。读操作函数还能透明的处理用PKCS#8格式加密和解密的私钥

    • 往文件中写入不加密的私钥的例子
    if (!PEM_write_PrivateKey(fp, key, NULL, NULL, 0, 0, NULL))
    {
        /* 错误处理代码 */
    }
    • 往BIO中写入一个私钥,采用3DES加密,加密口令提示输入的例子
    if (!PEM_write_bio_PrivateKey(bp, key, EVP_des_ede3_cbc(), NULL, 0, 0, NULL))
    {
        /* 错误处理代码 */
    }
    • 从BIO中读取一个私钥,使用”hello”作为解密口令的例子
    key = PEM_read_bio_PrivateKey(bp, NULL, 0, "hello");
    if (key == NULL)
    {
        /* 错误处理代码 */
    }
    • 从BIO中读取一个私钥,并使用回调函数获得解密口令的例子
    key = PEM_read_bio_PrivateKey(bp, NULL, pass_cb, "My Private Key");
    if (key == NULL)
    {
        /* 错误处理代码 */
    }

    3.5.2 符合PKCS8和PKCS5 v2.0标准的私钥对象PKCS8PrivateKey的IO

    
    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_PKCS8PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,
                                     char *kstr, int klen,pem_password_cb *cb, void *u);

      这两个函数使用PKCS#8标准保存EVP_PKEY里面的私钥到文件或者BIO中,并采用PKCS#5 v2.0的标准加密私钥。enc参数定义了使用的加密算法。跟其他PEM的IO函数不一样的是,本函数的加密是基于PKCS#8层次上的,而不是基于PEM信息字段的,所以这两个函数也是单独实现的函数,而不是宏定义函数。如果enc参数为NULL,那么就不会执行加密操作,只是使用PKCS#8私钥信息结构。成功执行返回大于0 的数,否则返回0。

      使用这两个函数保存的PEM对象可以使用3.5.1介绍的PEM_read_bio_PrivateKey或PEM_read_PrivateKey读出来。

    下面是一个将私钥保存为PKCS#8格式,并使用3DES算法进行加密,使用的口令是”hello”的例子

    if (!PEM_write_bio_PKCS8PrivateKey(bp, key, EVP_des_ede3_cbc(), NULL, 0, 0, "hello"))
    {
        /*出错处理代码*/
    }

    3.5.3 符合PKCS8和PKCS5 v1.5或PKCS12标准的私钥对象PKCS8PrivateKey的IO

    int       PEM_write_bio_PKCS8PrivateKey_nid(BIO *bp, EVP_PKEY *x, int nid,
                               char *kstr, int klen,pem_password_cb *cb, void *u);                               
    int       PEM_write_PKCS8PrivateKey_nid(FILE *fp, EVP_PKEY *x, int nid,
                                     char *kstr, int klen,pem_password_cb *cb, void *u);

      这两个函数也是单独实现的函数,而不是宏定义函数。他们也是将私钥保存成PKCS#8格式,但是采用的方式是PKCS#5 v1.5或者PKCS#12进行私钥的加密。nid参数指定了相应的加密算法,其值应该为相应对象的NID。成功执行返回大于0 的数,否则返回0。
      使用这两个函数保存的PEM对象可以使用3.5.1介绍的PEM_read_bio_PrivateKey或PEM_read_PrivateKey读出来。

    3.5.4 公钥对象PUBKEY的IO

    EVP_PKEY* PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_PUBKEY(BIO *bp, EVP_PKEY *x);
    
    EVP_PKEY* PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x,pem_password_cb *cb, void *u);
    int       PEM_write_PUBKEY(FILE *fp, EVP_PKEY *x);

      这四个函数对EVP_PKEY结构的公钥进行PEM格式的读写处理。公钥是作为SubjectPublicKeyInfo存储结构进行编码的。

    3.5.5 RSA私钥对象RSAPrivateKey的IO

    RSA *     PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSAPrivateKey(BIO *bp, RSA *x, const EVP_CIPHER *enc,
                              unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
    
    RSA *     PEM_read_RSAPrivateKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSAPrivateKey(FILE *fp, RSA *x, const EVP_CIPHER *enc,
                            unsigned char *kstr, int klen, pem_password_cb *cb, void *u);

      这四个函数对RSA结构的RSA私钥进行PEM格式的读写处理。它使用跟PrivateKey相同的函数进行处理,但如果私钥类型不是RSA,就会返回错误信息。

    3.5.6 RSA公钥对象RSAPublicKey的IO

    RSA *     PEM_read_bio_RSAPublicKey(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSAPublicKey(BIO *bp, RSA *x);
    
    RSA *     PEM_read_RSAPublicKey(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSAPublicKey(FILE *fp, RSA *x);
    

      这四个函数是对RSA结构的公钥进行PEM格式的读写处理。本函数使用PKCS#1 RSAPublicKey结构标准对RSA公钥进行编码操作

    3.5.7 RSA公钥对象RSA_PUBKEY的IO

    RSA *     PEM_read_bio_RSA_PUBKEY(BIO *bp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_RSA_PUBKEY(BIO *bp, RSA *x);
    
    RSA *     PEM_read_RSA_PUBKEY(FILE *fp, RSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_RSA_PUBKEY(FILE *fp, RSA *x);
    

      这四个函数也是对RSA结构的公钥进行PEM格式的读写处理。但是本函数使用SubjectPublicKeyInfo结构标准对RSA公钥进行编码操作,如果公钥类型不是RSA,就出错返回失败信息。

    3.5.8 DSA私钥对象DSAPrivateKey的IO函数

    DSA *     PEM_read_bio_DSAPrivateKey(BIO *bp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSAPrivateKey(BIO *bp, DSA *x, const EVP_CIPHER *enc,
                    unsigned char *kstr, int klen,pem_password_cb *cb, void *u);
    
    DSA *     PEM_read_DSAPrivateKey(FILE *fp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_DSAPrivateKey(FILE *fp, DSA *x, const EVP_CIPHER *enc,
                       unsigned char *kstr, int klen,pem_password_cb *cb, void *u);

      这些函数对以DSA结构存储的DSA私钥进行PEM格式的IO读写。它们使用的处理格式跟PrivateKey系列函数是相同的,但是如果私钥不是DSA类型的,则出错返回。

    3.5.9 DSA公钥对象DSA_PUBKEY的IO函数

    DSA *     PEM_read_bio_DSA_PUBKEY(BIO *bp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSA_PUBKEY(BIO *bp, DSA *x);
    
    DSA *     PEM_read_DSA_PUBKEY(FILE *fp, DSA **x,pem_password_cb *cb, void *u);
    int       PEM_write_DSA_PUBKEY(FILE *fp, DSA *x);

      这些函数对以DSA结构存储的DSA公钥进行PEM格式的IO读写。该公钥是以SubjectPublicKeyInfo结构进行编码的,如果公钥不是DSA类型,则将会出错返回。

    3.5.10 DSA参数对象DSAParams的IO函数

    DSA *     PEM_read_bio_DSAparams(BIO *bp, DSA **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_DSAparams(BIO *bp, DSA *x);
    
    DSA *     PEM_read_DSAparams(FILE *fp, DSA **x, pem_password_cb *cb, void *u);
    int       PEM_write_DSAparams(FILE *fp, DSA *x);

      这些函数对以DSA结构存储的DSA参数进行PEM格式的IO读写操作。

    3.5.11 DH参数对象DHParams的IO函数

    DH *      PEM_read_bio_DHparams(BIO *bp, DH **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_DHparams(BIO *bp, DH *x);
    
    DH *      PEM_read_DHparams(FILE *fp, DH **x, pem_password_cb *cb, void *u);
    int       PEM_write_DHparams(FILE *fp, DH *x);

      这些函数对以DH结构保存的DH参数进行PEM格式的IO读写操作,这些参数采用了PKCS#3的DH参数结构进行编码。

    3.5.12 X509证书对象X509的IO函数

    //X509------------------------------------------------------------------------------
    X509 *    PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509(BIO *bp, X509 *x);
    
    X509 *    PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_X509(FILE *fp, X509 *x);

      这些函数对以X509结构保存的X509证书进行PEM格式的IO读写操作,这些函数也可以对信任X509证书进行相同的操作,但是信任设置信息会丢失。

    3.5.13 X509信任证书对象X509_AUX的IO函数

    X509 *    PEM_read_bio_X509_AUX(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_AUX(BIO *bp, X509 *x);
    
    X509 *    PEM_read_X509_AUX(FILE *fp, X509 **x, pem_password_cb *cb, void *u);
    int       PEM_write_X509_AUX(FILE *fp, X509 *x);

      这些函数对以X509结构保存的信任X509证书进行PEM格式的IO读写操作。

    3.5.14 X509证书请求对象X509_REQ的IO函数

    X509_REQ* PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_REQ(BIO *bp, X509_REQ *x);
    
    X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x,pem_password_cb *cb, void *u);
    int       PEM_write_X509_REQ(FILE *fp, X509_REQ *x);
    
    //x509证书 写一个新请求
    int       PEM_write_bio_X509_REQ_NEW(BIO *bp, X509_REQ *x);
    int       PEM_write_X509_REQ_NEW(FILE *fp, X509_REQ *x);

      这些函数对以X509_REQ结构存储的符合PKCS#10标准的证书请求进行PEM格式的IO读写操作。不同的是,X509_REQ系列写函数使用CERTIFICATE REQUEST作为头,而X509_REQ_NEW系列写函数则采用NEW CERTIFICATE REQUEST作为头(一些CA要求这种格式)。而X509_REQ读函数对这两种情况都能处理,所以没有X509_REQ_NEW的读函数了。

    3.5.15 X509吊销列表对象X509_CRL的IO函数

    X509_CRL* PEM_read_bio_X509_CRL(BIO *bp, X509_CRL **x,pem_password_cb *cb, void *u);
    int       PEM_write_bio_X509_CRL(BIO *bp, X509_CRL *x);
    
    X509_CRL* PEM_read_X509_CRL(FILE *fp, X509_CRL **x,pem_password_cb *cb, void *u);
    int       PEM_write_X509_CRL(FILE *fp, X509_CRL *x);

      这些函数对以X509_CRL结构存储的X509 CRL进行PEM格式的IO读写操作。

    3.5.16 PKCS#7编码内容对象的PKCS7的IO函数

    PKCS7 *   PEM_read_bio_PKCS7(BIO *bp, PKCS7 **x, pem_password_cb *cb, void *u);
    int       PEM_write_bio_PKCS7(BIO *bp, PKCS7 *x);
    
    PKCS7 *   PEM_read_PKCS7(FILE *fp, PKCS7 **x, pem_password_cb *cb, void *u);
    int       PEM_write_PKCS7(FILE *fp, PKCS7 *x);

      这些函数对以PKCS7结构存储的PKCS#7内容信息进行PEM格式的IO读写操作。

    3.5.17 Netscape证书序列对象NETSCAPE_CERT_SEQUENCE的IO函数

    NETSCAPE_CERT_SEQUENCE* PEM_read_bio_NETSCAPE_CERT_SEQUENCE(BIO*bp,NETSCAPE_CERT_SEQUENCE **x,
                                                 pem_password_cb *cb, void *u);
    int                     PEM_write_bio_NETSCAPE_CERT_SEQUENCE(BIO *bp, NETSCAPE_CERT_SEQUENCE *x);
    
    NETSCAPE_CERT_SEQUENCE* PEM_read_NETSCAPE_CERT_SEQUENCE(FILE *fp,NETSCAPE_CERT_SEQUENCE **x,
                                           pem_password_cb *cb, void *u);
    int                     PEM_write_NETSCAPE_CERT_SEQUENCE(FILE *fp, NETSCAPE_CERT_SEQUENCE *x);

      这些函数对以NETSCAPE_CERT_SEQUENCE结构存储的Netscape证书序列进行PEM格式的IO读写操作。

    3.6 回调函数例子

    3.6.1获取口令回调函数的例子

      在PEM读写的过程中,特别对于私钥文件,可能经常要使用到获取口令的回调函数,在签名我们介绍的一些列函数也可以看出,基本上都是带有回调函数的参数的。openssl缺省的回调函数是基于命令行的,在许多情况下可能并不适应,这就要求用户自己定义回调函数。在前面的相关章节,我们已经介绍了该回调函数的格式,现在我们给出一个回调函数的实现例子。

    int pass_cb(char *buf, int size, int rwflag, void *u);
    {
    
        int len;
        char *tmp;
        /* rwflag是一个标准,如果为1,可能还需要作些别的处理工作*/
        printf("输入口令: \"%s\"\n", u);
        /* 这里应该是得到口令的代码*/
        tmp = "hello";
        len = strlen(tmp);
        if (len <= 0) return 0;
        /* 如果口令超出给定长度,就把多余的删掉 */
        if (len > size) len = size;
        memcpy(buf, tmp, len);
        return len;
    }

    3.6.2使用PEM系列函数常犯的一个错误

      PEM系列函数的格式和参数基本相同,下面是一个常犯的导致错误的用法。

    X509 *x;
    PEM_read_bio_X509(bp, &x, 0, NULL);

      这样的用法会导致出现不可预测的错误,因为x并没有进行初始化,分配内存空间,而接下来调用的函数却会往x里面写入数据,导致内存非法操作。这也是openssl本身没有处理好的一个BUG.


      版权声明本文根据DragonKing牛,E-Mail:wzhah@263.NET发布在https://openssl.126.com的系列文章整理修改而成(这个网站已经不能访问了),我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!


    展开全文
  • 前言:PEM是OpenSSL和许多其他SSL工具的标准格式,OpenSSL 使用PEM 文件格式存储证书和密钥。这种格式被设计用来安全的包含在ascii甚至富文本文档中,如电子邮件。这意味着您可以简单的复制和粘贴pem文件的内容到...

    前言:

    PEMOpenSSL和许多其他SSL工具的标准格式,OpenSSL 使用PEM 文件格式存储证书和密钥。这种格式被设计用来安全的包含在ascii甚至富文本文档中,如电子邮件。这意味着您可以简单的复制和粘贴pem文件的内容到另一个文档中。

    PEM文件Base64编码的证书。PEM证书通常用于web服务器,因为他们可以通过一个简单的文本编辑器,很容易地转换成可读的数据。通常当一个PEM编码在文本编辑器中打开文件,它会包含不同的页眉和页脚。

    -----BEGIN CERTIFICATE REQUEST----- and -----END CERTIFICATEREQUEST-----

    CSR(证书签名请求)

    -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATEKEY-----

    私钥

    -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----

    证书文件

    PKCS #8: Private-Key Information Syntax(语法) Standard(标准)

    OpenSSL:是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。

    OpenSSL整个软件包大概可以分成三个主要的功能部分:密码算法库、SSL协议库以及应用程序。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。

    一、OpenSSL生成pem格式公私钥

    1、生成RSA私钥

    openssl genrsa -out rsa_private_key.pem1024

     

    该命令会生成1024位的私钥,运行,如下图:

     

    生成私钥文件rsa_private_key.pem,内容如下:


    用记事本方式打开它,可以看到-----BEGIN RSA PRIVATE KEY-----开头,-----END RSA PRIVATE KEY-----结尾的字符串,这个就是原始的私钥。

     

    备注:

             若运行openssl.exe,会进入OpenSSL命令行界面,此时输入命令时,则无需再写openssl。(只是该命令行界面中,暂时无法拷贝,本人太懒,不太喜欢)

     

    2、RSA私钥转换成PKCS8格式

    openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem-outform PEM -nocrypt

     

    可以看到,控制台打印出的内容,-----BEGIN PRIVATE KEY-----开头,-----END PRIVATE KEY-----结尾的字符串,这个就是PKCS#8格式的私钥。


    备注:

    使用该命令,将私钥转成PKCS#8格式,但原rsa_private_key.pem文件中的私钥字符串并没有任何变化。但控制台输出的private key,跟rsa_private_key.pem文件中的private key,不一样。若需使用PKCS8格式的私钥,即控制台中显示的私钥,将其拷贝出来即可。


    切记:

    opensslpkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM nocrypt

    “-”书写正确,上述nocrypt,因为前面的不是英文的,导致nocrypt失效,所以还需要输入密码。需要输入两次密码,运行,如下图:


           可以看到,控制台打印出的内容,-----BEGIN ENCRYPTED PRIVATE KEY-----开头,-----END ENCRYPTED PRIVATE KEY-----结尾的字符串,这个就是加了密的PKCS#8格式的私钥。

           因为输入了密码,转换后的字符串不一样,会比加了-nocrypt的长一些,所以原来的程序解析不了该私钥字符串,会出错。

     

    3、生成RSA公钥

    openssl rsa -in rsa_private_key.pem-pubout -out rsa_public_key.pem

     

    运行,如下图:

     


    生成公钥文件rsa_public_key.pem内容如下:


             用记事本方式打开它,可以看到-----BEGIN PUBLIC KEY-----开头,-----END PUBLIC KEY-----结尾的字符串,这个就是公钥。

     

    4、Java使用pem文件内容,示例代码

    1)私钥签名

    a)获取私钥

    //获取KeyFactory,指定RSA算法

    KeyFactorykeyFactory = KeyFactory.getInstance("RSA");

    //BASE64编码的私钥字符串进行解码

    BASE64Decoderdecoder = newBASE64Decoder();

    byte[] encodeByte = decoder.decodeBuffer(priKey);

    //BASE64解码后的字节数组,构造成PKCS8EncodedKeySpec对象,生成私钥对象

    PrivateKeyprivatekey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodeByte));

            

    b)使用私钥,对数据进行签名

             //获取Signature实例,指定签名算法(本例使用SHA1WithRSA

    Signaturesignature = Signature.getInstance("SHA1WithRSA");

    //加载私钥

    signature.initSign(privatekey);

    //更新待签名的数据

    signature.update(plain.getBytes("UTF-8"));

    //进行签名

    byte[] signed = signature.sign();

    //将加密后的字节数组,转换成BASE64编码的字符串,作为最终的签名数据

    BASE64Encoderencoder = newBASE64Encoder();

    return encoder.encode(signed);

     

    2)公钥验签

    a)获取公钥

    //获取KeyFactory,指定RSA算法

    KeyFactorykeyFactory = KeyFactory.getInstance("RSA");

       //BASE64编码的公钥字符串进行解码

    BASE64Decoderdecoder = newBASE64Decoder();

    byte[] encodeByte = decoder.decodeBuffer(pubKey);

    //BASE64解码后的字节数组,构造成X509EncodedKeySpec对象,生成公钥对象

    PublicKeypublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodeByte));

     

    b)使用公钥,进行验签

    //获取Signature实例,指定签名算法(与之前一致)

    Signaturesignature = Signature.getInstance("SHA1WithRSA");

    //加载公钥

    signature.initVerify(publicKey);

    //更新原数据

    signature.update(plain.getBytes("UTF-8"));

    //公钥验签(true-验签通过;false-验签失败)

    BASE64Decoderdecoder = newBASE64Decoder();

    returnsignature.verify(decoder.decodeBuffer(sign));

            

             备注:

    验签时,签名数据需要先BASE64解码

    展开全文
  • PEM文件

    2018-01-18 13:57:56
    OpenSSL 使用 PEM 文件格式存储证书和密钥。PEM 实质上是 Base64 编码的二进制内容,再加上开始和结束行,如证书文件的-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----。在这些标记外面可以有额外的信息,...
  • 文件 - 介绍 含PEM文件

    2020-04-14 18:00:53
    1.文件分类 文件是指由创建者所定义的、具有文件名的一组相关元素的集合,可分为有结构文件和无结构文件两种。在有结构的文件中,文件由若干个相关记录组成;而无结构文件则被看成是一个字符流。...
  • 在ssh协议中,采用一种非对称加密的方式保障通信安全,即a,b通过tcp建立连接后,b生成一对公私钥并将公钥发送给a,a再将密钥用b的公钥发送给a,后续通信便使用...常见公钥后缀:pem crt key 常见私钥后缀:pfx p...
  • 通常C#使用xml格式的密钥文件,不能使用Linux下的公钥和私钥pem文件。而XML格式密钥对文件有移植性问题,在一个电脑上能用的xml密钥对,换一个电脑不一定能使用(需要从pem文件重新转换为xml)。本源码直接使用linux...
  • C#使用pem格式的密钥对文件来做RSA加解密接签名和验证签名,这里的pem文件是openssl命令生成的密钥对文件,其中私钥pem文件需要用openssl命令转换成pkcs8格式的pem文件。如果已有pem文件,也可以通过openssl命令转换...
  • PEM格式 PEM格式是证书颁发机构颁发证书的最常见格式.PEM证书通常具有扩展名,例如.pem,.crt,.cer和.key。它们是Base64编码的ASCII文件,包含“----- BEGIN CERTIFICATE -----”和“----- END CERTIFICATE -----...
  • keystore转x509pem工具

    2020-07-30 23:32:24
    在使用signapk工具签名时,是需要 x509.pem + pk8格式的证书,它是一个公私钥分开存放的格式,在电脑上生成的证书一般是以 keystore格式存放的,有时在证书签发机构申请的证书也是 keystore格式的。这时用signapk...
  • Openssl之PEM系列

    2017-03-13 10:36:24
    1.PEM编码文件结构介绍  PEM全称是Privacy Enhanced Mail,该标准定义了加密一个准备要发送邮件的标准,主要用来将各种对象保存成PEM格式,并将PEM格式的各种对象读取到相应的结构中。它的基本流程是这样的: 1. ...
  • DER、CRT、CER、PEM格式的证书及转换 一、证书和编码 X.509证书,其核心是根据RFC 5280编码或数字签名的数字文档。 实际上,术语X.509证书通常指的是IETF的PKIX证书和X.509 v3证书标准的CRL 文件,即如RFC ...
  • 对接第三方接口时,对方有时只给了公钥私钥,这就需要自己来生成pem文件, OpenSSL在线生成RSA 公私钥,同时把私钥在线生成合成PEM文件,公钥直接放到txt,改后缀为.pem, 对于公钥,新建txt,将公钥内容保存成.pem...
  • Der 、Cer、 Pfx、 Pem它们都是扩展名(文件名的后缀,代表格式) .DER:用二进制DER编码的证书;.PEM:用ASCLL(BASE64)编码的证书;.CER:存放公钥,没有私钥;.PFX:存放公钥和私钥(pem 后缀的证书都是base64...
  • ssl证书生成pem文件

    2020-05-13 10:21:52
    而Nginx或者其他SSL服务加载为pem格式,现转换方法如下: 第一步: mkdir /etc/ssl/cert/domain.com cd /etc/ssl/cert/domain.com 第二步: openssl rsa -in domain.com.key -text > key.pem openssl x509 -...
  • keystore转x509pem+pk8工具

    2020-07-21 09:59:41
    对于android开发人员来说,一般只会用到keystore,但有些特殊情况(比如说做自动打包工具)就需要用signapk工具签名,这时需要的就是x509pem+pk8了。 但是网上用keystore生成x509pem+pk8的教程实在太复杂,分了好几...
  • Privacy-Enhanced Mail (PEM) 本来是研究来给邮件加密的,现在主要成为一种约定俗成的文件格式,用于存储和发送加密密钥,证书或其他数据。 简单理解就是格式如下这种文件。 -----BEGIN XXX----- BASE64 -----END ...
  • DER 和 PEM 格式

    2017-04-03 17:26:22
    编码 (也用于扩展名) .DER = 扩展名DER用于二进制DER编码的... 扩展名PEM用于ASCII(Base64)编码的各种X.509 v3 证书。文件开始由一行"—– BEGIN …“开始。 der类型的不用在编码解码,直接就是二进制的数
1 2 3 4 5 ... 20
收藏数 63,276
精华内容 25,310
关键字:

pem