简介:Crypto++ 5.6.5 是开源免费的 C++ 密码学库,涵盖对称加密(AES、DES、3DES)、非对称加密(RSA、ElGamal)、哈希函数(SHA-1、SHA-256)、消息认证码(HMAC)及伪随机数生成器(Fortuna)等算法,广泛用于数据保护、网络安全与数字证书验证。本资源面向需要在 Windows 平台快速集成密码学能力的 C++ 开发者,提供已针对 Visual Studio 2015 编译好的库文件与配套测试用例,省去自行编译配置的繁琐过程,可直接引入项目使用。压缩包为 gz 格式,大小约 18.13MB,内含编译产物与 cryptotest 测试脚本,可用于验证库功能正确性,并作为学习各类算法调用方式的示例代码。目前已有 464 人学习下载。借助现成库文件与测试用例,开发者能快速完成头文件包含路径与链接器设置,实例化 AES::Encryption 等对象进行加解密操作,同时通过运行测试确认库在本机环境下的可靠性,降低集成门槛与排错成本。
1. Crypto++ 5.6.5 在 VS2015 下的编译:为什么今天还有人折腾这套老组合
如果你手里有一个维护了七八年的 C++ 工程,里面用到了 AES、SHA、RSA 这类密码学原语,而且工程本身还锁在 VS2015 的工具链上,那你大概率绕不开 Crypto++ 5.6.5 这个版本。它不是最新的,甚至可以说有点老,但它的 API 稳定、依赖干净、源码自带完整测试用例,在很多存量项目里就是那个“能跑就别动”的存在。问题在于,这套组合的编译过程对新手并不友好:VS2015 的 MSBuild 对 C++11 的支持是半吊子,Crypto++ 5.6.5 又恰好卡在“用了一些新特性但又不完全依赖新标准”的尴尬位置,直接打开 sln 编译,十有八九会撞上一堆链接错误和警告。
这篇文章要解决的就是这件事:怎么在 VS2015 环境下,把 Crypto++ 5.6.5 编译成可用的静态库或动态库,怎么跑通它自带的测试用例来验证编译结果,以及在这个过程中哪些参数和配置项是必须动的。适合两类人看:一类是刚接手老项目、需要重新构建密码库的工程师;另一类是想拿 Crypto++ 当学习材料、但被编译门槛卡住的学生或自学者。我不会假设你手里有某个特定仓库地址,所有操作都基于“你从官方渠道拿到了 5.6.5 的源码压缩包”这个前提来写。
2. 编译前的环境确认与工程结构梳理
2.1 VS2015 工具链的版本检查与补丁确认
在动手之前,先确认你的 VS2015 到底是什么状态。打开“开发者命令提示符”,执行:
cl.exe你会看到类似Microsoft (R) C/C++ Optimizing Compiler Version 19.00.xxxxx for x86的输出。这里的19.00就是 VS2015 的编译器主版本号。Crypto++ 5.6.5 的源码里有一些条件编译分支,会根据_MSC_VER的值决定是否启用某些特性。VS2015 对应的是_MSC_VER == 1900,如果你看到的是19.10或更高,那说明你装的可能是 VS2017 或更新版本,编译脚本里的判断逻辑会走不同分支,后面遇到的错误也会不一样。
另外,VS2015 必须打上 Update 3 补丁。Update 3 之前的版本在 C++ 标准库实现上有一些已知问题,会导致 Crypto++ 的std::string和std::vector相关代码在链接阶段报错。检查方法是在 VS 里点“帮助”->“关于 Microsoft Visual Studio”,版本号里带Update 3字样才算合格。如果没有,先去装补丁,这一步没有捷径。
2.2 Crypto++ 5.6.5 源码目录里哪些文件真正参与编译
解压源码包后,你会看到根目录下有一堆.cpp和.h文件,还有一个cryptest.sln和若干.vcxproj。很多人第一反应是直接双击cryptest.sln,然后在 VS 里点“生成解决方案”。这个做法本身没错,但你要知道它到底在编译什么。
Crypto++ 的源码组织方式是“扁平化”的:所有算法实现都放在根目录的.cpp文件里,比如aes.cpp、sha.cpp、rsa.cpp。cryptest.sln里包含两个主要工程:一个是cryptlib,它把所有.cpp编译成一个静态库cryptlib.lib;另一个是cryptest,它链接cryptlib.lib并生成测试可执行文件cryptest.exe。你最终要拿到的“库文件”就是cryptlib.lib,而“测试用例”就是cryptest.exe跑出来的结果。
这里有一个容易忽略的点:Crypto++ 5.6.5 的工程文件默认使用“多字节字符集”而不是 Unicode。如果你的项目是 Unicode 的,后面链接时会出现_MBCS相关的符号冲突。我一般会在编译库之前,先把cryptlib工程的字符集改成“使用 Unicode 字符集”,这样生成的库兼容性更好。改法是在 VS 里右键cryptlib工程 ->“属性”->“配置属性”->“常规”->“字符集”,选“使用 Unicode 字符集”。
2.3 用 MSBuild 命令行编译 cryptlib 的最小命令
图形界面点来点去容易漏配置,我习惯用 MSBuild 命令行来编译,这样每一步都可复现。打开“VS2015 开发者命令提示符”,cd 到源码根目录,执行:
msbuild cryptlib.vcxproj /p:Configuration=Release /p:Platform=x64 /p:CharacterSet=Unicode /m这条命令的含义拆开看:cryptlib.vcxproj是库工程文件;Configuration=Release指定发布配置,调试配置会生成带调试符号的库,体积大且性能低,除非你要单步调试密码算法,否则用 Release;Platform=x64指定 64 位目标,如果你的主工程是 32 位,就改成Win32;CharacterSet=Unicode覆盖工程默认的字符集设置;/m允许并行编译,加快速度。
编译成功后,你会在x64\Output\Release\目录下看到cryptlib.lib。这个路径是 Crypto++ 工程预设的输出目录,不要随意改,因为cryptest工程的链接器配置里写死了相对路径。如果你改了输出目录,后面编译测试用例时会报“无法打开 cryptlib.lib”。
注意:如果你在编译过程中看到大量
C4996警告(关于std::copy或sprintf被标记为不安全),不用慌,这些是 VS2015 默认开启的“安全开发生命周期检查”产生的,不影响库的功能。想关掉的话,在工程属性里把“SDL 检查”设为“否”即可。
3. 编译测试用例 cryptest 并跑通自检流程
3.1 cryptest 工程的链接依赖与运行库配置
cryptest工程默认会去x64\Output\Release\找cryptlib.lib,但有一个前提:你的cryptlib必须已经用同样的配置(Release/x64/Unicode)编译过。如果之前编译cryptlib时用了 Win32,现在编译cryptest却选了 x64,链接器会报“模块计算机类型 x64 与目标计算机类型 x86 冲突”。这种错误信息很直白,但新手容易懵,记住“库和测试用例的 Platform 必须一致”就行。
另一个关键配置是“运行库”选项。Crypto++ 的工程默认使用“多线程 DLL (/MD)”,这意味着生成的cryptest.exe依赖vcruntime140.dll和msvcp140.dll。如果你的测试机器上没装 VS2015 运行库,exe 会直接闪退。解决办法有两个:要么在目标机器上装运行库,要么把工程改成“多线程 (/MT)”静态链接运行库。我一般选后者,因为测试用例经常要拷到别的机器上跑,静态链接省事。改法是在cryptlib和cryptest两个工程的“C/C++”->“代码生成”->“运行库”里,都改成“多线程 (/MT)”。
3.2 用命令行编译 cryptest 并执行全部自检
配置改好后,在源码根目录执行:
msbuild cryptest.vcxproj /p:Configuration=Release /p:Platform=x64 /p:CharacterSet=Unicode /m编译完成后,cryptest.exe会出现在x64\Output\Release\下。直接双击运行,你会看到一个交互式菜单,列出各种测试选项。但我们要的是自动化验证,所以用命令行参数:
cryptest.exe v这个v参数表示“验证所有算法”。程序会依次跑对称加密、哈希、公钥算法的已知答案测试(KAT),每个测试通过会打印passed,失败会打印FAILED并给出具体算法名。整个过程大概持续几十秒到几分钟,取决于机器性能。如果你看到最后输出All tests passed!,说明库编译正确,测试用例也跑通了。
还有一个常用参数是tv,它会输出更详细的测试向量,适合排查某个具体算法的问题。比如你怀疑 AES 的实现有问题,可以跑cryptest.exe tv aes,它只测 AES 相关的向量,输出每一组输入和输出,方便对比。
3.3 测试结果里哪些失败可以忽略、哪些必须修
跑cryptest.exe v的时候,偶尔会看到个别算法报FAILED,但大部分是passed。这种情况先别慌,看失败的是哪个算法。根据我的经验,在 VS2015 + Crypto++ 5.6.5 的组合下,最容易出问题的是SHA-3相关的测试,因为 5.6.5 里的 SHA-3 实现还是基于 Keccak 的早期版本,而测试向量可能用的是最终标准。这种失败不影响你使用 AES、SHA-256、RSA 这些主流算法,可以暂时忽略。
但如果失败的是AES、SHA-256、RSA这些核心算法,那就必须查。最常见的原因是编译时开了/O2优化但触发了编译器的某个 bug,导致常量折叠出错。解决办法是在cryptlib工程的“C/C++”->“优化”里,把“优化”从“最大优化 (/O2)”改成“自定义”,然后手动勾选除“省略帧指针”之外的所有选项。这个坑我在三个不同的项目里都踩过,症状是 AES 加密结果偶尔不对,换成/Od调试配置就正常,换成/O2就随机失败。
提示:如果你只是用 Crypto++ 做常规的加解密和签名验签,不需要跑全部测试用例。编译出
cryptlib.lib后,写一个最简单的 AES 加密小程序,能正确加解密一段字符串,就说明库是可用的。cryptest.exe v更适合作为交付前的完整性检查。
4. 避坑:VS2015 编译 Crypto++ 5.6.5 的五个血泪教训
4.1 现象:链接时报LNK2019无法解析的外部符号_memcpy_s
原因:Crypto++ 5.6.5 的源码里用了memcpy_s这个“安全版”函数,但 VS2015 的某些配置下,memcpy_s的声明在<string.h>里被条件编译屏蔽了。如果你的工程预处理器定义里没有_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES,编译器就找不到这个符号。
解决:在cryptlib工程的“C/C++”->“预处理器”->“预处理器定义”里,添加一行_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1。这个宏会让编译器把memcpy自动映射到memcpy_s,同时保留对旧代码的兼容。加完之后重新编译cryptlib,再链接cryptest,错误消失。
4.2 现象:编译cryptest时报fatal error C1083无法打开cryptlib.lib
原因:cryptest工程的链接器附加依赖项里写的是相对路径..\..\x64\Output\Release\cryptlib.lib,但你的cryptlib可能编译到了别的目录,或者你改了输出目录。还有一种可能是你编译cryptlib时选了 Debug 配置,而cryptest选了 Release,两个配置的输出目录不同。
解决:先确认cryptlib.lib的实际路径,然后在cryptest工程属性里,把“链接器”->“输入”->“附加依赖项”改成绝对路径,或者把cryptlib的输出目录改回默认的x64\Output\Release\。我一般选后者,因为改绝对路径会导致工程换台机器就编译不了。
4.3 现象:运行cryptest.exe时闪退,没有任何输出
原因:cryptest.exe依赖vcruntime140.dll和msvcp140.dll,如果目标机器没装 VS2015 运行库,程序启动时就会因为找不到 DLL 而闪退。另外,如果你把cryptest.exe拷到了别的目录,而它需要读取同目录下的测试向量文件(比如TestVectors/文件夹),也会因为找不到文件而异常退出。
解决:要么在目标机器上装 VS2015 运行库,要么在编译时把运行库改成“多线程 (/MT)”静态链接。如果是测试向量文件缺失,把源码根目录下的TestVectors文件夹整个拷到cryptest.exe所在目录即可。
4.4 现象:编译过程中 VS2015 卡死或报cl.exe内存不足
原因:Crypto++ 的某些.cpp文件(比如integer.cpp)非常大,包含大量模板实例化代码。VS2015 的 32 位编译器在处理这些文件时,内存占用会超过 2GB 上限,导致编译器崩溃或系统卡死。
解决:改用 64 位编译器。在 VS2015 开发者命令提示符里,用vcvarsall.bat amd64初始化环境,然后再执行msbuild。64 位编译器没有 2GB 内存限制,能顺利编译大文件。另外,把/m并行编译的并行度调低(比如/m:2),避免多个大文件同时编译耗尽内存。
4.5 现象:测试用例里RSA签名验证失败,但AES正常
原因:RSA 算法依赖大数运算库,而 Crypto++ 5.6.5 的大数运算在 VS2015 的/O2优化下有一个已知的“严格别名”问题。编译器在优化时假设不同类型的指针不会指向同一块内存,但 Crypto++ 的代码里有一些类型双关(type punning)操作,导致优化后的代码行为异常。
解决:在cryptlib工程的“C/C++”->“优化”里,把“启用严格别名”设为“否”。或者更彻底一点,在“C/C++”->“命令行”->“其他选项”里加上/d2noftol3和/d2noboost这两个未公开的编译器开关,它们能关闭 VS2015 里一些激进的优化。这个坑比较隐蔽,因为编译能过,只有跑 RSA 测试时才会暴露。
5. 把编译好的库集成到自己的工程:一个最小可用的 AES 加解密示例
5.1 工程配置:头文件路径、库路径与预处理器宏
假设你已经拿到了cryptlib.lib,现在要在一个新的 VS2015 控制台工程里用它。新建一个空项目,然后做三件事:
第一,在“C/C++”->“常规”->“附加包含目录”里,添加 Crypto++ 源码根目录的路径。因为 Crypto++ 的头文件是扁平化放置的,你直接包含#include "aes.h"就能找到,不需要加子目录。
第二,在“链接器”->“常规”->“附加库目录”里,添加cryptlib.lib所在的目录。
第三,在“链接器”->“输入”->“附加依赖项”里,填入cryptlib.lib。如果你用的是静态运行库(/MT),还要确保你的工程也用了 /MT,否则会出现运行库冲突。
另外,在“C/C++”->“预处理器”->“预处理器定义”里,加上CRYPTOPP_ENABLE_NAMESPACE_WEAK=0。这个宏在 5.6.5 里用来控制是否启用弱加密算法(比如 MD5),设为 0 可以避免一些命名空间冲突。
5.2 用 AES-CBC 加密一段字符串并验证解密结果
下面是一个最小可用的示例代码,演示 AES-256-CBC 加密和解密:
#include <iostream> #include <string> #include "aes.h" #include "modes.h" #include "filters.h" #include "hex.h" using namespace std; using namespace CryptoPP; int main() { // 32 字节密钥,对应 AES-256 byte key[AES::MAX_KEYLENGTH] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, 0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f }; // 16 字节 IV byte iv[AES::BLOCKSIZE] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; string plaintext = "Hello Crypto++ 5.6.5 on VS2015"; string ciphertext, recoveredtext; // 加密 CBC_Mode<AES>::Encryption encryptor; encryptor.SetKeyWithIV(key, sizeof(key), iv); StringSource ss1(plaintext, true, new StreamTransformationFilter(encryptor, new StringSink(ciphertext) ) ); // 解密 CBC_Mode<AES>::Decryption decryptor; decryptor.SetKeyWithIV(key, sizeof(key), iv); StringSource ss2(ciphertext, true, new StreamTransformationFilter(decryptor, new StringSink(recoveredtext) ) ); // 输出十六进制密文和还原后的明文 string hexcipher; StringSource ss3(ciphertext, true, new HexEncoder(new StringSink(hexcipher)) ); cout << "Ciphertext (hex): " << hexcipher << endl; cout << "Recovered: " << recoveredtext << endl; return 0; }这段代码的逻辑说明:CBC_Mode<AES>::Encryption构造了一个 AES-CBC 加密器,SetKeyWithIV同时设置密钥和初始向量。StringSource把明文字符串作为输入源,StreamTransformationFilter是 Crypto++ 的管道过滤器,负责把加密器应用到数据流上,StringSink把结果追加到ciphertext字符串。解密过程对称,只是把加密器换成解密器。
参数说明:key的长度必须是 32 字节(AES-256),如果你用 16 字节就是 AES-128,用 24 字节就是 AES-192。iv的长度固定 16 字节,CBC 模式要求 IV 不可预测但不需要保密,实际项目中每次加密都应该用随机 IV 并随密文一起传输。StreamTransformationFilter默认会自动填充 PKCS#7,解密时会自动去除填充,所以你不需要手动处理 padding。
5.3 编译运行这个示例时最容易遇到的三个链接错误
第一个错误:LNK2019无法解析CryptoPP::StringSource::StringSource。这是因为你只链接了cryptlib.lib,但StringSource的实现在filters.cpp里,而filters.cpp已经被编译进cryptlib.lib了。如果还是报这个错,检查你的“附加依赖项”里是不是漏了cryptlib.lib,或者库的 Platform 和你的工程不一致。
第二个错误:LNK2038检测到RuntimeLibrary不匹配。你的工程用了/MD,但cryptlib.lib是用/MT编译的。解决办法是统一运行库选项,要么都改成/MT,要么都改成/MD。我建议用/MT,因为静态链接运行库的 exe 部署更方便。
第三个错误:C4996警告被当作错误。VS2015 默认把std::copy的“不安全”警告升级为错误。在工程属性里把“C/C++”->“常规”->“SDL 检查”设为“否”,或者在“C/C++”->“高级”->“禁用特定警告”里填入4996。
5.4 验证库是否真正可用的三个检查点
第一个检查点:加密后的密文长度。对于 AES-CBC,密文长度应该是明文长度向上取整到 16 的倍数。比如明文 30 字节,密文应该是 32 字节。如果密文长度和明文一样,说明填充没生效,可能是StreamTransformationFilter的BlockPaddingScheme参数被设成了NO_PADDING。
第二个检查点:用同一个密钥和 IV 加密两次,密文应该完全一样。如果不一样,说明你的 IV 被随机化了,或者加密器状态没重置。CBC 模式下,相同的密钥和 IV 对相同明文产生相同密文,这是确定性的。
第三个检查点:把密文用十六进制打印出来,和在线 AES 加密工具的结果对比。注意在线工具通常默认用 PKCS#7 填充,密钥和 IV 要设成和你代码里一样的值。如果结果一致,说明库工作正常。
6. 进阶:用编译好的库做批量文件校验与性能摸底
6.1 用 SHA-256 对目录下所有文件生成摘要
编译出库之后,除了加解密,另一个高频用途是文件完整性校验。下面这段代码遍历指定目录,对每个文件计算 SHA-256 摘要:
#include <iostream> #include <fstream> #include <filesystem> #include <string> #include "sha.h" #include "filters.h" #include "hex.h" namespace fs = std::experimental::filesystem; using namespace std; using namespace CryptoPP; string sha256_file(const string& path) { SHA256 hash; string digest; FileSource fs(path.c_str(), true, new HashFilter(hash, new HexEncoder(new StringSink(digest))) ); return digest; } int main() { string dir = "C:\\testdata"; for (auto& p : fs::recursive_directory_iterator(dir)) { if (fs::is_regular_file(p)) { string digest = sha256_file(p.path().string()); cout << digest << " " << p.path().string() << endl; } } return 0; }逻辑说明:FileSource把文件内容读入管道,HashFilter把数据喂给 SHA256 对象并输出摘要,HexEncoder把二进制摘要转成十六进制字符串。recursive_directory_iterator递归遍历目录,is_regular_file过滤掉子目录。
参数说明:FileSource的第二个参数true表示“pump all”,即一次性读完整个文件。对于大文件(比如几个 GB),这样会占用大量内存,应该改成false并用ChannelSwitch分块读取。HashFilter的第二个参数是new HexEncoder(...),如果你不需要十六进制输出,可以直接用new StringSink(digest)得到二进制摘要。
6.2 用cryptest.exe tv提取特定算法的测试向量
cryptest.exe除了跑全量测试,还能输出测试向量。比如你想看 AES 的测试向量长什么样:
cryptest.exe tv aes输出会列出每一组密钥、明文、密文,格式是key: ...、plaintext: ...、ciphertext: ...。这些向量可以直接拿来写单元测试。如果你在集成 Crypto++ 到自己的项目时,想验证某个算法的行为是否和标准一致,用这个命令提取向量,然后在自己的测试代码里硬编码这些向量做断言,比跑全量cryptest.exe v更轻量。
6.3 性能摸底:AES-256 在不同缓冲区大小下的吞吐量
最后给一个简单的性能测试代码,帮你判断库在你的机器上的实际表现:
#include <iostream> #include <chrono> #include <vector> #include "aes.h" #include "modes.h" using namespace std; using namespace CryptoPP; int main() { byte key[AES::MAX_KEYLENGTH] = {0}; byte iv[AES::BLOCKSIZE] = {0}; vector<size_t> sizes = {1024, 4096, 16384, 65536, 1048576}; for (size_t sz : sizes) { vector<byte> plain(sz, 0x41); vector<byte> cipher(sz + AES::BLOCKSIZE); CBC_Mode<AES>::Encryption enc; enc.SetKeyWithIV(key, sizeof(key), iv); auto start = chrono::high_resolution_clock::now(); for (int i = 0; i < 100; i++) { enc.ProcessData(cipher.data(), plain.data(), sz); } auto end = chrono::high_resolution_clock::now(); double ms = chrono::duration<double, milli>(end - start).count(); double mbps = (sz * 100.0 / 1048576.0) / (ms / 1000.0); cout << "Buffer " << sz << " bytes: " << mbps << " MB/s" << endl; } return 0; }这段代码对每种缓冲区大小跑 100 次 AES-CBC 加密,计算吞吐量。注意ProcessData是原地加密,cipher和plain可以指向同一块内存,但这里为了清晰用了两个缓冲区。实际测试时,缓冲区越大,吞吐量越接近峰值,因为减少了函数调用和状态切换的开销。在典型的桌面 CPU 上,AES-256 的吞吐量应该在 100 MB/s 到 500 MB/s 之间,具体取决于 CPU 是否支持 AES-NI 指令集。Crypto++ 5.6.5 会自动检测 AES-NI 并启用硬件加速,你不需要手动配置。
我自己的习惯是:每次把 Crypto++ 集成到新项目之前,先跑一遍这个性能测试,记录基线数据。如果后面发现加解密变慢了,对比基线就能判断是库的问题还是业务代码的问题。这个习惯帮我省过好几次“以为是算法慢、其实是日志打太多”的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取