如何让本地文件SHA1哈希与GitHub API返回值匹配?
本地文件SHA1哈希与GitHub API返回值不匹配问题
我正在尝试将本地文件的哈希值与GitHub API返回的哈希值对比:
- 创建了
test.txt并上传到仓库,调用compute_sha1_hash函数得到的哈希值为:2aae6c35c94fcfb415dbe95f408b9ce91ee846ed - GitHub API响应中该文件的哈希值为:
95d09f2b10159347eece71399a7e2e907ea3df4f
已知GitHub使用SHA1算法,为何两者不匹配?是否需要额外计算才能对应?我已经实现了添加blob 前缀加文件长度加空字节的方案(compute_sha1_hash_2函数),但哈希值还是不匹配。
完整代码如下:
#include <curl/curl.h> #include <openssl/evp.h> #include <fstream> #include <vector> #include <sstream> #include <iomanip> #include <iostream> #include <regex> std::string compute_sha1_hash(const std::string& file_path) { std::ifstream file(file_path, std::ios::binary); if (!file) { throw std::runtime_error("Could not open file"); } EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx == NULL) { throw std::runtime_error("Failed to create EVP_MD_CTX"); } if (EVP_DigestInit_ex(mdctx, EVP_sha1(), NULL) != 1) { throw std::runtime_error("Failed to initialize SHA-1 context"); } std::vector<char> buffer(4096); while (file.read(buffer.data(), buffer.size())) { if (EVP_DigestUpdate(mdctx, buffer.data(), file.gcount()) != 1) { throw std::runtime_error("Failed to update SHA-1 context"); } } if (file.gcount() > 0) { if (EVP_DigestUpdate(mdctx, buffer.data(), file.gcount()) != 1) { throw std::runtime_error("Failed to update SHA-1 context"); } } std::vector<unsigned char> hash_value(EVP_MD_size(EVP_sha1())); unsigned int digest_len; if (EVP_DigestFinal_ex(mdctx, hash_value.data(), &digest_len) != 1) { throw std::runtime_error("Failed to finalize SHA-1 context"); } EVP_MD_CTX_free(mdctx); // Convert to hex std::ostringstream oss; for (const auto& byte : hash_value) { oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte); } return oss.str(); } // Brian613542705's answer std::string compute_sha1_hash_2(const std::string& file_path) { std::ifstream file(file_path, std::ios::binary); if (!file) { throw std::runtime_error("Could not open file"); } // Read the content of the file std::ostringstream oss; oss << file.rdbuf(); std::string content = oss.str(); // Prepend "blob " and the size of the content std::string preparedContent = "blob " + std::to_string(content.size()) + "\0" + content; EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx == NULL) { throw std::runtime_error("Failed to create EVP_MD_CTX"); } if (EVP_DigestInit_ex(mdctx, EVP_sha1(), NULL) != 1) { throw std::runtime_error("Failed to initialize SHA-1 context"); } if (EVP_DigestUpdate(mdctx, preparedContent.c_str(), preparedContent.size()) != 1) { throw std::runtime_error("Failed to update SHA-1 context"); } std::vector<unsigned char> hash_value(EVP_MD_size(EVP_sha1())); unsigned int digest_len; if (EVP_DigestFinal_ex(mdctx, hash_value.data(), &digest_len) != 1) { throw std::runtime_error("Failed to finalize SHA-1 context"); } EVP_MD_CTX_free(mdctx); // Convert to hex std::ostringstream sha1oss; for (const auto& byte : hash_value) { sha1oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte); } return sha1oss.str(); } static size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realsize = size * nmemb; auto& mem = *static_cast<std::string*>(userp); mem.append(static_cast<char*>(contents), realsize); return realsize; } void curl(std::string& data, const char* url) { CURL* curl_handle = curl_easy_init(); curl_easy_setopt(curl_handle, CURLOPT_URL, url); curl_easy_setopt(curl_handle, CURLOPT_TCP_KEEPALIVE, 0); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &data); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); // redirects CURLcode res = curl_easy_perform(curl_handle); if(res != CURLE_OK) std::cerr << "curl_easy_perform failed: " << curl_easy_strerror(res) << '\n'; curl_easy_cleanup(curl_handle); curl_global_cleanup(); } int main() { std::string fileHash = compute_sha1_hash("C:/Users/jabu/test.txt"); std::string fileHash_2 = compute_sha1_hash_2("C:/Users/jabu/test.txt"); std::string jsonString; curl(jsonString, "https://api.github.com/repos/jajabu33/test/git/trees/main?recursive=1"); // just to kick compare the strings std::regex shaPattern("\"sha\":\\s*\"([a-fA-F0-9]+)\""); std::sregex_iterator shaIterator(jsonString.begin(), jsonString.end(), shaPattern); std::sregex_iterator endIterator; std::string githubHash; for (; shaIterator != endIterator; ++shaIterator) { std::smatch match = *shaIterator; if (match.size() >= 2) githubHash = match[1].str(); } if (fileHash == githubHash) std::cout << "Hashes are equal\n"; else std::cout << "Hashes are not equal\n"; return 0; }
问题原因与解决方案
核心原因:换行符差异或文件读取逻辑问题
GitHub默认会将Windows格式的CRLF换行符转换为LF存储,若本地文件是CRLF格式,两者字节流内容和长度不一致,即使使用blob前缀计算哈希也无法匹配。另外,原compute_sha1_hash_2用ostringstream读取二进制文件可能存在隐藏的编码或字节处理问题。
解决步骤:
统一文件换行符格式
将本地文件转换为LF格式(与GitHub存储的一致),可通过VS Code等编辑器将换行符设置为LF后保存。修正文件读取与哈希计算逻辑
改用二进制方式直接读取文件字节,避免ostringstream可能带来的字节处理偏差:std::string compute_sha1_hash_2(const std::string& file_path) { std::ifstream file(file_path, std::ios::binary | std::ios::ate); if (!file) { throw std::runtime_error("Could not open file"); } std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); std::vector<char> buffer(size); if (!file.read(buffer.data(), size)) { throw std::runtime_error("Failed to read file"); } // 构建GitHub要求的blob前缀格式 std::string header = "blob " + std::to_string(size) + "\0"; std::string preparedContent; preparedContent.reserve(header.size() + size); preparedContent.append(header); preparedContent.append(buffer.data(), size); // 计算SHA1 EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx == NULL) { throw std::runtime_error("Failed to create EVP_MD_CTX"); } if (EVP_DigestInit_ex(mdctx, EVP_sha1(), NULL) != 1) { throw std::runtime_error("Failed to initialize SHA-1 context"); } if (EVP_DigestUpdate(mdctx, preparedContent.c_str(), preparedContent.size()) != 1) { throw std::runtime_error("Failed to update SHA-1 context"); } std::vector<unsigned char> hash_value(EVP_MD_size(EVP_sha1())); unsigned int digest_len; if (EVP_DigestFinal_ex(mdctx, hash_value.data(), &digest_len) != 1) { throw std::runtime_error("Failed to finalize SHA-1 context"); } EVP_MD_CTX_free(mdctx); // 转换为十六进制字符串 std::ostringstream sha1oss; sha1oss << std::hex << std::setfill('0'); for (const auto& byte : hash_value) { sha1oss << std::setw(2) << static_cast<int>(byte); } return sha1oss.str(); }精准提取目标文件的哈希值
原代码用正则匹配所有SHA1值,最后获取的可能不是test.txt的哈希。建议使用JSON库解析API响应,精准定位目标文件:// 引入nlohmann/json库 #include <nlohmann/json.hpp> using json = nlohmann::json; // 在main函数中替换哈希提取逻辑 json parsedJson = json::parse(jsonString); std::string githubHash; for (const auto& item : parsedJson["tree"]) { if (item["path"] == "test.txt") { githubHash = item["sha"]; break; } }验证文件字节一致性
通过GitHub API获取文件原始内容,保存后对比本地文件的字节流,确保两者完全一致。
验证方法
修改后重新运行代码:
- 确认本地文件与GitHub存储的文件字节流完全匹配(换行符、空字节等)
- 使用修正后的
compute_sha1_hash_2计算本地哈希 - 精准提取
test.txt对应的GitHub哈希 - 对比两者是否一致
内容的提问来源于stack exchange,提问作者Jabu
相关产品推荐
相关产品推荐

