简介:这份资源面向需要在 Windows 平台使用 C++ 完成 RSA 加密解密的开发者,尤其适合被网上 Linux 版示例、老旧代码和残缺工程困扰、希望直接跑通调试的人群。压缩包共 12 个文件,约 450KB,包含 cpp 主程序、h 头文件、cbp 工程文件、exe 可执行文件以及 o 目标文件等,采用 Code::Blocks 组织,结构完整,下载后无需额外改造即可编译运行。内容围绕 OpenSSL 的 RSA 接口展开,涉及密钥生成、公钥加密、私钥解密以及 Base64 编码等常见环节,并配有 BigInteger 等辅助模块,便于理解大数运算与加解密流程的衔接。目前已有 2929 人学习下载,说明该示例在同类 Windows 版资料中具有一定参考价值。对于正在做课程设计、安全实验或需要快速验证 RSA 流程的读者,可直接以该工程为起点,对照源码梳理调用顺序与参数含义,减少环境配置和版本兼容上的试错成本。
1. 从一次数据上报翻车说起:为什么我最终选了 OpenSSL 做 RSA
前阵子帮一个做设备管理后台的朋友排查问题,客户端上报的配置数据在传输过程中被中间层篡改,日志里看到的密文和原始明文对不上,查了两天才定位到是自研的简易加密逻辑在 Windows 下因为字符编码差异算错了摘要。这件事之后我下定决心,凡是涉及密钥交换、数据签名这类场景,一律用成熟库,不再自己造轮子。这份资源就是围绕openssl rsa c++ 加密解密展开的 Windows 版实现,把 OpenSSL 这头“巨兽”在 MSVC 环境下的编译、链接、密钥生成、加解密调用整条链路都跑通了。它适合两类人:一类是刚接触非对称加密、想在 Windows 上把 RSA 跑起来的新手;另一类是手里有现成 C++ 工程,需要把 RSA 加解密模块嵌进去的熟手。下面我按自己拆项目的顺序,把这份资源从头到尾讲一遍。
2. 环境搭建与工程结构:Windows 下把 OpenSSL 接进 C++ 工程
2.1 技术选型与依赖确认
这份资源的核心依赖只有两个:OpenSSL 库和一套 C++ 编译环境。OpenSSL 负责提供 RSA 的密钥生成、加解密、填充模式等底层能力,C++ 侧只做调用封装。Windows 上常见的做法是用 MSVC(Visual Studio 自带的编译器)配合 OpenSSL 的预编译库,或者用 vcpkg 一键拉取。资源里走的是手动配置路径,因为很多老项目没法随意引入包管理器,手动配一次反而更稳。
选 OpenSSL 而不是 Windows 自带的 CryptoAPI,理由很直接:跨平台一致性好,同一套代码在 Linux 上几乎不用改;文档和社区案例多,遇到填充模式、密钥格式的问题容易搜到答案;而且 OpenSSL 的 EVP 接口把算法细节抽象得比较干净,后续换算法成本低。常见做法是把 OpenSSL 装到一个固定目录,比如C:\OpenSSL-Win64,然后在工程里显式指定头文件和库路径,避免依赖系统环境变量。
2.2 目录结构与文件职责
这份资源的工程结构不复杂,但每个文件职责清晰,我按自己的理解拆一下:
rsa_demo/ ├── include/ │ └── openssl/ # OpenSSL 头文件目录 ├── lib/ │ ├── libcrypto.lib # 加密库导入库 │ └── libssl.lib # SSL 库导入库 ├── src/ │ ├── main.cpp # 程序入口,演示加解密流程 │ ├── rsa_util.h # RSA 工具类声明 │ └── rsa_util.cpp # RSA 加解密、密钥读写实现 ├── keys/ │ ├── public.pem # 公钥文件 │ └── private.pem # 私钥文件 └── rsa_demo.vcxproj # Visual Studio 工程文件rsa_util是核心,把密钥生成、PEM 读写、公钥加密、私钥解密四件事封装成独立函数。main.cpp只负责串流程,方便替换成自己的业务逻辑。keys目录存放运行时生成的密钥对,实际项目里通常会换成配置中心或密钥管理服务下发。
2.3 编译链接配置:三个必须改对的地方
在 Visual Studio 里配置 OpenSSL,我踩过的坑集中在三处。第一处是头文件路径,要在「项目属性 → C/C++ → 常规 → 附加包含目录」里加上$(ProjectDir)include。第二处是库目录,在「链接器 → 常规 → 附加库目录」里加上$(ProjectDir)lib。第三处是依赖项,在「链接器 → 输入 → 附加依赖项」里填libcrypto.lib和libssl.lib。
如果编译时报LNK2019 无法解析的外部符号,九成是库没链上或者位数不匹配。OpenSSL 库分 32 位和 64 位,工程平台也要对应选 x64 或 Win32,混用必挂。另外运行时如果报找不到libcrypto-3-x64.dll,把 OpenSSL 的bin目录加到系统 PATH,或者把 DLL 拷到 exe 同目录。这一步没有捷径,配一次记下来,以后新工程照抄。
提示:OpenSSL 3.x 和 1.1.1 的 API 有差异,资源里用的是 3.x 的 EVP 接口。如果你手里是 1.1.1,部分函数名和返回值判断要微调,别直接照搬。
3. 核心模块拆解:密钥生成、PEM 读写与加解密调用链
3.1 密钥对生成:一行命令还是代码里生成
生成 RSA 密钥对有两种方式。一种是用 OpenSSL 命令行,适合一次性生成后固定使用:
# 生成 2048 位私钥,PEM 格式 openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048 # 从私钥导出公钥 openssl rsa -pubout -in private.pem -out public.pem另一种是在 C++ 代码里动态生成,适合需要为每个用户或每次会话分配独立密钥的场景。资源里两种都覆盖了,代码生成的核心逻辑如下:
// 生成 RSA 密钥对,返回 EVP_PKEY 指针 EVP_PKEY* generateRSAKey(int bits) { EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); if (!ctx) return nullptr; // 初始化密钥生成上下文 if (EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); return nullptr; } // 设置密钥长度,常用 2048 或 4096 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) { EVP_PKEY_CTX_free(ctx); return nullptr; } EVP_PKEY* pkey = nullptr; // 执行生成,pkey 需要调用方负责释放 if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); return nullptr; } EVP_PKEY_CTX_free(ctx); return pkey; }这里bits参数控制密钥长度,2048 是当前安全基线,4096 更稳但加解密耗时明显上升。EVP_PKEY_CTX是 OpenSSL 3.x 推荐的上下文对象,所有密钥操作都围绕它展开。生成失败时返回nullptr,调用方必须判空,否则后面解引用直接崩。注意EVP_PKEY_keygen成功后pkey的内存要由调用方用EVP_PKEY_free释放,这是 OpenSSL 一贯的“谁申请谁释放”风格。
3.2 PEM 文件读写:把密钥落盘和读回内存
密钥生成后要持久化,PEM 是最通用的文本格式。写文件的逻辑:
// 将私钥写入 PEM 文件 bool writePrivateKey(EVP_PKEY* pkey, const std::string& path) { FILE* fp = fopen(path.c_str(), "wb"); if (!fp) return false; // 使用 PEM_write_PrivateKey 写出私钥 int ret = PEM_write_PrivateKey(fp, pkey, nullptr, nullptr, 0, nullptr, nullptr); fclose(fp); return ret == 1; } // 将公钥写入 PEM 文件 bool writePublicKey(EVP_PKEY* pkey, const std::string& path) { FILE* fp = fopen(path.c_str(), "wb"); if (!fp) return false; int ret = PEM_write_PUBKEY(fp, pkey); fclose(fp); return ret == 1; }读回时用PEM_read_PrivateKey和PEM_read_PUBKEY,传入文件指针和回调参数。这里有个细节:Windows 下fopen的路径如果含中文,要用宽字符版本或者确保工程字符集设置正确,否则会打开失败但返回的错误码很模糊,容易误判成权限问题。我一般会在读文件前先判断文件是否存在,避免 OpenSSL 抛出一堆底层错误。
3.3 公钥加密与私钥解密:EVP 接口的完整调用
加解密是这份资源最核心的部分。RSA 加密有长度限制,2048 位密钥最多加密 245 字节(PKCS#1 v1.5 填充),超了必须分段或改用混合加密。资源里演示的是单块加解密,逻辑如下:
// 公钥加密,返回密文 std::vector<unsigned char> rsaEncrypt(EVP_PKEY* pkey, const std::string& plain) { EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr); std::vector<unsigned char> cipher; size_t outLen = 0; if (EVP_PKEY_encrypt_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); return cipher; } // 设置 PKCS#1 v1.5 填充,也可换成 OAEP EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING); // 第一次调用传 nullptr 获取密文长度 if (EVP_PKEY_encrypt(ctx, nullptr, &outLen, (const unsigned char*)plain.data(), plain.size()) <= 0) { EVP_PKEY_CTX_free(ctx); return cipher; } cipher.resize(outLen); // 第二次调用真正加密 if (EVP_PKEY_encrypt(ctx, cipher.data(), &outLen, (const unsigned char*)plain.data(), plain.size()) <= 0) { cipher.clear(); } EVP_PKEY_CTX_free(ctx); return cipher; }解密是逆过程,用EVP_PKEY_decrypt_init和EVP_PKEY_decrypt,填充模式必须和加密端一致,否则解密出来是乱码或者直接报错。这里两次调用EVP_PKEY_encrypt是 OpenSSL 的固定套路:第一次拿长度,第二次拿数据。很多人第一次写会只调一次然后传个固定大小的 buffer,结果要么截断要么溢出。outLen在第二次调用后会被更新为实际密文长度,别再用第一次的值。
注意:RSA 加密只适合加密小数据,比如对称密钥、摘要、短配置。业务数据量大时,标准做法是 RSA 加密一个 AES 密钥,再用 AES 加密数据,也就是混合加密体系。
4. 避坑与排查:Windows 下 RSA 加解密最常见的五个翻车点
4.1 现象:编译通过但运行时报「no OPENSSL_Applink」
原因:OpenSSL 在 Windows 下用 FILE* 做 IO 时,需要链接applink.c来桥接 C 运行时。解决:在工程里把 OpenSSL 源码中的applink.c加入编译,或者直接#include <openssl/applink.c>。这个坑在 Linux 上不存在,所以从 Linux 迁过来的代码经常中招。
4.2 现象:加密正常,解密返回空或报错
原因:填充模式不匹配,或者加密用的是公钥、解密也用了公钥。RSA 的规则是公钥加密、私钥解密,反过来是私钥签名、公钥验签。解决:检查EVP_PKEY_CTX_set_rsa_padding两端是否一致,确认解密时传入的是私钥EVP_PKEY。
4.3 现象:中文明文加密后解密变成乱码
原因:std::string在 Windows 下默认按本地编码存储,而 OpenSSL 按字节处理。如果源文件是 GBK 而读取时按 UTF-8 解释,字节序列就错了。解决:统一用 UTF-8 编码源文件,或者在加密前把字符串转成std::vector<unsigned char>明确按字节处理,不要依赖std::string的隐式转换。
4.4 现象:链接时报「模块计算机类型 x64 与目标计算机类型 x86 冲突」
原因:OpenSSL 库的位数和工程平台不一致。解决:确认下载的 OpenSSL 是 Win64 还是 Win32,工程配置管理器里平台选对应的。如果用的是 vcpkg,安装时指定--triplet x64-windows。
4.5 现象:内存缓慢增长,长时间运行后崩溃
原因:EVP_PKEY、EVP_PKEY_CTX、BIO这些对象没释放。OpenSSL 不会自动回收。解决:每个new或_new出来的对象都要有对应的_free,建议用 RAII 封装成智能指针,比如std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>,避免手动管理漏掉。
5. 二次开发改造:把 Demo 变成能进项目的加密模块
这份资源跑通之后,直接拿去用还差几步。我一般会做三件事:封装成类、加异常处理、补混合加密。
第一步,把散落的函数收进一个RsaCrypto类,构造时加载密钥,析构时自动释放,对外只暴露encrypt和decrypt两个方法。这样业务代码不用关心 OpenSSL 的上下文生命周期。第二步,把返回值从裸指针改成std::optional或抛异常,避免调用方忘记判空。第三步,如果业务数据超过 245 字节,在类里内置 AES 密钥生成和加密,RSA 只用来保护 AES 密钥。
改造后的调用大概长这样:
RsaCrypto crypto("keys/public.pem", "keys/private.pem"); std::string plain = "device_config=xxx"; auto cipher = crypto.encrypt(plain); // 内部处理长度限制 auto recovered = crypto.decrypt(cipher); // 返回 std::optional<std::string> if (!recovered) { // 记录日志,排查密钥或填充问题 }验证改造是否成功,我习惯写一个往返测试:随机生成 100 条不同长度的明文,加密再解密,比对原文。长度覆盖 1 字节到 200 字节,边界值单独测。这个测试跑通,基本能排除填充和长度处理的低级错误。
还有一个容易忽略的点:密钥文件的权限。Windows 下没有 Linux 的chmod 600,但至少别把私钥放在 Web 目录或共享盘。生产环境里私钥应该由密钥管理服务下发到内存,而不是落盘。我现在的习惯是,每次接入新的加密模块,先跑一遍往返测试,再检查私钥有没有被写进日志或临时文件。从那以后,凡是涉及密钥的代码,我都会强制走一遍「生成、加密、解密、释放」的完整链路,确认没有内存泄漏和明文残留才提交。希望帮到你。
本文还有配套的精品资源,点击获取