文章目录
使用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
#include <openssl/evp.h>
#include "DigestAlgo.h"
#include "CommonFun.h"
# define SM3_DIGEST_LENGTH 32
class EvpDigest {
public:
/**
* 创建摘要对象,指定摘要算法
* @param algo 摘要算法
*/
explicit EvpDigest(DigestAlgo algo);
virtual ~EvpDigest();
/**
* 更新数据
* @param str
* @return
*/
int digestUpdate(const std::string& str);
/**
* 计算摘要结果
* @return
*/
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
2.EvpDigest 类函数实现
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <iostream>
#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;
}
3.使用 EvpDigest 类计算摘要,文件 EvpDigestTest.cpp
#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 函数的实现,请参考 前面的文章