自学内容网 自学内容网

VC++网络安全编程范例(12)-PKI编程

PKI(Public Key Infrastructure ) 即"公钥基础设施",是一种遵循既定标准的密钥管理平台,它能够为所有网络应用提供加密和数字签名等密码服务及所必需的密钥和证书管理体系,简单来说,PKI就是利用公钥理论和技术建立的提供安全服务的基础设施。PKI技术是信息安全技术的核心,也是电子商务的关键和基础技术。   

PKI的基础技术包括加密、数字签名、数据完整性机制、数字信封、双重数字签名等。


PKI是Public Key Infrastructure的缩写,是指用公钥概念和技术来实施和提供安全服务的具有普适性的安全基础设施。这个定义涵盖的内容比较宽,是一个被很多人接受的概念。

这个定义说明,任何以公钥技术为基础的安全基础设施都是PKI。当然,没有好的非对称算法和好的密钥管理就不可能提供完善的安全服务,也就不能叫做PKI。也就是说,该定义中已经隐含了必须具有的密钥管理功能。  

 X.509标准中,为了区别于权限管理基础设施(Privilege Management Infrastructure,简称PMI),将PKI定义为支持公开密钥管理并能支持认证、加密、完整性和可追究性服务的基础设施]。这个概念与第一个概念相比,不仅仅叙述PKI能提供的安全服务,更强调PKI必须支持公开密钥的管理。也就是说,仅仅使用公钥技术还不能叫做PKI,还应该提供公开密钥的管理。因为PMI仅仅使用公钥技术但并不管理公开密钥,所以,PMI就可以单独进行描述了而不至于跟公钥证书等概念混淆。X.509中从概念上分清PKI和PMI有利于标准的叙述。然而,由于PMI使用了公钥技术,PMI的使用和建立必须先有PKI的密钥管理支持。也就是说,PMI不得不把自己与PKI绑定在一起。当我们把两者合二为一时,PMI+PKI就完全落在X.509标准定义的PKI范畴内。根据X.509的定义,PMI+PKI仍旧可以叫做PKI,而PMI完全可以看成PKI的一个部分。   美国国家审计总署在2001年[iii]和2003年的报告中都把PKI定义为由硬件、软件、策略和人构成的系统,当完善实施后,能够为敏感通信和交易提供一套信息安全保障,包括保密性、完整性、真实性和不可否认。尽管这个定义没有提到公开密钥技术,但到目前为止,满足上述条件的也只有公钥技术构成的基础设施,也就是说,只有第一个定义符合这个PKI的定义。所以这个定义与第一个定义并不矛盾。  

 综上所述,我们认为:PKI是用公钥概念和技术实施的,支持公开密钥的管理并提供真实性、保密性、完整性以及可追究性安全服务的具有普适性的安全基础设施。
完整的PKI系统必须具有权威认证机构(CA)、数字证书库、密钥备份及恢复系统、证书作废系统、应用接口(API)等基本构成部分,构建PKI也将围绕着这五大系统来着手构建。   PKI技术是信息安全技术的核心,也是电子商务的关键和基础技术。PKI的基础技术包括加密、数字签名、数据完整性机制、数字信封、双重数字签名等。一个典型、完整、有效的PKI应用系统至少应具有以下部分:

   · 公钥密码证书管理。  

 · 黑名单的发布和管理。  

 · 密钥的备份和恢复。  

 · 自动更新密钥。  

 · 自动管理历史密钥。  

 · 支持交叉认证。   

认证机构(CA):即数字证书的申请及签发机关,CA必须具备权威性的特征;  

 数字证书库:用于存储已签发的数字证书及公钥,用户可由此获得所需的其他用户的证书及公钥;  

 密钥备份及恢复系统:如果用户丢失了用于解密数据的密钥,则数据将无法被解密,这将造成合法数据丢失。

为避免这种情况,PKI提供备份与恢复密钥的机制。但须注意,密钥的备份与恢复必须由可信的机构来完成。并且,密钥备份与恢复只能针对解密密钥,签名私钥为确保其唯一性而不能够作备份。   证书作废系统:证书作废处理系统是PKI的一个必备的组件。与日常生活中的各种身份证件一样,证书有效期以内也可能需要作废,原因可能是密钥介质丢失或用户身份变更等。为实现这一点,PKI必须提供作废证书的一系列机制。

  应用接口(API):PKI的价值在于使用户能够方便地使用加密、数字签名等安全服务,因此一个完整的PKI必须提供良好的应用接口系统,使得各种各样的应用能够以安全、一致、可信的方式与PKI交互,确保安全网络环境的完整性和易用性。   通常来说,CA是证书的签发机构,它是PKI的核心。众所周知,构建密码服务系统的核心内容是如何实现密钥管理。公钥体制涉及到一对密钥(即私钥和公钥),私钥只由用户独立掌握,无须在网上传输,而公钥则是公开的,需要在网上传送,故公钥体制的密钥管理主要是针对公钥的管理问题,目前较好的解决方案是数字证书机制。


PKI就是一种基础设施,其目标就是要充分利用公钥密码学的理论基础,建立起一种普遍适用的基础设施,为各种网络应用提供全面的安全服务。公开密钥密码为我们提供了一种非对称性质,使得安全的数字签名和开放的签名验证成为可能。而这种优秀技术的使用却面临着理解困难、实施难度大等问题。正如让电视机的开发者理解和维护发电厂有一定的难度一样,要让每一个应用程序的开发者完全正确地理解和实施基于公开密钥密码的安全有一定的难度。PKI希望通过一种专业的基础设施的开发,让网络应用系统的开发人员从繁琐的密码技术中解脱出来而同时享有完善的安全服务。  

 将PKI在网络信息空间的地位与电力基础设施在工业生活中的地位进行类比可以更好地理解PKI。电力基础设施,通过伸到用户的标准插座为用户提供能源,而PKI通过延伸到用户本地的接口,为各种应用提供安全的服务。有了PKI,安全应用程序的开发者可以不用再关心那些复杂的数学运算和模型,而直接按照标准使用一种插座(接口)。正如电冰箱的开发者不用关心发电机的原理和构造一样,只要开发出符合电力基础设施接口标准的应用设备,就可以享受基础设施提供的能源。   

PKI与应用的分离也是PKI作为基础设施的重要标志。正如电力基础设施与电器的分离一样。网络应用与安全基础实现了分离,有利于网络应用更快地发展,也有利于安全基础设施更好地建设。正是由于PKI与其他应用能够很好地分离,才使得我们能够将之称为基础设施,PKI也才能从千差万别的安全应用中独立出来,才能有效地独立地发展壮大。PKI与网络应用的分离实际上就是网络社会的一次“社会分工”,这种分工可能会成为网络应用发展史上的重要里程碑。


PKI作为一种安全技术,已经深入到网络的各个层面。这从一个侧面反映了PKI强大的生命力和无与伦比的技术优势。PKI的灵魂来源于公钥密码技术,这种技术使得“知其然不知其所以然”成为一种可以证明的状态,使得网络上的数字签名有了理论上的安全保障。围绕着如何用好这种非对称密码技术,数字证书破壳而出,并成为PKI中最为核心的元素。  

 PKI的优势主要表现在:  

 1、 采用公开密钥密码技术,能够支持可公开验证并无法仿冒的数字签名,从而在支持可追究的服务上具有不可替代的优势。这种可追究的服务也为原发数据完整性提供了更高级别的担保。支持可以公开地进行验证,或者说任意的第三方可验证,能更好地保护弱势个体,完善平等的网络系统间的信息和操作的可追究性。  

 2、 由于密码技术的采用,保护机密性是PKI最得天独厚的优点。PKI不仅能够为相互认识的实体之间提供机密性服务,同时也可以为陌生的用户之间的通信提供保密支持。   

3、 由于数字证书可以由用户独立验证,不需要在线查询,原理上能够保证服务范围的无限制地扩张,这使得PKI能够成为一种服务巨大用户群的基础设施。PKI采用数字证书方式进行服务,即通过第三方颁发的数字证书证明末端实体的密钥,而不是在线查询或在线分发。这种密钥管理方式突破了过去安全验证服务必须在线的限制。   

4、 PKI提供了证书的撤销机制,从而使得其应用领域不受具体应用的限制。撤销机制提供了在意外情况下的补救措施,在各种安全环境下都可以让用户更加放心。另外,因为有撤销技术,不论是永远不变的身份、还是经常变换的角色,都可以得到PKI的服务而不用担心被窃后身份或角色被永远作废或被他人恶意盗用。为用户提供“改正错误”或“后悔”的途径是良好工程设计中必须的一环。  

 5、 PKI具有极强的互联能力。不论是上下级的领导关系,还是平等的第三方信任关系,PKI都能够按照人类世界的信任方式进行多种形式的互联互通,从而使PKI能够很好地服务于符合人类习惯的大型网络信息系统。PKI中各种互联技术的结合使建设一个复杂的网络信任体系成为可能。PKI的互联技术为消除网络世界的信任孤岛提供了充足的技术保障。


在寻找PKI的招牌应用(Killer Application)屡告失败后,PKI突破性的增长并没有出现。大量的投资进入了PKI建设中却没有带来预期的收益。有人说PKI根本就是一种骗术,有些人说PKI已经死了,而有人说它没有死,只是在休息。    

 2001年,美国审计总署总结PKI发展面临的挑战时指出,互操作问题,系统费用昂贵等是当时的主要困难。2003年美国审计总署总结联邦PKI发展问题时仍旧强调,在PKI建设中,针对技术问题和法律问题,在很多地方缺乏策略和指南或者存在错误的策略和指南;实施费用高,特别是在实施一些非标准的接口时资金压力更大;互操作问题依然突出,PKI系统与其他系统的集成时面临已有系统的调整甚至替换的问题;使用和管理PKI需要更多培训,PKI的管理仍旧有严重障碍。  

 尽管PKI建设的问题很多,也没有出现如同人们想象的突破性的发展,但我们仍旧不难发现,所有这些挑战,实际上都源于PKI技术的复杂。比如,实施者对标准的理解不一致是造成互操作问题一个重要原因。目前,随着人们研究的深入,标准的出台,更多实施者的参与,更多应用的推进都会极大推进互操作性问题的解决。大量的技术人员参与建设,也会加速PKI产品的降价,降低PKI用户的购买成本。随着用户对PKI的深入了解,使用和维护PKI也将不是一个昂贵的过程。诸多的困难,并没有阻挡,也不可能阻挡PKI的应用的脚步。PKI已经逐步深入到网络应用的各个环节。PKI的诸多优势使得PKI的应用逐步扩大。  

 举一个例子,电子商务中需要可追究性保护,而PKI提供的任意第三方可验证能够提供一个更加公平的环境支持。比如,甲方乙方签订合同后,不用担心对方跟某位领导(也许可以称为权威方)有什么关系,因为任意的一个第三方都可以验证合法的合同内容。这种公开的验证方式使得商务活动可以变得更加民主与和谐。这样公平的技术环境是对称密码技术和基于身份的密码技术无法提供的。在一个企业内部,PKI也可以为构建一个更加民主的商务环境提供技术支持。比如,聪明的老板或领导一定是言而有信的领导,肯定会将下属或员工看成自己的合作者,也愿意一样接受制度的制约。PKI这种第三方验证的方式鼓励了这种平等的合作,表明了一种“在法律面前人人平等”的民主作风。而使用对称密码技术的员工却不得不无条件地信任密钥分发中心,对称密码的管理系统没有能力提供追究领导责任的技术途径。  

 PKI提供的安全服务支持了许多以前无法完成的应用。PKI技术可以保证运行代码正确地通过网络下载而不被黑客篡改;可以保证数字证件,比如护照的真实性,而不用担心被证件阅读者假冒;可以用于版权保护而不用担心没有证据;可以用于负责任的新闻或节目分级管理从而净化文化环境,等等。

  PKI技术并没有一个招牌应用,也没有人们想象中那么迅速的发展。然而,也许正是没有招牌应用才使得PKI能够成为所有应用的安全基础;没有快速发展也许说明PKI的发展不会是昙花一现,而是经久不衰。作为一项目前还没有替代品的技术,PKI正逐步得到更加广泛的应用。

我们来用VC++实现使用CA私钥签署CSR以生成一个正式的509证书,请见代码实现与注释讲解

#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <openssl/pem.h>

//Key 和 certificate都要包括
#define CA_FILE   DATA_DIR"\\serverca.pem"

#define CSR_FILE  DATA_DIR"\\test_csr_req.pem"
//新生成的CSR签名的证书
#define CERT_FILE DATA_DIR"\\client_cert_new.crt"

//expiration seconds(100days), openssl.exe use one-month as default.
#define EXPIRE_SECONDS (60*60*24*100)

st_keyvalue ext_ent[] =
{
    { "basicConstraints", "CA:FALSE" },//此证书非CA
    { "nsComment", "Certificate Generated By Jian Shen via Openssl API" },
    //"hash"表示由系统依据PKIX指南自动选择key identifier.
    { "subjectKeyIdentifier", "hash" },
    //等价于"keyid:always,issuer:always",由证书使用者解释
    { "authorityKeyIdentifier", "keyid:always,issuer:always" },
    //密钥用途列表参见crypto\x509v3\v3bitst.c中的代码或者openssl.txt
    { "keyUsage","nonRepudiation,digitalSignature,keyAgreement,keyEncipherment" }
};

#define EXT_COUNT sizeof(ext_ent)/sizeof(ext_ent[0])

int main(int argc, char *argv[])
{
    int i, subjAltName_pos;
    long serial = 1000;
    EVP_PKEY *pkey, *CApkey;
    const EVP_MD *digest;
    //用服务器CA证书去签名客户端
    X509 *cert, *ServerCAcert;//新证书对象以及服务器CA证书
    X509_REQ *req;//CSR对象
    X509_NAME *name;
    X509V3_CTX ctx;
    X509_EXTENSION *subjAltName;
    STACK_OF(X509_EXTENSION) *req_exts;
    FILE *fp;
    BIO *out;
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();
    //打来标准输出,输出证书信息
    //X509V3_EXT_print需要BIO*作为参数,这就是我们需要做的
    if (!(out = BIO_new_fp(stdout, BIO_NOCLOSE)))
        int_error("创建stdout BIO时出错");
    //读入CSR
    if (!(fp = fopen(CSR_FILE, "r")))
        int_error("打开CSR请求文件时出错");
    if (!(req = PEM_read_X509_REQ(fp, NULL, NULL, NULL)))
        int_error("读CSR请求时出错");
    fclose(fp);
    //验证基于该请求的签名
    if (!(pkey = X509_REQ_get_pubkey(req)))
        int_error("从请求中获取公钥时出错");
    if (X509_REQ_verify(req, pkey) != 1)
        int_error("验证证书中的签名时出错");
    //读入CA certificate
    if (!(fp = fopen(CA_FILE, "r")))
        int_error("打开CA证书文件时出错");

    if (!(ServerCAcert = PEM_read_X509(fp, NULL, NULL, NULL)))
        int_error("从CA证书文件中读取证书时出错");
    // 读入CA 私钥
    if (!(CApkey = PEM_read_PrivateKey(fp, NULL, NULL, "srvca")))
        int_error("从文件中读取CA私钥时出错");
    fclose(fp);
    
    if (!(name = X509_REQ_get_subject_name(req)))
        int_error("从请求中获取主题名时出错");
    X509_NAME_print(out, name, 0);
    fputc('\n', stdout);

    if (!(req_exts = X509_REQ_get_extensions(req)))
        int_error("获取证书扩展时出错");

    //获取可选subject信息
    subjAltName_pos = X509v3_get_ext_by_NID(req_exts,OBJ_sn2nid("subjectAltName"), -1);
    subjAltName = X509v3_get_ext(req_exts, subjAltName_pos);
    X509V3_EXT_print(out, subjAltName, 0, 0);
    fputc('\n', stdout);
    //创建新证书
    if (!(cert = X509_new()))
        int_error("创建 X509 object时出错");
    //设置证书的版本号和序列号
    if (X509_set_version(cert, 2L) != 1)
        int_error("设置证书版本时出错"); 
    //序列号应该从序列号文件中读取
    ASN1_INTEGER_set(X509_get_serialNumber(cert), serial++);
    //从请求和CA设置证书的发行者和主题名
    if (!(name = X509_REQ_get_subject_name(req)))
        int_error("从请求中获取主题名时出错");

    if (X509_set_subject_name(cert, name) != 1)
        int_error("设置证书的主题名时出错");

    if (!(name = X509_get_subject_name(ServerCAcert)))
        int_error("从CA证书中获取主题名时出错");

    if (X509_set_issuer_name(cert, name) != 1)
        int_error("设置证书发行名时出错");
    //设置公钥
    if (X509_set_pubkey(cert, pkey) != 1)
        int_error("设置证书的公钥时出错");
    //设置开始时间
    if (!(X509_gmtime_adj(X509_get_notBefore(cert), 0)))
        int_error("设置证书的启用时间时出错");

    if (!(X509_gmtime_adj(X509_get_notAfter(cert), EXPIRE_SECONDS)))
        int_error("设置证书的结束时间时出错");
    //添加x509 v3扩展
    X509V3_set_ctx(&ctx, ServerCAcert, cert, NULL, NULL, 0);
    printf("\nEXT_COUNT=%d\n",EXT_COUNT);
    for (i = 0; i < EXT_COUNT; i++)
    {
        X509_EXTENSION *ext;
        printf("%s=%s\n",ext_ent[i].key, ext_ent[i].value);
        if (!(ext = X509V3_EXT_conf(NULL, &ctx,ext_ent[i].key, ext_ent[i].value)))
        {
            printf("Error on \"%s = %s\"\n",ext_ent[i].key, ext_ent[i].value);
            int_error("创建X509扩展对象时出错");
        }
        if (!X509_add_ext(cert, ext, -1))
        {
            printf("Error on \"%s = %s\"\n",ext_ent[i].key, ext_ent[i].value);
            int_error("增加 X509 extension到证书中时出错");
        }
        X509_EXTENSION_free(ext);
    }
    //add the subjectAltName in the request to the cert
    if (!X509_add_ext(cert, subjAltName, -1))
        int_error("增加subjectAltName到证书中时出错");
    //根据公钥的类型选择摘要算法
    if( !(digest=getDigestByPkeyType(CApkey->type)))
        int_error("为一个可用的摘要检查CA私钥时出错");
    //根据serverCA的私钥签名证书
    if (!(X509_sign(cert, CApkey, digest)))
        int_error("签署证书时出错");
    
    if (!(fp = fopen(CERT_FILE, "w")))
        int_error("打开文件时出错");
    //以PEM格式写完整的签名证书到文件
    if (PEM_write_X509(fp, cert) != 1)
        int_error("写入证书时出错");
    fclose(fp);
    return 0;
}

 我们来用VC++实现生成客户端CSR,请见代码实现与注释讲解

#include <stdio.h>
#include <stdlib.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
#include <openssl/pem.h>
//利用客户端私钥产生CSR
#define PKEY_FILE DATA_DIR"\\client.pem"
//生成的CSR文件
#define CSR_FILE  DATA_DIR"\\test_csr_req.pem"

//预设在证书中的各个字段,用于标志证书拥有者身份
st_keyvalue entries[] =
{
    { "countryName", "CN" },
    { "stateOrProvinceName", "GD" },
    { "localityName", "ShenZhen" },
    { "organizationName", "Cosmichut Ltd." },
    { "organizationalUnitName", "software" },
    { "commonName", "Client For Test" },
};

#define ENTRY_COUNT sizeof(entries)/sizeof(entries[0])

int main(int argc, char *argv[])
{
    int i;
    X509_REQ *req;//证书请求对象
    X509_NAME *x509_name;//X509名字对象,其中包含一个栈
    EVP_PKEY *pkey;
    const EVP_MD *digest;//摘要算法对象
    FILE *fp;
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();
    //首先打开私钥文件
    if (!(fp = fopen(PKEY_FILE, "r")))
        int_error("读私钥文件时出错");
    //从客户端公私钥文件中读入私钥,指定私钥证书口令。
    //如果设为NULL,则系统会弹出控制台窗口强制要求输入口令。
    if (!(pkey = PEM_read_PrivateKey(fp, NULL, NULL, "iamclnt")))
        int_error("读私钥时出错");
    fclose(fp);
    //创建一个新的空的请求
    if (!(req = X509_REQ_new()))
        int_error("创建X509_REQ证书请求对象时失败");
    //为请求对象设置公钥
    X509_REQ_set_pubkey(req, pkey);
    //生成X509_NAME对象
    if (!(x509_name = X509_NAME_new()))
        int_error("创建X509_NAME对象时失败");
    for (i = 0; i < ENTRY_COUNT; i++)
    {
        int nid;
        X509_NAME_ENTRY *ent;
        //依据字段名查找NID值,每个合法的字段名都对应一个有效NID值
        if ((nid = OBJ_txt2nid(entries[i].key)) == NID_undef)
        {
            fprintf(stderr, "为%s查找NID时出错\n",
                                entries[i].key);
            int_error("查找证书中字段名时出错");
        }
        //依据NID,以及其值创建X509_NAME_ENTRY对象
        if (!(ent = X509_NAME_ENTRY_create_by_NID(NULL, nid,
            MBSTRING_ASC,(unsigned char*)entries[i].value, -1)))
            int_error("从NID中创建名字项出错");
        //将X509_NAME_ENTRY对象加入到x509_name对象中
        if (X509_NAME_add_entry(x509_name, ent, -1, 0) != 1)
            int_error("增加实体到名称中时出错");
    }
    //将上述信息放入CSR的subject域中
    if (X509_REQ_set_subject_name(req, x509_name) != 1)
        int_error("增加主题到请求中时出错");

    //增加某些我们所想要的扩展
    X509_EXTENSION *ext;
    //下面是一个类型(类型为X509_EXTENSION)安全的栈指针
    STACK_OF(X509_EXTENSION) *extlist;
    //可选的扩展主题
    char *name = "subjectAltName";
    char *value = "DNS:dns.example.com";//加入DNS
    extlist = sk_X509_EXTENSION_new_null();
    if (!(ext = X509V3_EXT_conf(NULL, NULL, name, value)))
            int_error("创建subjectAltName扩展时出错");
    sk_X509_EXTENSION_push(extlist, ext);
    if (!X509_REQ_add_extensions(req, extlist))
        int_error("增加subjectAltName到请求中时出错");
    //释放扩展主题对象
    sk_X509_EXTENSION_pop_free(extlist, X509_EXTENSION_free);
    //根据公钥类型选者摘要算法
    if( !(digest=getDigestByPkeyType(pkey->type)))
        int_error("为公钥类型获取一个有效的摘要时出错");
    //利用自己的私钥签名CSR
    if (!(X509_REQ_sign(req, pkey, digest)))
        int_error("签署请求时出错");

    if (!(fp = fopen(CSR_FILE, "w")))
        int_error("打开请求文件时出错");
    //将CSR写入文件中
    if (PEM_write_X509_REQ(fp, req) != 1)
    {
        fclose(fp);
        int_error("写请求时出错");
    }
    fclose(fp);
    EVP_PKEY_free(pkey);
    X509_REQ_free(req);
    return 0;
}
#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
//包含root和Client证书
#define CERTS_FILE  DATA_DIR"\\client.pem"
//解密用的私钥,密钥文件必须匹配证书。否则解密不被执行
#define KEY_FILE    DATA_DIR"\\client_key.pem"
#define TARGET_CERT_FILE CERTS_FILE
//下面是证书钥对
//#define KEY_FILE DATA_DIR"\\rootkey.pem"
//#define TARGET_CERT_FILE DATA_DIR"\\rootcert.pem"

#define SRC_FILE "pkcs7_enc_dec.c"//欲被加密文件
#define CIPHER_FILE "encrypted_src.txt"//加密后的文件
#define PLAINTEXT_FILE "decypted_src.txt"//解密后的文件

int main(int argc, char *argv[])
{
    STACK_OF(X509) *certs;
    X509 *cert_x509;
    EVP_PKEY *pkey;
    PKCS7 *pkcs7;
    
    FILE *fp;
    BIO *srcfile_bio, *cipherfile_bio, *plainfile_bio, *pkcs7_bio;

    const EVP_CIPHER *cipher;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();

    if( NULL==(srcfile_bio = BIO_new_file(SRC_FILE, "r")) )
        int_error("为源文件创建bio时出错");

    if( NULL==(cipherfile_bio = BIO_new_file(CIPHER_FILE, "w")) )
        int_error("为写密文文件创建bio时出错");

    //PKCS7_encrypt 支持多目标加密,你需要将证书作为第一个certs 参数
    certs = sk_X509_new_null();
    if ( NULL==(fp = fopen(CERTS_FILE, "r")) )
    {
        printf("打开证书文件:%s时出错\n",CERTS_FILE);
        return -1;
    }

    while(1)
    {
        //由于fp在执行PEM_read_x509时被移动,我们可以直接读出所有证书
        if ( NULL==(cert_x509 = PEM_read_X509(fp, NULL, NULL, NULL)))
        {
            break;
        }
        sk_X509_push(certs, cert_x509);
    }
    fclose(fp);

    printf("%d个证书在证书文件%s里\n",certs->num,CERTS_FILE);
    if(certs->num==0)
    {
        printf("没有证书在%s\n",CERTS_FILE);
        return -1;
    }
    
    cipher = EVP_aes_128_cfb();
    if ( NULL == (pkcs7 = PKCS7_encrypt(
        certs,      //使用证书库中的所有证书加密
        srcfile_bio,//要被加密的文件(数据)BIO
        cipher,     //对称加密算法
        0           //加密标志
        )))
    {
        int_error("创建PKCS#7对象时出错");
    }
    //将pkcs7对象写入到目标文件BIO中
    if (SMIME_write_PKCS7(cipherfile_bio, pkcs7,srcfile_bio, 0) != 1)
        int_error("写S/MIME数据时出错");

    BIO_vfree(srcfile_bio);
    BIO_vfree(cipherfile_bio);
    printf("依源文件[%s]创建并保存PKCS#7数据到文件[%s]中成功\n",SRC_FILE,CIPHER_FILE);
    //读入的明文文件
    if( NULL == (cipherfile_bio = BIO_new_file(CIPHER_FILE, "r")) )
        int_error("为读密文文件创建bio时出错\n");

    if( NULL == (plainfile_bio = BIO_new_file(PLAINTEXT_FILE, "w")) )
        int_error("为写解密后的明文文件创建bio时出错\n");

    //从证书文件中读入私钥,系统会要求提供该证书的保护口令。
    //事实上,对应于加密证书库中的任一证书的私钥皆可。
    if ( NULL == (fp = fopen(KEY_FILE, "r")) ||
         NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, "iamclnt")))
    {
        int_error("读在%s中的私钥时出错\n",KEY_FILE);
    }
    fclose(fp);
    //从文件中读入pkcs7对象
    if ( NULL == (pkcs7 = SMIME_read_PKCS7(cipherfile_bio, &pkcs7_bio)))
        int_error("读PKCS#7数据时出错\n");

    //对应于上述欲解密密文的私钥的公钥证书文件
    if ( NULL==(fp = fopen(TARGET_CERT_FILE, "r")) ||
         NULL==(cert_x509 = PEM_read_X509(fp, NULL, NULL, NULL)))
    {
        int_error("为验证解密而读%s证书时出错\n",TARGET_CERT_FILE);
    }
    fclose(fp);
    if ( PKCS7_decrypt(
        pkcs7,        //要解密的pkcs7数据对象
        pkey,         //解密私钥(之一)
        cert_x509,    //对应于私钥的证书库
        plainfile_bio,//解密后的输出的明文
        0) != 1 )
    {
        int_error("解密PKCS#7数据时出错\n");
    }

    BIO_vfree(cipherfile_bio);
    BIO_vfree(plainfile_bio);
    printf("解密PKCS#7数据文件成功,明文在文件[%s]中\n",PLAINTEXT_FILE);
    printf("\n click any key to continue.");getchar();
    return 0;
}

 我们来用VC++实现使用openssl的pkcs7库加密和解密数据,请见代码实现与注释讲解

#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
//包含root和Client证书
#define CERTS_FILE  DATA_DIR"\\client.pem"
//解密用的私钥,密钥文件必须匹配证书。否则解密不被执行
#define KEY_FILE    DATA_DIR"\\client_key.pem"
#define TARGET_CERT_FILE CERTS_FILE
//下面是证书钥对
//#define KEY_FILE DATA_DIR"\\rootkey.pem"
//#define TARGET_CERT_FILE DATA_DIR"\\rootcert.pem"

#define SRC_FILE "pkcs7_enc_dec.c"//欲被加密文件
#define CIPHER_FILE "encrypted_src.txt"//加密后的文件
#define PLAINTEXT_FILE "decypted_src.txt"//解密后的文件

int main(int argc, char *argv[])
{
    STACK_OF(X509) *certs;
    X509 *cert_x509;
    EVP_PKEY *pkey;
    PKCS7 *pkcs7;
    
    FILE *fp;
    BIO *srcfile_bio, *cipherfile_bio, *plainfile_bio, *pkcs7_bio;

    const EVP_CIPHER *cipher;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();

    if( NULL==(srcfile_bio = BIO_new_file(SRC_FILE, "r")) )
        int_error("为源文件创建bio时出错");

    if( NULL==(cipherfile_bio = BIO_new_file(CIPHER_FILE, "w")) )
        int_error("为写密文文件创建bio时出错");

    //PKCS7_encrypt 支持多目标加密,你需要将证书作为第一个certs 参数
    certs = sk_X509_new_null();
    if ( NULL==(fp = fopen(CERTS_FILE, "r")) )
    {
        printf("打开证书文件:%s时出错\n",CERTS_FILE);
        return -1;
    }

    while(1)
    {
        //由于fp在执行PEM_read_x509时被移动,我们可以直接读出所有证书
        if ( NULL==(cert_x509 = PEM_read_X509(fp, NULL, NULL, NULL)))
        {
            break;
        }
        sk_X509_push(certs, cert_x509);
    }
    fclose(fp);

    printf("%d个证书在证书文件%s里\n",certs->num,CERTS_FILE);
    if(certs->num==0)
    {
        printf("没有证书在%s\n",CERTS_FILE);
        return -1;
    }
    
    cipher = EVP_aes_128_cfb();
    if ( NULL == (pkcs7 = PKCS7_encrypt(
        certs,      //使用证书库中的所有证书加密
        srcfile_bio,//要被加密的文件(数据)BIO
        cipher,     //对称加密算法
        0           //加密标志
        )))
    {
        int_error("创建PKCS#7对象时出错");
    }
    //将pkcs7对象写入到目标文件BIO中
    if (SMIME_write_PKCS7(cipherfile_bio, pkcs7,srcfile_bio, 0) != 1)
        int_error("写S/MIME数据时出错");

    BIO_vfree(srcfile_bio);
    BIO_vfree(cipherfile_bio);
    printf("依源文件[%s]创建并保存PKCS#7数据到文件[%s]中成功\n",SRC_FILE,CIPHER_FILE);
    //读入的明文文件
    if( NULL == (cipherfile_bio = BIO_new_file(CIPHER_FILE, "r")) )
        int_error("为读密文文件创建bio时出错\n");

    if( NULL == (plainfile_bio = BIO_new_file(PLAINTEXT_FILE, "w")) )
        int_error("为写解密后的明文文件创建bio时出错\n");

    //从证书文件中读入私钥,系统会要求提供该证书的保护口令。
    //事实上,对应于加密证书库中的任一证书的私钥皆可。
    if ( NULL == (fp = fopen(KEY_FILE, "r")) ||
         NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, "iamclnt")))
    {
        int_error("读在%s中的私钥时出错\n",KEY_FILE);
    }
    fclose(fp);
    //从文件中读入pkcs7对象
    if ( NULL == (pkcs7 = SMIME_read_PKCS7(cipherfile_bio, &pkcs7_bio)))
        int_error("读PKCS#7数据时出错\n");

    //对应于上述欲解密密文的私钥的公钥证书文件
    if ( NULL==(fp = fopen(TARGET_CERT_FILE, "r")) ||
         NULL==(cert_x509 = PEM_read_X509(fp, NULL, NULL, NULL)))
    {
        int_error("为验证解密而读%s证书时出错\n",TARGET_CERT_FILE);
    }
    fclose(fp);
    if ( PKCS7_decrypt(
        pkcs7,        //要解密的pkcs7数据对象
        pkey,         //解密私钥(之一)
        cert_x509,    //对应于私钥的证书库
        plainfile_bio,//解密后的输出的明文
        0) != 1 )
    {
        int_error("解密PKCS#7数据时出错\n");
    }

    BIO_vfree(cipherfile_bio);
    BIO_vfree(plainfile_bio);
    printf("解密PKCS#7数据文件成功,明文在文件[%s]中\n",PLAINTEXT_FILE);
    printf("\n click any key to continue.");getchar();
    return 0;
}

我们来用VC++实现使用使用openssl的pkcs7库签名和验证签名,请见代码实现与注释讲解

#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>

#define CA_FILE   DATA_DIR"\\rootcert.pem"
#define CA_DIR    NULL

#define CLNT_KEY  DATA_DIR"\\client_key.pem"
#define CLNT_CERT DATA_DIR"\\clientcert.pem"

#define SRC_FILE  "pkcs7_sign_verify.c"//被签名文件
#define SIGNED_FILE "pkcs7_sign_verify_signed.txt"//已签名文件

//创建X509 STORE对象
X509_STORE *create_store(void)
{
    X509_STORE *store;
    X509_LOOKUP *lookup;
    //创建一个certs store
    if (!(store = X509_STORE_new()))
    {
        printf("创建X509_STORE_CTX时出错\n");
        return NULL;
    }
    //设置验证回调函数
    X509_STORE_set_verify_cb_func(store, verify_callback);
    //载入CA证书
    if (X509_STORE_load_locations(store, CA_FILE, CA_DIR) != 1)
    {
        printf("载入CA文件或者目录时出错\n");
        return NULL;
    }

    if (X509_STORE_set_default_paths(store) != 1)
    {
        printf("设置缺省X509 store时出错\n");
        return NULL;
    }

    if ( NULL==(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())))
    {
        fprintf(stderr, "创建X509_LOOKUP时出错\n");
        return NULL;
    }

    return store;
}

int main(int argc, char *argv[])
{
    X509 *cert;
    EVP_PKEY *pkey;
    X509_STORE *store;
    PKCS7 *pkcs7;
    
    FILE *fp;
    BIO *srcfile_bio, *signedfile_bio, *pkcs7_bio;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();

    if( NULL==(srcfile_bio = BIO_new_file(SRC_FILE, "r")) )
        int_error("为读源文件创建bio时出错");

    if( NULL==(signedfile_bio = BIO_new_file(SIGNED_FILE, "w")) )
        int_error("为已签名的文件创建bio时出错");

    //读入签名者私钥
    if ( NULL == (fp = fopen(CLNT_KEY, "r")) ||
         NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)))
    {
        printf("读签名者的私钥时出错\n");
        return -1;
    }
    fclose(fp);

    //读签名者的X509证书
    if ( NULL == (fp = fopen(CLNT_CERT, "r")) ||
         NULL == (cert = PEM_read_X509(fp, NULL, NULL, NULL)))
    {
        int_error("读签名者证书时出错\n");
    }
    fclose(fp);
    //执行对指定数据(取自文件BIO)签名
    if ( NULL==(pkcs7 = PKCS7_sign(cert, pkey, NULL, srcfile_bio, 0)))
        int_error("执行PKCS签名时出错");

    //将签名写入到目标文件中
    if (SMIME_write_PKCS7(signedfile_bio, pkcs7, srcfile_bio, 0) != 1)
        int_error("写入PKCS签名后的数据(S/MIME格式数据)时出错");

    BIO_vfree(srcfile_bio);
    BIO_vfree(signedfile_bio);
    //读入被签名的文件
    if( NULL==(signedfile_bio = BIO_new_file(SIGNED_FILE, "r")) )
        int_error("为读入被签名文件创建BIO出错");

    //验证签名数据
    if ( NULL==(store = create_store()) )
        int_error("建立X509_STORE时出错");

    //读入PKCS签名
    if ( NULL==(pkcs7 = SMIME_read_PKCS7(signedfile_bio, &pkcs7_bio)))
        int_error("校验时,读入PKCS#7对象时出错");

    BIO_vfree(signedfile_bio);
    //验证PKCS签名是否正确
    if ( PKCS7_verify(pkcs7, NULL, store, pkcs7_bio, NULL, 0) != 1)
    {
        int_error("PKCS签名校验失败");
    }
    else
    {
        printf("证书和签名校验正确!\n");
    }

    printf("\n click any key to continue.");getchar();
    return 0;
}
#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>

#define CERTS_FILE    DATA_DIR"\\client.pem"
#define CLNT_KEY CERTS_FILE
//使用*.p12作为扩展名会使得Windows操作系统正确认识和处理它
#define PKCS12_FILE "client_keypair.p12"

void print(const char *promptStr,unsigned char *data,int len)
{
    int i;
    printf("\n===%s[输出长度=%d]======\n",promptStr,len);
    for(i = 0; i < len; i++) printf("%02x", data[i]);
    printf("\n=======================\n");
}

int main(int argc, char *argv[])
{
    X509 *cert_clnt,*cert_tmp;
    EVP_PKEY *pkey;
    EVP_PKEY *pkey_frompkcs12;
    STACK_OF(X509) *ca_chain=NULL;
    PKCS12 *pkcs12;    
    FILE *fp;
    char* pass="iwAuto0";
    char* name="Client Private/Publication Key";
    char plainText[]="[For a test to read/write pkcs12 object]"; 
    unsigned char encData[512];
    char decData[512];
    int  len=0;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();

    //读入私钥对象
    if ( NULL == (fp = fopen(CLNT_KEY, "r")) ||
         NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)))
    {
        printf("读客户端私钥出错\n");
        return -1;
    }
    rewind(fp);
    //读入证书链
    ca_chain = sk_X509_new_null();
    while(1)
    {
        //文件指针被移动,所以循环可以读取所有证书
        if ( NULL==(cert_tmp = PEM_read_X509(fp, NULL, NULL, NULL)))
        {
            break;
        }
        sk_X509_push(ca_chain, cert_tmp);
        if( 1==ca_chain->num )
        {
            cert_clnt=cert_tmp;//客户端证书
        }
    }
    fclose(fp);
    printf("%d个证书在证书文件%s中\n",ca_chain->num,CERTS_FILE);
    if(ca_chain->num==0)
    {
        printf("没有证书在%s\n",CERTS_FILE);
        return -1;
    }
    //创建PKCS12对象
    pkcs12=PKCS12_create(
        pass,       //对象保护口令
        name,       //对象名称
        pkey,       //要保护的私钥
        cert_clnt,  //对应私钥的证书对象
        ca_chain,   //用于验证证书的证书链
        0,0,0,0,0   //其它缺省或者未指定参数
        );
    if( NULL==pkcs12)
        int_error("创建PKCS12对象时出错");

    //将对象写入文件
    if ( NULL == (fp = fopen(PKCS12_FILE, "w")) )
    {
        printf("以写方式打开文件%s时出错\n",PKCS12_FILE);
        return -1;
    }
    if ( i2d_PKCS12_fp(fp,pkcs12) != 1)
        int_error("将pkcs12对象写入文件时出错\n");

    fclose(fp);

    if ( NULL == (fp = fopen(PKCS12_FILE, "r")) )
    {
        printf("以读方式打开文件%s时出错\n",PKCS12_FILE);
        return -1;
    }
    if( NULL==(pkcs12=d2i_PKCS12_fp(fp, NULL)) )
        int_error("从文件中读pkcs12对象时出错");

    sk_X509_pop_free(ca_chain,X509_free);
    EVP_PKEY_free(pkey);//释放该公钥对象
    ca_chain=NULL;
    cert_clnt=NULL;
    //从PKCS12对象中解析出私钥,证书链,以及证书。
    //当然由于pkcs12对象受密码保护,所以要输入保护密码
    if( PKCS12_parse(pkcs12,pass, &pkey_frompkcs12,&cert_clnt,&ca_chain)!=1)
    {
        int_error("解析pkcs12对象时出错");
    }    
    printf("读取并解析pkcs12对象成功\n pkcs 文件: \n"
        " %s\n 证书编号:%d\n 证书名:%s\n",
        PKCS12_FILE,ca_chain->num,cert_clnt->name);
    //利用解析出来的私钥去加密和解密
    len=EVP_PKEY_encrypt(encData,(const unsigned char*)plainText,
                         sizeof(plainText),pkey_frompkcs12);
    if(len==-1)
        int_error("EVP_PKEY_encrypt失败");

    print("加密后的数据是:",encData,len);
    //用公钥解密
    len=EVP_PKEY_decrypt((unsigned char*)decData,encData,len,pkey_frompkcs12);
    if(len==-1)
        int_error("EVP_PKEY_decrypt 失败");

    //print("解密后的数据是:",(unsigned char *)decData,len);
    printf("\n 明文是[长度=%d]:%s\n",len,decData);

    printf("\n click any key to continue.");getchar();
    return 0;
}

我们来用VC++实现使用使用导入和导出pkcs12对象,请见代码实现与注释讲解

#include "commonlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>

#define CERTS_FILE    DATA_DIR"\\client.pem"
#define CLNT_KEY CERTS_FILE
//使用*.p12作为扩展名会使得Windows操作系统正确认识和处理它
#define PKCS12_FILE "client_keypair.p12"

void print(const char *promptStr,unsigned char *data,int len)
{
    int i;
    printf("\n===%s[输出长度=%d]======\n",promptStr,len);
    for(i = 0; i < len; i++) printf("%02x", data[i]);
    printf("\n=======================\n");
}

int main(int argc, char *argv[])
{
    X509 *cert_clnt,*cert_tmp;
    EVP_PKEY *pkey;
    EVP_PKEY *pkey_frompkcs12;
    STACK_OF(X509) *ca_chain=NULL;
    PKCS12 *pkcs12;    
    FILE *fp;
    char* pass="iwAuto0";
    char* name="Client Private/Publication Key";
    char plainText[]="[For a test to read/write pkcs12 object]"; 
    unsigned char encData[512];
    char decData[512];
    int  len=0;

    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    seed_prng();

    //读入私钥对象
    if ( NULL == (fp = fopen(CLNT_KEY, "r")) ||
         NULL == (pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)))
    {
        printf("读客户端私钥出错\n");
        return -1;
    }
    rewind(fp);
    //读入证书链
    ca_chain = sk_X509_new_null();
    while(1)
    {
        //文件指针被移动,所以循环可以读取所有证书
        if ( NULL==(cert_tmp = PEM_read_X509(fp, NULL, NULL, NULL)))
        {
            break;
        }
        sk_X509_push(ca_chain, cert_tmp);
        if( 1==ca_chain->num )
        {
            cert_clnt=cert_tmp;//客户端证书
        }
    }
    fclose(fp);
    printf("%d个证书在证书文件%s中\n",ca_chain->num,CERTS_FILE);
    if(ca_chain->num==0)
    {
        printf("没有证书在%s\n",CERTS_FILE);
        return -1;
    }
    //创建PKCS12对象
    pkcs12=PKCS12_create(
        pass,       //对象保护口令
        name,       //对象名称
        pkey,       //要保护的私钥
        cert_clnt,  //对应私钥的证书对象
        ca_chain,   //用于验证证书的证书链
        0,0,0,0,0   //其它缺省或者未指定参数
        );
    if( NULL==pkcs12)
        int_error("创建PKCS12对象时出错");

    //将对象写入文件
    if ( NULL == (fp = fopen(PKCS12_FILE, "w")) )
    {
        printf("以写方式打开文件%s时出错\n",PKCS12_FILE);
        return -1;
    }
    if ( i2d_PKCS12_fp(fp,pkcs12) != 1)
        int_error("将pkcs12对象写入文件时出错\n");

    fclose(fp);

    if ( NULL == (fp = fopen(PKCS12_FILE, "r")) )
    {
        printf("以读方式打开文件%s时出错\n",PKCS12_FILE);
        return -1;
    }
    if( NULL==(pkcs12=d2i_PKCS12_fp(fp, NULL)) )
        int_error("从文件中读pkcs12对象时出错");

    sk_X509_pop_free(ca_chain,X509_free);
    EVP_PKEY_free(pkey);//释放该公钥对象
    ca_chain=NULL;
    cert_clnt=NULL;
    //从PKCS12对象中解析出私钥,证书链,以及证书。
    //当然由于pkcs12对象受密码保护,所以要输入保护密码
    if( PKCS12_parse(pkcs12,pass, &pkey_frompkcs12,&cert_clnt,&ca_chain)!=1)
    {
        int_error("解析pkcs12对象时出错");
    }    
    printf("读取并解析pkcs12对象成功\n pkcs 文件: \n"
        " %s\n 证书编号:%d\n 证书名:%s\n",
        PKCS12_FILE,ca_chain->num,cert_clnt->name);
    //利用解析出来的私钥去加密和解密
    len=EVP_PKEY_encrypt(encData,(const unsigned char*)plainText,
                         sizeof(plainText),pkey_frompkcs12);
    if(len==-1)
        int_error("EVP_PKEY_encrypt失败");

    print("加密后的数据是:",encData,len);
    //用公钥解密
    len=EVP_PKEY_decrypt((unsigned char*)decData,encData,len,pkey_frompkcs12);
    if(len==-1)
        int_error("EVP_PKEY_decrypt 失败");

    //print("解密后的数据是:",(unsigned char *)decData,len);
    printf("\n 明文是[长度=%d]:%s\n",len,decData);

    printf("\n click any key to continue.");getchar();
    return 0;
}


原文地址:https://blog.csdn.net/2401_88752464/article/details/144109921

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!