一、 基本用法
// 创建MD5哈希对象
CryptographicHash hash(CryptographicHash::HashType::Md5);
// 添加数据
hash.addData("Hello World");
// 获取十六进制结果
std::string hexResult = hash.result();
// 获取二进制结果
std::string binResult = hash.result(CryptographicHash::Type::Binary);
必要头文件
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <map>
通过枚举关联算法长度和实现
enum class HashType : char {
Md5,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
};
using hashFunc = const EVP_MD* (*)();
const std::map<HashType, hashFunc> HashMethods = {
{HashType::Md5, EVP_md5},
{HashType::Sha1, EVP_sha1},
{HashType::Sha224, EVP_sha224},
{HashType::Sha256, EVP_sha256},
{HashType::Sha384, EVP_sha384},
{HashType::Sha512, EVP_sha512},
{HashType::Sha3_224, EVP_sha3_224},
{HashType::Sha3_256, EVP_sha3_256},
{HashType::Sha3_384, EVP_sha3_384},
{HashType::Sha3_512, EVP_sha3_512},
};
const std::map<HashType, int> HashLength
{
{HashType::Md5,MD5_DIGEST_LENGTH},
{HashType::Sha1,SHA_DIGEST_LENGTH},
{HashType::Sha224,SHA224_DIGEST_LENGTH},
{HashType::Sha256,SHA256_DIGEST_LENGTH},
{HashType::Sha384,SHA384_DIGEST_LENGTH},
{HashType::Sha512,SHA512_DIGEST_LENGTH},
{HashType::Sha3_224,SHA224_DIGEST_LENGTH},
{HashType::Sha3_256,SHA256_DIGEST_LENGTH},
{HashType::Sha3_384,SHA384_DIGEST_LENGTH},
{HashType::Sha3_512,SHA512_DIGEST_LENGTH},
};
二、核心实现步骤
2.1、EVP_MD_CTX_new
创建并初始化一个哈希函数上下文 EVP_MD_CTX
对象
EVP_MD_CTX *EVP_MD_CTX_new(void);
返回值:一个指向 EVP_MD_CTX
结构体对象的指针
EVP_MD_CTX
是 OpenSSL 中用于保存哈希函数上下文信息的结构体。它包含了执行哈希计算所需的各种状态、缓冲区和上下文信息。
调用 EVP_MD_CTX_new
函数会分配内存,并对 EVP_MD_CTX
结构体对象进行初始化。之后,可以将该对象传递给其他 OpenSSL 哈希函数相关的函数,以在其中进行进一步的处理和计算。
m_ctx = EVP_MD_CTX_new();
assert(m_ctx);
2.2、EVP_DigestInit_ex
用于初始化哈希(散列)函数的上下文
int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
参数解释:
ctx
:指向要初始化的EVP_MD_CTX
结构体指针,该结构体用于保存哈希函数的上下文信息。type
:指向EVP_MD
函数指针,表示要使用的哈希函数的类型。可以使用 OpenSSL 提供的各种哈希函数类型,如 SHA256、SHA512、MD5 等。impl
:可选参数,指定在初始化哈希函数上下文时要使用的加密引擎(如果有)。
返回值:
- 1:成功
- 0:失败
int ret = EVP_DigestInit_ex(m_ctx, HashMethods.at(hashType)(),NULL);
assert(ret == 1);
2.3、EVP_DigestUpdate
用于更新哈希函数的上下文,EVP_DigestUpdate
函数将 data
指向的数据添加到哈希函数的上下文中,并在计算摘要时使用这些数据。可以多次调用此函数以处理连续的数据块。
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, size_t count);
参数解释:
ctx
:指向哈希函数的上下文对象的指针。data
:指向要计算摘要的数据的指针。count
:要处理的数据的字节数。
返回值:
- 1:成功。
- 0:失败。
const int ret = EVP_DigestUpdate(m_ctx, data, length);
2.4、EVP_DigestFinal_ex
用于计算哈希函数的最终摘要,并将结果存储在指定的md缓冲区中,s记录数据长度,md和s都是传入传出参数
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
参数解释:
ctx
:指向哈希函数的上下文对象的指针。md
:指向存储摘要结果的缓冲区的指针。s
:指向用于存储摘要结果长度的变量的指针。
返回值:
- 1:成功
- 0:失败
unsigned int len = 0;
unsigned char md[HashLength.at(m_hashType)];
const int ret = EVP_DigestFinal_ex(m_ctx, md, &len);
在调用 EVP_DigestFinal_ex
函数之前,必须先调用 EVP_DigestInit_ex
和 EVP_DigestUpdate
函数,以便初始化哈希函数上下文并处理要计算摘要的数据。
2.5、EVP_MD_CTX_free
用于释放哈希函数的上下文对象所占用的内存空间
void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
参数解释:
ctx
:指向哈希函数的上下文对象的指针。
CryptographicHash::~CryptographicHash() {
if (m_ctx) {
EVP_MD_CTX_free(m_ctx);
}
}
完整示例:
- CryptographicHash.h
#ifndef CRYPTOGRAPHICHASH_H
#define CRYPTOGRAPHICHASH_H
#include <map>
#include <string>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
enum class HashType : char {
Md5,
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Sha3_224,
Sha3_256,
Sha3_384,
Sha3_512,
};
using hashFunc = const EVP_MD* (*)();
const std::map<HashType, hashFunc> HashMethods = {
{HashType::Md5, EVP_md5},
{HashType::Sha1, EVP_sha1},
{HashType::Sha224, EVP_sha224},
{HashType::Sha256, EVP_sha256},
{HashType::Sha384, EVP_sha384},
{HashType::Sha512, EVP_sha512},
{HashType::Sha3_224, EVP_sha3_224},
{HashType::Sha3_256, EVP_sha3_256},
{HashType::Sha3_384, EVP_sha3_384},
{HashType::Sha3_512, EVP_sha3_512},
};
const std::map<HashType, int> HashLength
{
{HashType::Md5,MD5_DIGEST_LENGTH},
{HashType::Sha1,SHA_DIGEST_LENGTH},
{HashType::Sha224,SHA224_DIGEST_LENGTH},
{HashType::Sha256,SHA256_DIGEST_LENGTH},
{HashType::Sha384,SHA384_DIGEST_LENGTH},
{HashType::Sha512,SHA512_DIGEST_LENGTH},
{HashType::Sha3_224,SHA224_DIGEST_LENGTH},
{HashType::Sha3_256,SHA256_DIGEST_LENGTH},
{HashType::Sha3_384,SHA384_DIGEST_LENGTH},
{HashType::Sha3_512,SHA512_DIGEST_LENGTH},
};
class CryptographicHash {
public:
enum class Type : char { Binary, Hex };
explicit CryptographicHash(HashType hashType);
~CryptographicHash();
//添加数据
void addData(const std::string &data) const;
void addData(const char *data, int length) const;
std::string result(Type type = Type::Hex) const;
private:
EVP_MD_CTX *m_ctx;
HashType m_hashType;
};
#endif //CRYPTOGRAPHICHASH_H
- CryptographicHash.cpp
#include "CryptographicHash.h"
#include <cassert>
CryptographicHash::CryptographicHash(const HashType hashType) {
m_hashType = hashType;
m_ctx = EVP_MD_CTX_new();
assert(m_ctx);
int ret = EVP_DigestInit_ex(m_ctx, HashMethods.at(hashType)(),NULL);
assert(ret == 1);
}
CryptographicHash::~CryptographicHash() {
if (m_ctx) {
EVP_MD_CTX_free(m_ctx);
}
}
void CryptographicHash::addData(const std::string &data) const {
addData(data.data(), data.size());
}
void CryptographicHash::addData(const char *data, const int length) const {
const int ret = EVP_DigestUpdate(m_ctx, data, length);
assert(ret == 1);
}
std::string CryptographicHash::result(const Type type) const {
unsigned int len = 0;
unsigned char md[HashLength.at(m_hashType)];
const int ret = EVP_DigestFinal_ex(m_ctx, md, &len);
assert(ret == 1);
//在将二进制哈希值转换为十六进制字符串时,二进制数据的每个字节(8 位)都会扩展为 2 个十六进制字符(每个字符 4 位)。
//md 是包含原始二进制摘要数据的数组,长度为 len 字节。将其转换为十六进制字符串时:
//md 中每个字节的范围是 0 到 255(0x00 到 0xFF),用十六进制表示时,每个字节正好需要2个字符:
//因此: 一个字节的最大值是 255,即十六进制的 FF,占两个字符。因此,它是 res[len*2]
if (type == Type::Hex) {
char res[len * 2];
for (int i = 0; i < len; ++i) {
sprintf(&res[i * 2], "%02x", md[i]);
}
return std::string(res, len * 2);
}
return std::string(reinterpret_cast<char *>(md), len);
}