使用OpenSSL接口读取pem编码格式文件中的证书

一. 概述

pem格式的全称是Private Enhance Mail(加强邮件文本格式),是一种常见的文本文件格式,用于存储和传输加密的数据,最初为了安全电子邮件而设计,后来被广泛应用在数字证书,SSL/TLS和秘钥管理。

pem文件中,每一段文本前后都有清晰的边界标记,如下

-----BEGIN XXX-----

HKK091IJ23KJJQkjc2k

k0.......

-----END XXX-----

解析pem文件的代码流程如下:

a. 读取pem文件中的标签获取不同的对象(证书,秘钥..)的BASE64编码的字符串

b. 使用BIO_new_mem_buf包装读取到的字符串作为一个BIO

c. 根据不同的对象(证书,秘钥)调用不同的SSL函数解析BIO,例如PEM_read_bio_X509,PEM_read_bio_PrivateKey

二. 代码

cpp 复制代码
#include <iostream>
#include <fstream>
#include <vector>

#include <unistd.h>
#include <string.h>

#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/err.h>

const char* CERTIFICATE_BEGIN = "-----BEGIN CERTIFICATE-----";
const char* CERTIFICATE_END = "-----END CERTIFICATE-----";

const int CERTFILE_CONTENT_LINE_MAXLEN = 14000;   // 14000 byte limit
char cert_content_tmp_buf[CERTFILE_CONTENT_LINE_MAXLEN] = {0};  // store every certificate content in pem file

void print_serial_number(X509 *cert) {
    ASN1_INTEGER *serial = X509_get_serialNumber(cert);
    if (!serial) {
        std::cout << "无法获取序列号" << std::endl;
        return;
    }

    // 将序列号转换为 BIGNUM(方便打印)
    BIGNUM *bn = ASN1_INTEGER_to_BN(serial, NULL);
    if (!bn) {
        std::cout << "序列号转换失败" << std::endl;
        return;
    }

    // 打印序列号(十进制)
    std::cout << "Serial Number (decimal): ";
    BN_print_fp(stdout, bn);
    std::cout << std::endl;

    // 打印序列号(十六进制)
    char *hex = BN_bn2hex(bn);
    std::cout << "Serial Number (hex): " << hex << std::endl;
    OPENSSL_free(hex);

    BN_free(bn);
}

void extract_public_key(X509 *cert) {
    EVP_PKEY *pkey = X509_get_pubkey(cert);
    if (!pkey) {
        std::cout << "无法提取公钥" << std::endl;
        return;
    }

    // 打印公钥类型(如 RSA、ECC)
    int type = EVP_PKEY_id(pkey);
    std::cout << "Public Key Type: " << OBJ_nid2sn(type) << std::endl;

    // 如果是 RSA 公钥,可进一步提取模数(n)和指数(e)
    if (type == EVP_PKEY_RSA) {
        RSA *rsa = EVP_PKEY_get1_RSA(pkey);
        const BIGNUM *n, *e;
        RSA_get0_key(rsa, &n, &e, NULL);

        std::cout << "RSA Modulus (n): ";
        BN_print_fp(stdout, n);
        std::cout << "\nRSA Exponent (e): ";
        BN_print_fp(stdout, e);
        std::cout << std::endl;

        RSA_free(rsa);
    }

    EVP_PKEY_free(pkey);
}

void print_validity(X509 *cert) {
    ASN1_TIME *not_before = X509_get_notBefore(cert);
    ASN1_TIME *not_after = X509_get_notAfter(cert);

    char* s_notBefore = (char*)(ASN1_STRING_data(not_before));
    char* s_notAfter = (char*)(ASN1_STRING_data(not_after));
    std::cout << "Valid From: " << s_notBefore;
    std::cout << "\nValid Until: " << s_notAfter;
    std::cout << std::endl;
}

void print_subject(X509 *cert) {
    X509_NAME *subject = X509_get_subject_name(cert);
    if (!subject) {
        std::cout << "Error get subject from cert" << std::endl;
    } else {
        X509_NAME_print_ex_fp(stdout, subject, 0, XN_FLAG_ONELINE);
        std::cout << std::endl;
    }
}

bool read_cert_from_file(const std::string& cert_file, std::vector<std::string>& cert_content_buf) {
    bool success = true;
    // file exist
    if (access(cert_file.c_str(), F_OK)) {
        std::cout << "cert file [" << cert_file << "] not exists" << std::endl;
        success = false;
    } else {
        // loop read content between cert begin-end tag
        std::ifstream fs(cert_file);
        std::string line;
        bool begin_find = false;
        int line_index = 1;  // indicate current read line number
        int cert_index = 0;  // indicate which certificate in this pem file
        int read_size = 0;
        while (std::getline(fs, line)) {
            if (!begin_find) {
                if (0 == line.compare(CERTIFICATE_BEGIN)) {
                    if (line.length() > CERTFILE_CONTENT_LINE_MAXLEN) {
                        std::cout << "data line " << line_index << " exceed max line length [" << CERTFILE_CONTENT_LINE_MAXLEN << "]" << std::endl;
                        success = false;
                        break;
                    }
                    memset(cert_content_tmp_buf, 0, CERTFILE_CONTENT_LINE_MAXLEN);
                    memcpy(cert_content_tmp_buf + read_size, line.c_str(), line.length());  // append current line to cert file buffer
                    read_size = 0;   // reset read size
                    read_size += line.length();
                    cert_content_tmp_buf[read_size] = '\n';
                    read_size++;
                    begin_find = true;
                }
            } else {
                memcpy(cert_content_tmp_buf + read_size, line.c_str(), line.length());  // append current line to cert file buffer
                read_size += line.length();
                if (0 == line.compare(CERTIFICATE_BEGIN)) {
                    std::cout << "data line " << line_index << " has duplicated [" << CERTIFICATE_BEGIN << "]" << std::endl;
                    success = false;
                    break;
                } else
                {
                    if (0 == line.compare(CERTIFICATE_END)) {
                        // store current certificate
                        std::cout << "read " << (cert_index + 1) << " complete certificate content, size is " << read_size << std::endl;
                        cert_content_buf.emplace_back(std::string(cert_content_tmp_buf, read_size));
                        // increase cert index
                        cert_index++;
                        // reset begin flag
                        begin_find = false;
                    } else {
                        cert_content_tmp_buf[read_size] = '\n';
                        read_size++;
                    }
                }
            }
        }
        fs.close();
        if (success) {
            std::cout << "total " << cert_index << " certificate in " << cert_file << std::endl;
        }
    }
    return success;
}

void parse_certificate(const std::string& certificate) {
    X509* x509;  // store parsed x509 cert content
    BIO* bio = BIO_new_mem_buf(certificate.c_str(), certificate.length());
    std::cout << "allocate BIO fro certificate buffer, size is " << certificate.length() << ",\n content is:\n " << certificate << std::endl;
    if (bio) {
        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (x509) {
            // cert serial number 
            print_serial_number(x509);
            // before and after date X509_get_notBefore & X509_get_notAfter
            print_validity(x509);
            // Subject X509_get_subject_name
            print_subject(x509);
            // Issuer X509_get_issuer_name
            
            // Public Key  X509_get_pubkey
            extract_public_key(x509);
            // free x509
            X509_free(x509);
        } else {
            std::cout << "Read X509 certificate content from  BIO fail" << std::endl;
            ERR_print_errors_fp(stderr);
        }
    } else {
        std::cout << "allocate BIO fro certificate buffer fail" << std::endl;
    }
}

int main(int argc, char** argv) {
    if (argc > 1) {
        std::string filePath = argv[1];
        std::vector<std::string> vec_cert;
        if (read_cert_from_file(std::string(filePath), vec_cert)) {
            for(auto cert : vec_cert) {
                std::cout << "##### parse certificate begin ######\n" << std::endl;
                parse_certificate(cert);
                std::cout << "\n##### parse certificate end ######" << std::endl;
            }
        }
    } else {
        std::cout << "usage: ./cert_read $PEM_FILE_NAME" << std::endl;
    }
}

三. 编译

CMakeLists.txt如下

cmake_minimum_required(VERSION 3.10)

project(certRead)

add_executable(${PROJECT_NAME} main.cpp)

target_link_libraries(${PROJECT_NAME} ssl crypto)

然后cmake + make编译

四. 运行结果

其中使用的证书是在下面这个在线网站上生成的:

SSL证书生成

相关推荐
傻啦嘿哟43 分钟前
Python 办公实战:用 python-docx 自动生成 Word 文档
开发语言·c#
翻滚吧键盘1 小时前
js代码09
开发语言·javascript·ecmascript
q567315231 小时前
R语言初学者爬虫简单模板
开发语言·爬虫·r语言·iphone
??tobenewyorker1 小时前
力扣打卡第二十一天 中后遍历+中前遍历 构造二叉树
数据结构·c++·算法·leetcode
rzl022 小时前
java web5(黑马)
java·开发语言·前端
时序数据说2 小时前
为什么时序数据库IoTDB选择Java作为开发语言
java·大数据·开发语言·数据库·物联网·时序数据库·iotdb
jingling5552 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架
oioihoii2 小时前
C++11 forward_list 从基础到精通:原理、实践与性能优化
c++·性能优化·list
m0_687399842 小时前
写一个Ununtu C++ 程序,调用ffmpeg API, 来判断一个数字电影的视频文件mxf 是不是Jpeg2000?
开发语言·c++·ffmpeg
爱上语文2 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端