c++ 通过(MD5和Merkle树)验证文件的完整性。
通过(MD5和Merkle树)来计算文件的哈希值,目的是验证文件的完整性。
MD5计算适用于单一哈希值的快速验证。
Merkle树适用于大文件的完整性检查,尤其在分布式系统中常用。
#include <iostream>
#include <openssl/md5.h>
#include <fstream>
#include <thread>
#include <vector>
#include <openssl/sha.h>
using namespace std;string GetFileListHash(string filepath)
{string hash;//以二进制方式打开文件ifstream ifs(filepath, ios::binary);if (!ifs)return hash;//一次读取多少字节的文件int block_size = 128;//文件读取bufunsigned char buf[1024] = { 0 };//hash输出unsigned char out[1024] = { 0 };while (!ifs.eof()){ifs.read((char*)buf, block_size);int read_size = ifs.gcount();if (read_size <= 0)break;MD5(buf, read_size, out);hash.insert(hash.end(), out, out + 16);}ifs.close();MD5((unsigned char*)hash.data(), hash.size(), out);return string(out,out+16);
}//文件可信树Hash
/*A A/ \ / \B C B C/ \ | / \ / \D E F D E F F/ \ / \ / \ / \ / \ / \ / \1 2 3 4 5 6 1 2 3 4 5 6 5 6
*/
string GetFileMerkleHash(string filepath)
{string hash;//存放hash叶子节点,后面所有结果都存在其中vector<string> hashes;ifstream ifs(filepath, ios::binary);if (!ifs)return hash;unsigned char buf[1024] = { 0 };unsigned char out[1024] = { 0 };int block_size = 128;while (!ifs.eof()){ifs.read((char*)buf, block_size);int read_size = ifs.gcount();if (read_size <= 0)break;SHA1(buf, read_size, out);//写入叶子节点的hash值hashes.push_back(string(out, out + 20));}while (hashes.size() > 1) // ==1 表示已经计算到root节点{//不是二的倍数补节点 (二叉树)if (hashes.size() & 1){//补充最后一个节点hashes.push_back(hashes.back());}//把两两节点的hash结果还写入hashes中,for (int i = 0; i < hashes.size() / 2; i++){//两个节点拼起来 i表示的是父节点string tmp_hash = hashes[i * 2];tmp_hash += hashes[i * 2 + 1];SHA1((unsigned char*)tmp_hash.data(), tmp_hash.size(), out);//写入结果hashes[i] = string(out, out + 20);}//hash列表删除上一次多余的hash值hashes.resize(hashes.size() / 2);}if (hashes.size() == 0) return hash;return hashes[0];
}
void PrintHex(string data)
{for (auto c : data)cout << hex << (int)(unsigned char)c;cout << endl;
}int main(int argc, char* argv[])
{cout << "Test Hash!" << endl;unsigned char data[] = "测试md5数据";unsigned char out[1024] = { 0 };int len = sizeof(data);MD5_CTX c;MD5_Init(&c);MD5_Update(&c, data, len);MD5_Final(out, &c);for (int i = 0; i < 16; i++)cout << hex << (int)out[i];cout << endl;data[1] = 9;MD5(data, len, out);for (int i = 0; i < 16; i++)cout << hex << (int)out[i];cout << endl;string filepath = "../../src/test_hash/test_hash.cpp";auto hash1 = GetFileListHash(filepath);PrintHex(hash1);//验证文件完整性for (;;){auto hash = GetFileListHash(filepath);auto thash = GetFileMerkleHash(filepath);cout << "HashList:\t";PrintHex(hash);cout << "MerkleTree:\t";PrintHex(thash);if (hash != hash1){ cout << "文件被修改" ;PrintHex(hash);} this_thread::sleep_for(1s);}getchar();return 0;
}
解析:
1. 获取文件的MD5哈希值(GetFileListHash函数)
该函数读取文件的每个块,并对每个块计算MD5哈希。
文件的哈希是通过多次计算块的哈希并合并它们来完成的。最后一次对合并后的所有块计算MD5,返回最终的文件哈希。
2. 获取文件的Merkle树哈希(GetFileMerkleHash函数)
该函数首先计算文件中每个块的SHA1哈希,形成Merkle树的叶子节点。
然后,通过将相邻的哈希值拼接并计算SHA1来构建Merkle树的上层节点,直到树的根节点计算出来。
3. 哈希计算:
先通过MD5_Init、MD5_Update和MD5_Final手动计算一个简单字符串的MD5哈希值。
4. 文件哈希:
然后计算指定文件的MD5和Merkle树哈希,用来检测文件是否被修改。
5. 文件完整性验证:
在无限循环中定期检查文件的哈希值,如果哈希值改变,输出“文件被修改”。