- 使用OpenSSL推荐的EVP高级接口计算摘要
- EvpDigest 类定义
- 1.EvpDigest 头文件接口
- 2.EvpDigest 类函数实现
- 3.使用 EvpDigest 类计算摘要,文件 EvpDigestTest.cpp
- 4.摘要实现类简要说明
- 总结
使用OpenSSL推荐的EVP高级接口计算摘要
直接使用openssl evp接口对底层低级接口进行了抽象,比较便于封装成C++类,与之前的 Digest 类相比,不需要在各个方法调用之前使用 switch 进行摘要算法的判断
- 创建摘要上下文,分配空间
- 初始化指定摘要算法的上下文
- 更新数据。可以添加多次,比如"hello, world" 可以分两次更新,可以参考如下代码
- 计算摘要值。对第3步更新的所有的数据计算摘要,通过传出参数返回摘要值
- 使用 EVP_MD_CTX_free 释放资源(重要)
- 将第4步计算的二进制摘要值转换为十六进制或Base64输出,本步骤可省略,看具体需要采用合适的方式
EvpDigest 类定义 1.EvpDigest 头文件接口
#ifndef OPENSSL_01_EVPDIGEST_H #define OPENSSL_01_EVPDIGEST_H #include2.EvpDigest 类函数实现#include "DigestAlgo.h" #include "CommonFun.h" # define SM3_DIGEST_LENGTH 32 class EvpDigest { public: explicit EvpDigest(DigestAlgo algo); virtual ~EvpDigest(); int digestUpdate(const std::string& str); int digestFinal(); void showDigestHex(); private: DigestAlgo digestAlgo; EVP_MD_CTX* context; const EVP_MD* evpMd; unsigned char* digest; unsigned int digestLength; }; #endif //OPENSSL_01_EVPDIGEST_H
#include3.使用 EvpDigest 类计算摘要,文件 EvpDigestTest.cpp#include #include #include "EvpDigest.h" EvpDigest::EvpDigest(DigestAlgo algo) : digestAlgo(algo) { switch (digestAlgo) { case DigestAlgo::MD5: digestLength = MD5_DIGEST_LENGTH; evpMd = EVP_md5(); break; case DigestAlgo::SHA1: digestLength = SHA_DIGEST_LENGTH; evpMd = EVP_sha1(); break; case DigestAlgo::SHA224: digestLength = SHA224_DIGEST_LENGTH; evpMd = EVP_sha224(); break; case DigestAlgo::SHA256: digestLength = SHA256_DIGEST_LENGTH; evpMd = EVP_sha256(); break; case DigestAlgo::SHA384: digestLength = SHA384_DIGEST_LENGTH; evpMd = EVP_sha384(); break; case DigestAlgo::SHA512: digestLength = SHA512_DIGEST_LENGTH; evpMd = EVP_sha512(); break; case DigestAlgo::SM3: digestLength = SM3_DIGEST_LENGTH; evpMd = EVP_sm3(); break; default: std::cout << "摘要算法类型错误..." << std::endl; exit(EXIT_FAILURE); } digest = new unsigned char[digestLength]; // 1.创建摘要上下文,分配空间 context = EVP_MD_CTX_new(); if (context == nullptr) { handleErrors(); } // 2.用指定摘要算法初始化上下文指针 int ret = EVP_DigestInit_ex(context, evpMd, nullptr); if (ret != 1) { handleErrors(); } } EvpDigest::~EvpDigest() { // 6.释放资源 delete[] digest; EVP_MD_CTX_free(context); std::cout << "析构函数调用..." << std::endl; } int EvpDigest::digestUpdate(const std::string& str) { // 3.传入待计算摘要的原始数据,update。可以调用多次 update int ret = EVP_DigestUpdate(context, str.c_str(), str.size()); if (ret != 1) { handleErrors(); } return ret; } int EvpDigest::digestFinal() { // 4.计算哈希值 int ret = EVP_DigestFinal_ex(context, digest, &digestLength); if (ret != 1) { handleErrors(); } return 0; } void EvpDigest::showDigestHex() { // 5.将二进制哈希值转换为十六进制输出 char* digestHex = new char[digestLength * 2 + 1]; for (int i = 0; i < digestLength; ++i) { sprintf(&digestHex[i * 2], "%02x", digest[i]); } std::cout << "摘要值: " << digestHex << std::endl; delete[] digestHex; }
#include "EvpDigest.h"
int main(int argc, char* argv[]) {
EvpDigest digest(DigestAlgo::SHA256);
digest.digestUpdate("123456");
digest.digestUpdate("123456");
digest.digestFinal();
digest.showDigestHex();
return 0;
}
4.摘要实现类简要说明
函数 EVP_DigestInit_ex 的第二个参数可以指定摘要算法,如 EVP_sha1、EVP_sha224、EVP_sha256、EVP_sha3_256、EVP_sm3……另外,EVP_sha256 与 EVP_sha3_256 有啥区别还没明白,以后明白了再补充
特别说明: OpenSSL3 里面包含国密算法,如 sm2、sm3、sm4,若要使用sm3计算摘要,可以在函数 EVP_DigestInit_ex 第二个参数传入 EVP_sm3()
几个函数的使用步骤
- 步骤1: 在构造函数中使用 EVP_XXX 函数给算法类型成员 evpMd 赋值,提供给 EVP_DigestInit_ex 函数的第二个参数,EVP_MD_CTX_new 给摘要上下文指针分配内存,同时根据算法类型记录摘要值字节长度
- 步骤2: 在构造函数中使用 EVP_DigestInit_ex 函数指定摘要算法,并初始化上下文指针
- 步骤3: 在 digestUpdate 函数中使用 EVP_DigestUpdate 函数更新数据,可以调用多次
- 步骤4: 在 digestFinal 函数中使用 EVP_DigestFinal_ex 计算摘要
- 步骤5: 补充一个比较重要的步骤,在析构函数中使用 EVP_MD_CTX_free 释放 EVP_MD_CTX_new 分配的资源,delete 运算符释放其他资源
- 步骤6: showDigestHex 函数将二进制摘要值转换为十六进制输出
CommonFun.h 头文件中包含 handleErrors 函数的实现,请参考 前面的文章
总结



