填坑: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 格式存储私钥:

#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文件里:

运行截图
在这里插入图片描述

posted on 2025-09-30 12:31  ljbguanli  阅读(42)  评论(0)    收藏  举报