填坑:VC++ 采用OpenSSL 3.0接口方式生成RSA密钥

文章目录

填坑:VC++ 采用OpenSSL 3.0接口方式生成RSA密钥

上一篇博客VC++ 使用OpenSSL创建RSA密钥PEM文件埋了点雷,还是要填掉的,借助现在强大的AI工具,也帮了不少忙,于是把修改的内容记录下来。

一些变化

首先,OpenSSL 3.0引入了一些重要的API变更,主要是为了更好的模块化和安全性,比如:

  1. Provider (提供者) 机制:这是最大的变化之一。算法实现现在通过"提供者"加载,而不是全局加载。
  2. RSA_generate_key_ex 的替代 :在某些情况下,EVP_PKEY_keygen_initEVP_PKEY_keygen 及其相关函数是更现代和推荐的密钥生成方式。虽然 RSA_generate_key_ex 仍然可用,但使用 EVP 接口更符合 OpenSSL 3.0 的设计哲学。
  3. PEM 文件写入函数PEM_write_bio_RSAPrivateKeyPEM_write_bio_RSA_PUBKEY 仍然可用,但在 EVP 框架下,更推荐使用 PEM_write_bio_PKCS8PrivateKeyPEM_write_bio_PUBKEYPKCS8 是更现代的私钥存储格式,可以包含更多元数据并支持多种加密算法。
  4. 初始化函数OpenSSL_add_all_algorithms()ERR_load_crypto_strings() 等函数在 OpenSSL 3.0 中仍然存在,但更推荐使用 OPENSSL_init_crypto() 来进行更精细的控制。
  5. 错误处理ERR_print_errors_fp 依然有效。

为适配 OpenSSL 3.0,我主要使用了 EVP 接口进行密钥生成和 PKCS8 格式存储私钥:

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

// OpenSSL 头文件
#include <openssl/rsa.h> // 仍然需要,因为 RSA 结构可能在内部被使用
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/evp.h> // EVP 接口

// 辅助函数:显示OpenSSL错误
void printOpenSSLError(const std::string& msg) {
    std::cerr << msg << std::endl;
    ERR_print_errors_fp(stderr);
}

// 生成RSA密钥对并保存到PEM文件 (适配 OpenSSL 3.0)
bool generateRSAKeyPair(const std::string& privateKeyFile, const std::string& publicKeyFile, int bits = 2048) {
    EVP_PKEY_CTX* pctx = NULL; // EVP_PKEY context for key generation
    EVP_PKEY* pkey = NULL;     // EVP_PKEY for the generated key pair
    BIO* bp_public = NULL;
    BIO* bp_private = NULL;
    int ret = 0; // 返回值

    // 写入加密的私钥,需要密码
    const char* password = "mypassword"; // 替换为你的密码,或者在实际应用中动态获取

    // 1. 创建 EVP_PKEY_CTX 用于 RSA 密钥生成
    // 使用 EVP_PKEY_RSA 来指定生成 RSA 密钥
    pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); // 或者 EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
    if (!pctx) {
        printOpenSSLError("Error creating EVP_PKEY_CTX");
        goto err;
    }

    if (EVP_PKEY_keygen_init(pctx) <= 0) {
        printOpenSSLError("Error initializing EVP_PKEY_keygen");
        goto err;
    }

    // 设置密钥位数
    if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, bits) <= 0) {
        printOpenSSLError("Error setting RSA keygen bits");
        goto err;
    }

    // 可以选择设置公钥指数,例如 RSA_F4 (65537)
    // BIGNUM* e = BN_new();
    // BN_set_word(e, RSA_F4);
    // EVP_PKEY_CTX_set_rsa_keygen_pubexp(pctx, e);
    // BN_free(e); // 如果设置了,记得释放

    // 2. 生成 RSA 密钥对
    if (EVP_PKEY_keygen(pctx, &pkey) <= 0) {
        printOpenSSLError("Error generating RSA key pair");
        goto err;
    }
    std::cout << "RSA key pair generated successfully (bits: " << bits << ")." << std::endl;

    // 3. 保存私钥到 PEM 文件 (PKCS#8 格式,带加密)
    bp_private = BIO_new_file(privateKeyFile.c_str(), "w+");
    if (bp_private == NULL) {
        printOpenSSLError("Error creating private key file BIO");
        goto err;
    }
    // 使用 PEM_write_bio_PKCS8PrivateKey,它更通用且推荐
    // EVP_des_ede3_cbc() 是一个旧的加密算法,OpenSSL 3.0 推荐使用更强的算法,例如 EVP_aes_256_cbc()
    if (!PEM_write_bio_PKCS8PrivateKey(bp_private, pkey, EVP_aes_256_cbc(), (char*)password, strlen(password), NULL, NULL)) {
        printOpenSSLError("Error writing PKCS#8 private key to file");
        goto err;
    }
    std::cout << "Private key saved to " << privateKeyFile << " (PKCS#8 encrypted with AES-256-CBC)." << std::endl;

    // 4. 保存公钥到 PEM 文件
    bp_public = BIO_new_file(publicKeyFile.c_str(), "w+");
    if (bp_public == NULL) {
        printOpenSSLError("Error creating public key file BIO");
        goto err;
    }
    // 使用 PEM_write_bio_PUBKEY
    if (!PEM_write_bio_PUBKEY(bp_public, pkey)) {
        printOpenSSLError("Error writing public key to file");
        goto err;
    }
    std::cout << "Public key saved to " << publicKeyFile << std::endl;

    ret = 1; // 成功
err:
    // 清理资源
    if (bp_private != NULL) BIO_free_all(bp_private);
    if (bp_public != NULL) BIO_free_all(bp_public);
    if (pctx != NULL) EVP_PKEY_CTX_free(pctx);
    if (pkey != NULL) EVP_PKEY_free(pkey);

    return (ret == 1);
}

int main() {
    // 1. 初始化 OpenSSL 库
    OpenSSL_add_all_algorithms();

    // 加载错误字符串,以便 ERR_print_errors_fp 可以打印有意义的信息
    ERR_load_crypto_strings();

    std::string privateKeyPath = "private_key.pem";
    std::string publicKeyPath = "public_key.pem";

    if (generateRSAKeyPair(privateKeyPath, publicKeyPath)) {
        std::cout << "\nRSA key pair generation and saving completed successfully." << std::endl;
    }
    else {
        std::cerr << "\nFailed to generate RSA key pair." << std::endl;
    }

    // 2. 清理 OpenSSL 库
    // 释放错误字符串
    ERR_free_strings();
	 EVP_cleanup(); // 清理所有算法

    return 0;
}

关键修改点和解释:

  1. EVP_PKEY_CTXEVP_PKEY

    • 在 OpenSSL 3.0 中,EVP 接口是推荐的通用加密操作接口。
    • EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL) 用于创建一个用于 RSA 操作的上下文。"RSA" 是算法名称。
    • EVP_PKEY_keygen_init(pctx) 初始化密钥生成过程。
    • EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, bits) 设置密钥的位数,与 RSA_generate_key_ex 中的 bits 参数功能相同。
    • EVP_PKEY_keygen(pctx, &pkey) 执行密钥生成,并将结果存储在 EVP_PKEY* pkey 中。这个 pkey 对象包含了公钥和私钥信息。
  2. 私钥保存 (PKCS#8)

    • PEM_write_bio_PKCS8PrivateKey 用于将私钥以 PKCS#8 格式写入 PEM 文件。PKCS#8 是一个更现代、更灵活的私钥存储标准,它本身可以包含加密信息。
    • 我将加密算法从 EVP_des_ede3_cbc() 改为 EVP_aes_256_cbc(),因为 DES-EDE3-CBC (即 Triple DES) 在现代标准下已不推荐,AES-256-CBC 更安全。
    • PKCS#8 格式的私钥通常以 -----BEGIN ENCRYPTED PRIVATE KEY----- 开头,而不是 -----BEGIN RSA PRIVATE KEY-----
  3. 公钥保存

    • PEM_write_bio_PUBKEY 用于将公钥写入 PEM 文件。它会写入标准的 -----BEGIN PUBLIC KEY----- 格式,该格式是基于 EVP_PKEY 结构的。
  4. 错误处理

    • ERR_print_errors_fp 仍然是打印 OpenSSL 错误的标准方式。

编译和运行:

  1. 头文件和库文件 :确保你的编译器能够找到 OpenSSL 3.0 的头文件 (-I 参数) 和库文件 (-L 参数,以及 -l 参数链接 libcryptolibssl)。
  2. Windows 用户 :对于在 Windows 上使用 MSVC 编译,你可能需要包含 openssl/applink.c 或者在链接时处理一些特定的库。我的代码中已经添加了 #include <openssl/applink.c>,这通常能解决控制台应用程序的一些问题。
  3. 链接库
    • Linux/macOS: g++ your_code.cpp -o your_app -I/path/to/openssl/include -L/path/to/openssl/lib -lcrypto
    • Windows (MinGW/MSYS2): g++ your_code.cpp -o your_app -I<openssl_dir>/include -L<openssl_dir>/lib -lssl -lcrypto -Wl,--start-group -lws2_32 -lgdi32 -lcrypt32 -Wl,--end-group (具体依赖可能因你的 OpenSSL 安装方式而异)
    • Windows (MSVC): 需要在项目属性中配置包含目录、库目录,并链接 libcrypto.liblibssl.lib (或者 crypt32.lib, ws2_32.lib 等)。

这个修改后的版本遵循 OpenSSL 3.0 的现代实践,提供了更好的模块化和对新算法的支持。

小结

至此,我们填掉了之前warning: 4996的坑,从 EVP_des_ede3_cbc() 改为 EVP_aes_256_cbc()也体现到了私钥PEM文件里:


相关推荐
Humbunklung2 小时前
VC++ 使用OpenSSL创建RSA密钥PEM文件
开发语言·c++·openssl
Larry_Yanan2 小时前
QML学习笔记(十五)QML的信号处理器(MouseArea)
c++·笔记·qt·学习·ui
zl21878654483 小时前
Playwright同步、异步、并行、串行执行效率比较
开发语言·python·测试工具
努力学习的小廉4 小时前
我爱学算法之—— 模拟(下)
c++·算法
Larry_Yanan4 小时前
QML学习笔记(十七)QML的属性变更信号
javascript·c++·笔记·qt·学习·ui
Tony Bai4 小时前
【Go开发者的数据库设计之道】05 落地篇:Go 语言四种数据访问方案深度对比
开发语言·数据库·后端·golang
gopyer4 小时前
180课时吃透Go语言游戏后端开发3:Go语言中其他常用的数据类型
开发语言·游戏·golang·游戏后端开发
come112344 小时前
Go vs. PHP:核心优势劣势对比
开发语言·golang·php
eqwaak04 小时前
Flask实战指南:从基础到高阶的完整开发流程
开发语言·后端·python·学习·flask