OpenSSL-3.0.3编程—封装EVP摘要计算为C++类EvpDigest


使用OpenSSL推荐的EVP高级接口计算摘要

直接使用openssl evp接口对底层低级接口进行了抽象,比较便于封装成C++类,与之前的 Digest 类相比,不需要在各个方法调用之前使用 switch 进行摘要算法的判断

  1. 创建摘要上下文,分配空间
  2. 初始化指定摘要算法的上下文
  3. 更新数据。可以添加多次,比如"hello, world" 可以分两次更新,可以参考如下代码
  4. 计算摘要值。对第3步更新的所有的数据计算摘要,通过传出参数返回摘要值
  5. 使用 EVP_MD_CTX_free 释放资源(重要)
  6. 将第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_sha1EVP_sha224EVP_sha256EVP_sha3_256EVP_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 函数的实现,请参考 前面的文章


总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值