说明
测试平台系统:Ubuntu18.04 X86_64
测试语言:C++11
测试编译器:GCC version 7.5.0
测试内容:字符串和文件的MD5哈希值计算方法
一、MD5简介
关于16位和32位MD5值:
MD5得到的是一个16字节的哈希值(或称散列值),每个字节格式化位16进制(0x**),取数值部分的两个字符,连起来得到一个32个字符的字符串。这就是所说的32位MD5值。16位MD5值就是取的32位MD5值的中间段,即第9至24位(如果从0开始作为第一位,则是第8至23位)。
二、OpenSSL计算MD5介绍
OpenSSL计算MD5分为两种方式:
1. 非连续缓冲区的字符串计算:当有大量的数据块(比如文件)需要计算其MD5值时,使用此方法
涉及的OpenSSL函数:
int MD5_Init(MD5_CTX *c);
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
int MD5_Final(unsigned char *md, MD5_CTX *c);
计算方法:
首先调用MD5_Init初始化,然后多次调用MD5_Update只至所有数据处理完毕,最后调用MD5_Final函数从其第一个参数得到最终16字节的md5哈希值,最终转换为16进制后得到32位MD5字符串。
2. 连续缓冲区的字符串计算:当只有一个字符串需要计算其MD5值时,使用此方法
涉及的OpenSSL函数:
unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md);
计算方法:
直接调用此函数即可得到字符串对应的MD5哈希值,然后转换为16进制即可得到32位MD5字符串。其实此函数也是调用了“MD_Init/MD5_Update/MD5_Final”这三个函数实现。
三、OpenSSL开发库安装
sudo apt-get install libssl-dev # Debian / Ubuntu系统下libssl即openssl库
补充:
在基于 Redhat / Fedora 的系统上安装这些软件包方法
sudo dnf install openssl-devel
四、借助C++11实现基于OpenSSL库的MD5计算
Utils.cpp
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/md5.h>
#ifdef OPENSSL_IS_BORINGSSL
#include <openssl/base64.h>
#endif
#include <algorithm>
#include <cstring>
#include <sstream>
#include <iomanip>
#include "Utils.h"
std::string CUtils::ComputeContentMD5(const std::string& data, EMD5Len md5len)
{
return ComputeContentMD5(data.c_str(), data.size(), md5len);
}
std::string CUtils::ComputeContentMD5(const char * data, size_t size, EMD5Len md5len)
{
if (!data) {
return "";
}
unsigned char md[MD5_DIGEST_LENGTH];
MD5(reinterpret_cast<const unsigned char*>(data), size, (unsigned char*)&md);
std::string strMD5 = HexToString(md, MD5_DIGEST_LENGTH);
if(EMD5Len::Len16 == md5len)
{
return strMD5.substr(8, 16);
}
return strMD5;
}
std::string CUtils::ComputeContentMD5(std::istream& stream, EMD5Len md5len)
{
auto ctx = EVP_MD_CTX_create();
unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len = 0;
EVP_MD_CTX_init(ctx);
#ifndef OPENSSL_IS_BORINGSSL
EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW);
#endif
EVP_DigestInit_ex(ctx, EVP_md5(), nullptr);
auto currentPos = stream.tellg();
if (currentPos == static_cast<std::streampos>(-1)) {
currentPos = 0;
stream.clear();
}
stream.seekg(0, stream.beg);
char streamBuffer[2048];
while (stream.good())
{
stream.read(streamBuffer, 2048);
auto bytesRead = stream.gcount();
if (bytesRead > 0)
{
EVP_DigestUpdate(ctx, streamBuffer, static_cast<size_t>(bytesRead));
}
}
EVP_DigestFinal_ex(ctx, md_value, &md_len);
EVP_MD_CTX_destroy(ctx);
stream.clear();
stream.seekg(currentPos, stream.beg);
std::string strMD5 = HexToString(md_value, md_len);
if(EMD5Len::Len16 == md5len)
{
return strMD5.substr(8, 16);
}
return strMD5;
}
std::string CUtils::HexToString(const unsigned char *data, size_t size)
{
static char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
std::stringstream ss;
for (size_t i = 0; i < size; i++)
ss << hex[(data[i] >> 4)] << hex[(data[i] & 0x0F)];
return ss.str();
}
Utils.h
class CUtils
{
enum class EMD5Len
{
Len32,
Len16,
};
#define UNUSED_PARAM(x) ((void)(x))
public:
std::string ComputeContentMD5(const std::string& data, EMD5Len md5len = EMD5Len::Len32);
std::string ComputeContentMD5(const char *data, size_t size, EMD5Len md5len = EMD5Len::Len32);
std::string ComputeContentMD5(std::istream & stream, EMD5Len md5len = EMD5Len::Len32);
std::string HexToString(const unsigned char *data, size_t size);
};
main.cpp
#include <string>
#include <iostream>
#include <fstream>
#include <memory>
#include <openssl/md5.h>
#include "Utils.h"
int main()
{
std::cout << MD5_DIGEST_LENGTH << std::endl;
std::cout << CUtils::ComputeContentMD5("hello hello heelo", CUtils::EMD5Len::Len32) << std::endl;
std::cout << CUtils::ComputeContentMD5("hello hello heelo", CUtils::EMD5Len::Len16) << std::endl;
std::string filePath = "/home/dog/Dev/C++/utils/Url.h";
std::shared_ptr<std::iostream> content = std::make_shared<std::fstream>(filePath, std::ios::in | std::ios::binary); // 读取文件内容
std::cout << CUtils::ComputeContentMD5(*content, CUtils::EMD5Len::Len32) << std::endl;
std::cout << CUtils::ComputeContentMD5(*content, CUtils::EMD5Len::Len16) << std::endl;
return 0;
}
Build:
$ g++ -std=c++11 -o main main.cpp Utils.cpp -I./ -lcrypto
Run:
参考
1. 在线计算MD5:http://www.metools.info/other/o21.html
2. OpenSSL计算MD5:https://www.cnblogs.com/binchen-china/p/5653337.html
https://blog.youkuaiyun.com/weixin_34162401/article/details/93024139