文章目录
VC++ 使用OpenSSL创建RSA密钥PEM文件
某个桌面应用需要对一些文件进行签名处理,上一篇写了C#相关的一些签名与验签,因原来的历史代码是VC++开发的,也许考虑在上面做类似的工作。
目前是Vibe Coding的时代了,AI这样的助手显然也派上了用场,在AI配合下,我把开发的内容给大家分享一下。
准备工作
1. 安装 OpenSSL
- Windows: 可以从 OpenSSL 的官方网站或第三方网站(如
https://slproweb.com/products/Win32OpenSSL.html
)下载预编译的二进制文件。安装后,记下安装路径。 - Linux/macOS: 通常通过包管理器安装:
- Debian/Ubuntu:
sudo apt-get install libssl-dev
- CentOS/Fedora:
sudo yum install openssl-devel
- macOS (Homebrew):
brew install openssl
- Debian/Ubuntu:
我的这个项目因为是用了vcpkg进行包管理(之前我的博文:VCPKG配合NuGet在项目中使用包也有过简单的描述),直接用vcpkg安装并按照开源项目的方式给MSBUILD使用就行了。参考命令如下:
bash
vcpkg install openssl:x64-windows
vcpkg integrate install
2. Visual Studio 项目配置 (Windows)
如果你在Windows上使用Visual Studio,你需要配置项目的 "附加包含目录" 和 "附加库目录" 以及 "附加依赖项"。
- C/C++ -> 附加包含目录:
C:\OpenSSL-Win64\include
(替换为你的OpenSSL安装路径)
- 链接器 -> 附加库目录:
C:\OpenSSL-Win64\lib
(替换为你的OpenSSL安装路径)
- 链接器 -> 输入 -> 附加依赖项:
libssl.lib
(或ssleay32.lib
)libcrypto.lib
(或libeay32.lib
)ws2_32.lib
(用于网络功能,但在这里可能不需要,最好加上以防万一)gdi32.lib
(可能需要,具体取决于你的OpenSSL版本和配置)
3. CMake 项目配置
如果你使用CMake,它会自动处理这些。
cmake
cmake_minimum_required(VERSION 3.10)
project(OpenSSLRSAExample CXX)
# 查找OpenSSL库
find_package(OpenSSL REQUIRED)
add_executable(generate_rsa_keys main.cpp)
target_link_libraries(generate_rsa_keys PRIVATE OpenSSL::SSL OpenSSL::Crypto)
C++ 源代码 (main.cpp
)
cpp
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
// OpenSSL 头文件
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h> // 用于错误处理
#pragma warning(disable: 4996)
// 辅助函数:显示OpenSSL错误
void printOpenSSLError(const std::string& msg) {
std::cerr << msg << std::endl;
ERR_print_errors_fp(stderr);
}
// 生成RSA密钥对并保存到PEM文件
bool generateRSAKeyPair(const std::string& privateKeyFile, const std::string& publicKeyFile, int bits = 2048) {
RSA* rsa = NULL;
BIGNUM* bne = NULL; // RSA 公钥指数
BIO* bp_public = NULL;
BIO* bp_private = NULL;
int ret = 0; // 返回值
// 写入加密的私钥,需要密码
const char* password = "mypassword"; // 替换为你的密码,或者在实际应用中动态获取
// 1. 生成 RSA 密钥对
// 设置公钥指数,通常是 65537
bne = BN_new();
if (bne == NULL) {
printOpenSSLError("Error creating BIGNUM");
goto err;
}
ret = BN_set_word(bne, RSA_F4); // RSA_F4 is 65537
if (ret != 1) {
printOpenSSLError("Error setting BIGNUM word");
goto err;
}
// 生成RSA密钥
rsa = RSA_new();
if (rsa == NULL) {
printOpenSSLError("Error creating RSA object");
goto err;
}
ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
if (ret != 1) {
printOpenSSLError("Error generating RSA key");
goto err;
}
std::cout << "RSA key pair generated successfully (bits: " << bits << ")." << std::endl;
// 2. 保存私钥到 PEM 文件
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_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL); // 未加密的私钥
if (!PEM_write_bio_RSAPrivateKey(bp_private, rsa, EVP_des_ede3_cbc(), (unsigned char*)password, strlen(password), NULL, NULL)) {
printOpenSSLError("Error writing private key to file");
goto err;
}
std::cout << "Private key saved to " << privateKeyFile << std::endl;
// 3. 保存公钥到 PEM 文件
bp_public = BIO_new_file(publicKeyFile.c_str(), "w+");
if (bp_public == NULL) {
printOpenSSLError("Error creating public key file BIO");
goto err;
}
if (!PEM_write_bio_RSA_PUBKEY(bp_public, rsa)) { // RSA_PUBKEY writes in PKCS#8 format
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 (bne != NULL) BN_free(bne);
if (rsa != NULL) RSA_free(rsa);
return (ret == 1);
}
int main() {
// 1. 初始化 OpenSSL 库
// 可以调用 OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL);
// 或者更简单的,使用 ERR_load_crypto_strings();
ERR_load_crypto_strings(); // 加载错误字符串,以便 ERR_print_errors_fp 可以打印有意义的信息
OpenSSL_add_all_algorithms(); // 加载所有加密算法
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 库
EVP_cleanup(); // 清理所有算法
ERR_free_strings(); // 释放错误字符串
return 0;
}
编译和运行
使用 CMake (推荐):
-
创建
main.cpp
和CMakeLists.txt
在同一个目录。 -
在终端中导航到该目录。
-
创建
build
目录并进入:bashmkdir build cd build
-
运行 CMake 生成项目文件 (例如,Visual Studio 项目文件):
bashcmake .. -G "Visual Studio 17 2022" # 根据你的VS版本调整
或在 Linux/macOS 上:
bashcmake ..
-
构建项目:
bashcmake --build . --config Release # 或 Debug
-
运行可执行文件 (
generate_rsa_keys.exe
在build/Release
或build/Debug
目录下)。
手动编译 (Windows, GCC/MinGW):
bash
g++ main.cpp -o generate_rsa_keys -I C:\OpenSSL-Win64\include -L C:\OpenSSL-Win64\lib -lssl -lcrypto -lws2_32 -lgdi32
(请替换 C:\OpenSSL-Win64
为你的 OpenSSL 安装路径)
手动编译 (Linux/macOS, GCC/Clang):
bash
g++ main.cpp -o generate_rsa_keys -lssl -lcrypto
预期输出
成功运行后,你会在程序所在的目录看到两个新文件:
private_key.pem
(包含加密的RSA私钥)public_key.pem
(包含RSA公钥)
控制台输出会是这样:
RSA key pair generated successfully (bits: 2048).
Private key saved to private_key.pem
Public key saved to public_key.pem
RSA key pair generation and saving completed successfully.
PEM 文件内容示例
private_key.pem
(部分内容,由于加密,格式会有点不同):
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,BA0A9F1C02EB1D53
MIIGsAYJKoZIhvcNAQEE... (大量base64编码的数据) ...
-----END RSA PRIVATE KEY-----
public_key.pem
(部分内容):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... (大量base64编码的数据) ...
-----END PUBLIC KEY-----
密钥长度 (bits
参数)
在 generateRSAKeyPair
函数中,bits
参数控制生成RSA密钥的长度。
1024
: 曾经常用,但现在被认为安全性不足。2048
: 当前推荐的标准,提供良好的安全性。3072
或4096
: 提供更高的安全性,但生成和使用速度较慢。
加密的私钥
示例中私钥是加密 保存的,密码是 "mypassword"。这意味着当你以后需要加载这个私钥时,需要提供相同的密码。如果你想保存未加密的私钥,可以注释掉 PEM_write_bio_RSAPrivateKey
调用中 EVP_des_ede3_cbc()
和密码相关的参数,只留下 NULL
。但请注意,未加密的私钥非常不安全,应避免在生产环境中使用。
cpp
// 保存未加密的私钥 (不推荐用于生产环境)
// if (!PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL)) {
// printOpenSSLError("Error writing unencrypted private key to file");
// goto err;
// }
存在的坑
由于我安装的OpenSSL版本是3.0以上,本文出现的主体代码有一个特点,就是 禁止了4996警告(#pragma warning(disable: 4996)
),虽然仅用了这个警告,但毕竟只是临时的一个解决办法,事实上OpenSSL 3.0以上版本代码接口发生了变化,是需要进行修改调整的。
再者,EVP_des_ede3_cbc()
是一个旧的加密算法,OpenSSL 3.0 推荐使用更强的算法,例如 EVP_aes_256_cbc()
,这些我将在下一篇博客里填坑。